img

小知识:

  • 如果controller中的方法 , 没有设置返回值,系统就默认将请求映射作为返回值逻辑视图
1
2
3
4
@RequestMapping("/test/voidTest")
public void test(){
System.out.println(this.getClass());
}
  • 配置文件的视图前缀 是 /WEB-INF/templates 后缀是.html

image-20220728115208217

10、文件上传和下载

10.1、文件下载

  • ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文,使用ResponseEntity实现下载文件的功能

<a th:href="@{/test/download}">文件下载测试 </a><br/>

controller方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@RequestMapping("/test/download")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws
IOException {
//如果要下载别的文件,区别就是 文件名不一样, 可以直接当做模板
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径
String realPath = servletContext.getRealPath("/static/img/0bd44a2d1fusXjl.jpg");
/*如果不知道分隔符用什么 可以直接用*/ realPath=realPath+ File.separator+"0bd44a2d1fusXjl.jpg";
//创建输入流
InputStream is = new FileInputStream(realPath);
//创建字节数组
byte[] bytes = new byte[is.available()]; // is.available()获取当前输入流所对应的文件的总字节数
//将流读到字节数组中
is.read(bytes);
//创建HttpHeaders对象设置响应头信息
MultiValueMap<String, String> headers = new HttpHeaders();
//前面的MultiValueMap是map的子接口 HttpHeaders() 是实现类
//设置要下载方式以及下载文件的名字
headers.add("Content-Disposition", "attachment;filename=图片.jpg");
//"Content-Disposition" 设置下载方式,filename表示下载下来的文件的默认的名字
//设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
//关闭输入流
is.close();
return responseEntity;
}

10.2、文件上传

  • 文件上传要求form表单的请求方式必须为post,并且添加属性enctype=“multipart/form-data”
    SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息

1>添加依赖:

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>

2>在SpringMVC的配置文件中添加配置:

1
2
3
4
5
6
7
<!--必须通过文件解析器的解析才能将文件转换为MultipartFile对象-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--注意这个bean 必须通过id 获取,通过clas获取会报错-->
<property name="maxUploadSize" value="1000000000000"/>
<!--设置文件上传的最大大小-->
<property name="defaultEncoding" value="utf-8"/> <!--设置默认编码-->
</bean>

3>控制器方法

1
2
3
4
<form   th:action="@{/test/upload}" method="post" enctype="multipart/form-data">
头像: <input type="file" name="photo"><br/>
<input type="submit" value="文件上传测试" />文件上传测试 <br/>
</form>
  • getName() 获取表单域 的 name属性的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

@RequestMapping("/test/upload")
public String testUp(MultipartFile photo, HttpSession session) throws
IOException {
// getName() 获取表单域 的 name属性的值
//获取上传的文件的文件名
String fileName = photo.getOriginalFilename();
//处理文件重名问题
String hzName = fileName.substring(fileName.lastIndexOf("."));
fileName = UUID.randomUUID().toString() + hzName;
//获取服务器中photo目录的路径 获取当前工程下photo目录的真实路径
ServletContext servletContext = session.getServletContext();
String photoPath = servletContext.getRealPath("photo");
//创建photoPath所对应的file对象
File file = new File(photoPath);
if(!file.exists()){ // 如果不存在 target/demo04_ajax-1.0-SNAPSHOT/photo/file
file.mkdir();
}
String finalPath = photoPath + File.separator + fileName;
//实现上传功能
photo.transferTo(new File(finalPath));
return "success";
}

MultipartFile类

  • MultipartFile是SpringMVC提供简化上传操作的工具类。
常用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package org.springframework.web.multipart;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.springframework.core.io.InputStreamSource;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.FileCopyUtils;

public interface MultipartFile extends InputStreamSource {
//getName() 返回参数的名称
String getName();
//获取源文件的昵称
@Nullable
String getOriginalFilename();
//getContentType() 返回文件的内容类型
@Nullable
String getContentType();
//isEmpty() 判断是否为空,或者上传的文件是否有内容
boolean isEmpty();
//getSize() 返回文件大小 以字节为单位
long getSize();
//getBytes() 将文件内容转化成一个byte[] 返回
byte[] getBytes() throws IOException;
//getInputStream() 返回InputStream读取文件的内容
InputStream getInputStream() throws IOException;

default Resource getResource() {
return new MultipartFileResource(this);
}
//transferTo(File dest) 用来把 MultipartFile 转换换成 File
void transferTo(File var1) throws IOException, IllegalStateException;

default void transferTo(Path dest) throws IOException, IllegalStateException {
FileCopyUtils.copy(this.getInputStream(), Files.newOutputStream(dest));
}
}

