SpringMVC(4)文件相关
小知识:
- 如果controller中的方法 , 没有设置返回值,系统就默认将请求映射作为返回值逻辑视图
1 |
|
- 配置文件的视图前缀 是 /WEB-INF/templates 后缀是.html
10、文件上传和下载
10.1、文件下载
- ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文,使用ResponseEntity实现下载文件的功能
<a th:href="@{/test/download}">文件下载测试 </a><br/>
controller方法
1 |
|
10.2、文件上传
- 文件上传要求form表单的请求方式必须为post,并且添加属性enctype=“multipart/form-data”
SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息
1>添加依赖:
1 | <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload --> |
2>在SpringMVC的配置文件中添加配置:
1 | <!--必须通过文件解析器的解析才能将文件转换为MultipartFile对象--> |
3>控制器方法
1 | <form th:action="@{/test/upload}" method="post" enctype="multipart/form-data"> |
- getName() 获取表单域 的 name属性的值
1 |
|
MultipartFile类
- MultipartFile是SpringMVC提供简化上传操作的工具类。
常用方法
1 | package org.springframework.web.multipart; |
InputStreamSource
这个接口本质上返回的还是一个InputStream 流对象
1 | package org.springframework.core.io; |
4>解决文件重名问题
- 由于输出流默认的是以覆盖方式写文件,因此如果有重名的文件就会覆盖之前的文件
1.UUID 简介
-
UUID 含义是通用唯一识别码 (Universally Unique Identifier),这是一个软件建构的标准。
也是被开源软件基金会 (Open Software Foundation, OSF) 的组织应用在分布式计算环境 (Distributed Computing Environment, DCE) 领域的一部分。
UUID 的目的,是让分布式系统中的所有元素,都能有唯一的辨识资讯,而不需要透过中央控制端来做辨识资讯的指定。
如此一来,每个人都可以建立不与其它人冲突的 UUID。在这样的情况下,就不需考虑数据库建立时的名称重复问题。
2.UUID 组成
UUID保证对在同一时空中的所有机器都是唯一的。通常平台会提供生成的API。
按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字。
UUID由以下几部分的组合:
- 当前日期和时间,UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同。
- 时钟序列。
- 全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。UUID的唯一缺陷在于生成的结果串会比较长。关于UUID这个标准使用最普遍的是微软的GUID(Globals Unique Identifiers)。
标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)
。
解决方法:
- 使用UUID改变文件名字
1 | //处理文件重名问题 |
11、拦截器(了解)
- 需要实现
HandlerInterceptor
接口, 需要注意的是 接口里面已经有写好的方法了
1 | public class FirstInterceptor implements HandlerInterceptor { |
11.1、拦截器的配置
-
SpringMVC中的拦截器用于拦截控制器方法的执行
-
SpringMVC中的拦截器需要实现HandlerInterceptor
-
SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置:
**1>**直接通过bean
不使用ref
1 | <mvc:interceptors> |
使用ref
1 | <bean id="firstInterceptor" class="pers.dhx_.interceptor.FirstInterceptor"/> |
**2>**通过@Component 以及组件扫描
- 通过注解 && 组件扫描
1 |
|
- 组件扫描
- 开启组件扫描(可以获取bean, 相当于配置了
<bean id="firstInterceptor" class="pers.dhx_.interceptor.FirstInterceptor"/>
) - 使用ref 获取
- 开启组件扫描(可以获取bean, 相当于配置了
1 | <!-- 自动扫描包 --> |
**3>**通过<mvc:interceptor>
- 以上三种配置方式都是对DispatcherServlet所处理的所有的请求进行拦截
- 可以通过ref或bean标签设置拦截器,通过mvc:mapping设置需要拦截的请求,通过
mvc:exclude-mapping设置需要排除的请求,即不需要拦截的请求
<mvc:exclude-mapping path="/testRequestEntity"/>
设置不拦截的请求
1 | <mvc:interceptor> |
- mvc:mapping 里面的path 的
/*
的意思是当只有请求的上下文路径下只有一层路径的时候才会进行拦截- 如果有多层就不会拦截
- 如果要拦截所有的请求, 需要使用
/**
11.2、拦截器的三个抽象方法
SpringMVC中的拦截器有三个抽象方法:
- preHandle:控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行
- 返回true为放行,即调用控制器方法;
- 返回false表示拦截,即不调用控制器方法
- postHandle:控制器方法执行之后执行postHandle()
- afterCompletion:处理完视图和模型数据,渲染视图完毕之后执行afterCompletion()
不管是否有资源(404) ,这三个方法都会执行
- bean和ref 标签所配置的拦截器默认对DispatchcerServlet处理的所有请求进行拦截
11.3、多个拦截器的执行顺序
1>若每个拦截器的preHandle()都返回true
此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关:
- preHandle()会按照配置的顺序执行,而postHandle()和afterCompletion()会按照配置的反序执行
2>若某个拦截器的preHandle()返回了false
- 返回false的
preHandle()
和它之前的拦截器的preHandle()都会执行, - 所有的postHandle()都不执行,
- 返回false的preHandle()拦截器之前的拦截器的afterCompletion()会执行
12、异常处理器
12.1、基于配置的异常处理
- 不配置也可以,springmvc默认配置 :
DefaultHandlerExceptionResolver
SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolver
HandlerExceptionResolver
接口的实现类有:
DefaultHandlerExceptionResolver
- ``SimpleMappingExceptionResolver`
SpringMVC提供了自定义的异常处理器SimpleMappingExceptionResolver,使用方式:
properties标签:
- key设置需要处理的异常,
- value设置出现异常时要跳转的逻辑视图
spring配置文件:
1 | <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> |
-
exceptionAttribute属性设置一个属性名,将出现的异常信息在请求域中进行共享
-
<property name="exceptionAttribute" value="ex"></property>
-
比如可以在前端页面中写 :
<p th:text="${ex}> </p>"
访问页面时就是显示当前异常的属性值(相当于通过属性名获取了属性名)
-
12.2、基于注解的异常处理
简单使用
@ControllerAdvice
将当前类标识为异常处理的组件@ExceptionHandler
用于设置所标识方法处理的异常- ex表示当前请求处理中出现的异常对象 errorException
1 |
|
@ControllerAdvice
这个类是为那些声明了(
@ExceptionHandler
、@InitBinder
或@ModelAttribute
注解修饰的)方法的类而提供的专业化的@Component
, 以供多个Controller
类所共享。说白了,就是aop思想的一种实现,你告诉我需要拦截规则,我帮你把他们拦下来,具体你想做更细致的拦截筛选和拦截之后的处理,你自己通过
@ExceptionHandler
、@InitBinder
或@ModelAttribute
这三个注解以及被其注解的方法来自定义。
- 首先,
ControllerAdvice
本质上是一个Component
,因此也会被当成组建扫描,一视同仁,扫扫扫。
初定义拦截规则:
-
ControllerAdvice
提供了多种指定Advice规则的定义方式,默认什么都不写,则是Advice所有Controller,当然你也可以通过下列的方式指定规则
比如对于String[] value() default {}
, 写成@ControllerAdvice("org.my.pkg")
或者@ControllerAdvice(basePackages="org.my.pkg")
, 则匹配org.my.pkg
包及其子包下的所有Controller
, -
当然也可以用数组的形式指定,如:
@ControllerAdvice(basePackages={"org.my.pkg", "org.my.other.pkg"})
, 也可以通过指定注解来匹配,比如我自定了一个@CustomAnnotation
注解,我想匹配所有被这个注解修饰的Controller
, 可以这么写:@ControllerAdvice(annotations={CustomAnnotation.class})
源码
1 |
|
1.处理全局异常
@ControllerAdvice
配合@ExceptionHandler
实现全局异常处理
- 用于在特定的处理器类、方法中处理异常的注解
接收Throwable类作为参数,我们知道Throwable是所有异常的父类,所以说,可以自行指定所有异常比如在方法上加:@ExceptionHandler(IllegalArgumentException.class)
,
则表明此方法处理IllegalArgumentException
类型的异常,
如果参数为空,将默认为方法参数列表中列出的任何异常(方法抛出什么异常都接得住)。
下面的例子:
- 处理所有
IllegalArgumentException
异常,域中加入错误信息errorMessage
并返回错误页面error
1 |
|
2.预设全局数据
@ControllerAdvice
配合@ModelAttribute
预设全局数据
@ModelAttribute
源码
1 | /** |
- 实际上这个注解的作用就是,允许你往
Model
中注入全局属性(可以供所有Controller中注有@Request Mapping的方法使用),value
和name
用于指定 属性的key
,binding
表示是否绑定,默认为true
。4
具体使用方法
-
全局参数绑定
- 方式一:
1
2
3
4
5
6
7
public class MyGlobalHandler {
public void presetParam(Model model){
model.addAttribute("globalAttr","this is a global attribute");
}
}这种方式比较灵活,需要什么自己加就行了,加多少属性自己控制
- 方式二:
1
2
3
4
5
6
7
8
9
10
11
12
public class MyGlobalHandler {
public Map<String, String> presetParam(){
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");
return map;
}
}这种方式对于加单个属性比较方便。默认会把返回值(如上面的map)作为属性的value,而对于key有两种指定方式:
- 当
@ModelAttribute()
不传任何参数的时候,默认会把返回值的字符串值作为key,如上例的key
则是 ”map"(值得注意的是,不支持字符串的返回值作为key)。 - 当
@ModelAttribute("myMap")
传参数的时候,则以参数值作为key
,这里key
则是 ”myMap“。
-
全局参数使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class AdviceController {
public String methodOne(Model model){
Map<String, Object> modelMap = model.asMap();
return (String)modelMap.get("globalAttr");
}
public String methodTwo({ String globalAttr)
return globalAttr;
}
public String methodThree(ModelMap modelMap) {
return (String) modelMap.get("globalAttr");
}
}这三种方式大同小异,其实都是都是从
Model
中存储属性的Map
里取数据。
3.请求参数预处理
@ControllerAdvice
配合@InitBinder
实现对请求参数的预处理再次之前我们先来了解一下
@IniiBinder
,先看一下源码,我会提取一些重要的注释进行浅析
1 | /** |
我们来看看具体用途,其实这些用途在
Controller
里也可以定义,但是作用范围就只限当前Controller,因此下面的例子我们将结合ControllerAdvice
作全局处理。
-
参数处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MyGlobalHandler {
public void processParam(WebDataBinder dataBinder){
/*
* 创建一个字符串微调编辑器
* 参数{boolean emptyAsNull}: 是否把空字符串("")视为 null
*/
StringTrimmerEditor trimmerEditor = new StringTrimmerEditor(true);
/*
* 注册自定义编辑器
* 接受两个参数{Class<?> requiredType, PropertyEditor propertyEditor}
* requiredType:所需处理的类型
* propertyEditor:属性编辑器,StringTrimmerEditor就是 propertyEditor的一个子类
*/
dataBinder.registerCustomEditor(String.class, trimmerEditor);
//同上,这里就不再一步一步讲解了
binder.registerCustomEditor(Date.class,
new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), false));
}
}这样之后呢,就可以实现全局的实现对
Controller
中RequestMapping
标识的方法中的所有String
和Date
类型的参数都会被作相应的处理。Controller:
1
2
3
4
5
6
7
8
9
10
11
12
public class BinderTestController {
public Map<String, Object> test(String str, Date date) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
map.put("str", str);
map.put("data", date);
return map;
}
}测试结果:
我们可以看出,str
和 date
这两个参数在进入 Controller
的test的方法之前已经被处理了,str
被去掉了两边的空格(%20
在Http url 中是空格的意思),String
类型的 1997-1-10
被转换成了Date
类型。
-
参数绑定
参数绑定可以解决特定问题,那么我们先来看看我们面临的问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class Person {
private String name;
private Integer age;
// omitted getters and setters.
}
class Book {
private String name;
private Double price;
// omitted getters and setters.
}
public class BinderTestController {
public void test(Person person, Book book) throws Exception {
System.out.println(person);
System.out.println(book);
}
}我们会发现
Person
类和Book
类都有name
属性,那么这个时候就会出先问题,它可没有那么只能区分哪个name
是哪个类的。因此@InitBinder
就派上用场了:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyGlobalHandler {
/*
* @InitBinder("person") 对应找到@RequstMapping标识的方法参数中
* 找参数名为person的参数。
* 在进行参数绑定的时候,以‘p.’开头的都绑定到名为person的参数中。
*/
public void BindPerson(WebDataBinder dataBinder){
dataBinder.setFieldDefaultPrefix("p.");
}
public void BindBook(WebDataBinder dataBinder){
dataBinder.setFieldDefaultPrefix("b.");
}
}因此,传入的同名信息就能对应绑定到相应的实体类中:
p.name -> Person.name b.name -> Book.name
还有一点注意的是如果
@InitBinder("value")
中的value
值和Controller
中@RequestMapping()
标识的方法的参数名不匹配,则就会产生绑定失败的后果,如:-
@InitBinder(“p”)、
-
@InitBinder(“b”)
-
public void test(Person person, Book book)
上述情况就会出现绑定失败,有两种解决办法:
-
统一名称,要么全叫
p
,要么全叫person
,只要相同就行。 -
方法参数加
@ModelAttribute
,有点类似@RequestParam1
2
3、
public void test( Person person, Book book)
-