InputStreamSource 这个接口本质上返回的还是一个InputStream 流对象

1
2
3
4
5
6
7
8
9
10
package org.springframework.core.io;

import java.io.IOException;
import java.io.InputStream;

public interface InputStreamSource {
//定位并打开资源,返回资源对应的输入流。
//每次调用都会返回新的输入流,调用者在使用完毕后必须关闭该资源。
InputStream getInputStream() throws IOException;
}

4>解决文件重名问题

  • 由于输出流默认的是以覆盖方式写文件,因此如果有重名的文件就会覆盖之前的文件

1.UUID 简介

  • UUID 含义是通用唯一识别码 (Universally Unique Identifier),这是一个软件建构的标准。

    也是被开源软件基金会 (Open Software Foundation, OSF) 的组织应用在分布式计算环境 (Distributed Computing Environment, DCE) 领域的一部分。

    UUID 的目的,是让分布式系统中的所有元素,都能有唯一的辨识资讯,而不需要透过中央控制端来做辨识资讯的指定。

    如此一来,每个人都可以建立不与其它人冲突的 UUID。在这样的情况下,就不需考虑数据库建立时的名称重复问题。

2.UUID 组成

UUID保证对在同一时空中的所有机器都是唯一的。通常平台会提供生成的API。

按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字。

UUID由以下几部分的组合:

  1. 当前日期和时间,UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同。
  2. 时钟序列。
  3. 全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。UUID的唯一缺陷在于生成的结果串会比较长。关于UUID这个标准使用最普遍的是微软的GUID(Globals Unique Identifiers)。

标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)

解决方法:

  • 使用UUID改变文件名字
1
2
3
//处理文件重名问题
String hzName = fileName.substring(fileName.lastIndexOf("."));
fileName = UUID.randomUUID().toString() + hzName;

11、拦截器(了解)

  • 需要实现HandlerInterceptor接口, 需要注意的是 接口里面已经有写好的方法了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class FirstInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("FirstInterceptor-->preHandle");
return HandlerInterceptor.super.preHandle(request, response, handler);
}

@Override // 在控制器方法执行之后执行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("FirstInterceptor-->postHandle");

HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}

@Override // 在渲染完视图之后执行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("FirstInterceptor-->afterCompletion");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}

11.1、拦截器的配置

  • SpringMVC中的拦截器用于拦截控制器方法的执行

  • SpringMVC中的拦截器需要实现HandlerInterceptor

  • SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置:

**1>**直接通过bean

不使用ref

1
2
3
<mvc:interceptors>
<bean class="pers.dhx_.interceptor.FirstInterceptor"/>
</mvc:interceptors>

使用ref

1
2
3
4
<bean id="firstInterceptor" class="pers.dhx_.interceptor.FirstInterceptor"/>
<mvc:interceptors>
<bean class="pers.dhx_.interceptor.FirstInterceptor"/>
</mvc:interceptors>

**2>**通过@Component 以及组件扫描

  • 通过注解 && 组件扫描
1
2
3
4
@Component
public class FirstInterceptor implements HandlerInterceptor {

}
  • 组件扫描
    1. 开启组件扫描(可以获取bean, 相当于配置了
      <bean id="firstInterceptor" class="pers.dhx_.interceptor.FirstInterceptor"/>
    2. 使用ref 获取
1
2
3
4
5
<!-- 自动扫描包 -->
<context:component-scan base-package="pers.dhx_"/>
<mvc:interceptors>
<bean class="pers.dhx_.interceptor.FirstInterceptor"/>
</mvc:interceptors>

**3>**通过<mvc:interceptor>

  • 以上三种配置方式都是对DispatcherServlet所处理的所有的请求进行拦截
  • 可以通过ref或bean标签设置拦截器,通过mvc:mapping设置需要拦截的请求,通过
    mvc:exclude-mapping设置需要排除的请求,即不需要拦截的请求

<mvc:exclude-mapping path="/testRequestEntity"/> 设置不拦截的请求

1
2
3
4
5
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/testRequestEntity"/>
<ref bean="firstInterceptor"></ref>
</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
2
3
4
5
6
7
8
9
10
11
12
13
14
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--
properties的键表示处理器方法执行过程中出现的异常
properties的值表示若出现指定异常时,设置一个新的视图名称,跳转到指定页面
-->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>

<!--exceptionAttribute属性设置一个属性名,将出现的异常信息在请求域中进行共享-->
<property name="exceptionAttribute" value="ex"></property>
</bean>
  • exceptionAttribute属性设置一个属性名,将出现的异常信息在请求域中进行共享

  • <property name="exceptionAttribute" value="ex"></property>

    • 比如可以在前端页面中写 :

      <p th:text="${ex}> </p>"

      访问页面时就是显示当前异常的属性值(相当于通过属性名获取了属性名)

12.2、基于注解的异常处理

简单使用

  • @ControllerAdvice将当前类标识为异常处理的组件
  • @ExceptionHandler用于设置所标识方法处理的异常
  • ex表示当前请求处理中出现的异常对象 errorException
1
2
3
4
5
6
7
8
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler(ArithmeticException.class)
public String handleArithmeticException(Exception ex, Model model){
model.addAttribute("ex", ex);
return "error";
}
}

@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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {

@AliasFor("basePackages")
String[] value() default {};

@AliasFor("value")
String[] basePackages() default {};

Class<?>[] basePackageClasses() default {};

Class<?>[] assignableTypes() default {};

Class<? extends Annotation>[] annotations() default {};

}

1.处理全局异常

@ControllerAdvice 配合 @ExceptionHandler 实现全局异常处理

  • 用于在特定的处理器类、方法中处理异常的注解

在这里插入图片描述

接收Throwable类作为参数,我们知道Throwable是所有异常的父类,所以说,可以自行指定所有异常比如在方法上加:@ExceptionHandler(IllegalArgumentException.class)

则表明此方法处理IllegalArgumentException 类型的异常,

如果参数为空,将默认为方法参数列表中列出的任何异常(方法抛出什么异常都接得住)。

下面的例子:

  • 处理所有IllegalArgumentException异常,域中加入错误信息errorMessage 并返回错误页面error
1
2
3
4
5
6
7
8
9
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ModelAndView handleException(IllegalArgumentException e){
ModelAndView modelAndView = new ModelAndView("error");
modelAndView.addObject("errorMessage", "参数不符合规范!");
return modelAndView;
}
}

2.预设全局数据

@ControllerAdvice 配合 @ModelAttribute 预设全局数据

@ModelAttribute源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Annotation that binds a method parameter or method return value
* to a named model attribute, exposed to a web view. Supported
* for controller classes with {@link RequestMapping @RequestMapping}
* methods.
* 此注解用于绑定一个方法参数或者返回值到一个被命名的model属性中,暴露给web视图。支持在
* 在Controller类中注有@RequestMapping的方法使用(这里有点拗口,不过结合下面的使用介绍
* 你就会明白的)
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ModelAttribute {

@AliasFor("name")
String value() default "";

@AliasFor("value")
String name() default "";

boolean binding() default true;

}
  • 实际上这个注解的作用就是,允许你往 Model 中注入全局属性(可以供所有Controller中注有@Request Mapping的方法使用),valuename 用于指定 属性的 keybinding 表示是否绑定,默认为 true。4
具体使用方法
  • 全局参数绑定

    • 方式一:
    1
    2
    3
    4
    5
    6
    7
    @ControllerAdvice
    public class MyGlobalHandler {
    @ModelAttribute
    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
    @ControllerAdvice
    public class MyGlobalHandler {

    @ModelAttribute()
    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有两种指定方式:

    1. @ModelAttribute() 不传任何参数的时候,默认会把返回值的字符串值作为key,如上例的 key 则是 ”map"(值得注意的是,不支持字符串的返回值作为key)。
    2. @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
    @RestController
    public class AdviceController {

    @GetMapping("methodOne")
    public String methodOne(Model model){
    Map<String, Object> modelMap = model.asMap();
    return (String)modelMap.get("globalAttr");
    }

    @GetMapping("methodTwo")
    public String methodTwo(@ModelAttribute("globalAttr") String globalAttr){
    return globalAttr;
    }


    @GetMapping("methodThree")
    public String methodThree(ModelMap modelMap) {
    return (String) modelMap.get("globalAttr");
    }

    }

    这三种方式大同小异,其实都是都是从Model 中存储属性的 Map里取数据。

3.请求参数预处理

@ControllerAdvice 配合 @InitBinder 实现对请求参数的预处理

再次之前我们先来了解一下 @IniiBinder,先看一下源码,我会提取一些重要的注释进行浅析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* Annotation that identifies methods which initialize the
* {@link org.springframework.web.bind.WebDataBinder} which
* will be used for populating command and form object arguments
* of annotated handler methods.
* 粗略翻译:此注解用于标记那些 (初始化[用于组装命令和表单对象参数的]WebDataBinder)的方法。
* 原谅我的英语水平,翻译起来太拗口了,从句太多就用‘()、[]’分割一下便于阅读
*
* Init-binder methods must not have a return value; they are usually
* declared as {@code void}.
* 粗略翻译:初始化绑定的方法禁止有返回值,他们通常声明为 'void'
*
* <p>Typical arguments are {@link org.springframework.web.bind.WebDataBinder}
* in combination with {@link org.springframework.web.context.request.WebRequest}
* or {@link java.util.Locale}, allowing to register context-specific editors.
* 粗略翻译:典型的参数是`WebDataBinder`,结合`WebRequest`或`Locale`使用,允许注册特定于上下文的编辑
* 器。
*
* 总结如下:
* 1. @InitBinder 标识的方法的参数通常是 WebDataBinder。
* 2. @InitBinder 标识的方法,可以对 WebDataBinder 进行初始化。WebDataBinder 是 DataBinder 的一
* 个子类,用于完成由表单字段到 JavaBean 属性的绑定。
* 3. @InitBinder 标识的方法不能有返回值,必须声明为void。
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InitBinder {
/**
* The names of command/form attributes and/or request parameters
* that this init-binder method is supposed to apply to.
* <p>Default is to apply to all command/form attributes and all request parameters
* processed by the annotated handler class. Specifying model attribute names or
* request parameter names here restricts the init-binder method to those specific
* attributes/parameters, with different init-binder methods typically applying to
* different groups of attributes or parameters.
* 粗略翻译:此init-binder方法应该应用于的命令/表单属性和/或请求参数的名称。默认是应用于所有命 * 令/表单属性和所有由带注释的处理类处理的请求参数。这里指定模型属性名或请求参数名将init-binder * 方法限制为那些特定的属性/参数,不同的init-binder方法通常应用于不同的属性或参数组。
* 我至己都理解不太理解这说的是啥呀,我们还是看例子吧
*/
String[] value() default {};
}

我们来看看具体用途,其实这些用途在 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
    @ControllerAdvice
    public class MyGlobalHandler {
    @InitBinder
    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));
    }
    }

    这样之后呢,就可以实现全局的实现对 ControllerRequestMapping标识的方法中的所有 StringDate类型的参数都会被作相应的处理。

    Controller:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @RestController
    public class BinderTestController {

    @GetMapping("processParam")
    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;
    }
    }

    测试结果:

在这里插入图片描述
我们可以看出,strdate 这两个参数在进入 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
    24
    class Person {

    private String name;
    private Integer age;
    // omitted getters and setters.
    }

    class Book {

    private String name;
    private Double price;
    // omitted getters and setters.
    }

    @RestController
    public class BinderTestController {

    @PostMapping("bindParam")
    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
    @ControllerAdvice
    public class MyGlobalHandler {

    /*
    * @InitBinder("person") 对应找到@RequstMapping标识的方法参数中
    * 找参数名为person的参数。
    * 在进行参数绑定的时候,以‘p.’开头的都绑定到名为person的参数中。
    */
    @InitBinder("person")
    public void BindPerson(WebDataBinder dataBinder){
    dataBinder.setFieldDefaultPrefix("p.");
    }

    @InitBinder("book")
    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)

    上述情况就会出现绑定失败,有两种解决办法:

    1. 统一名称,要么全叫p,要么全叫person,只要相同就行。

    2. 方法参数加 @ModelAttribute,有点类似@RequestParam

      1
      2
      3
      @InitBinder(“p”)@InitBinder(“b”)

      public void test(@ModelAttribute(“p”) Person person, @ModelAttribute(“b”) Book book)