Appearance
七、MVC支持
Spring Boot 的 MVC 支持主要来介绍实际项目中最常用的几个注解,包括 @RestController
、 @RequestMapping
、@PathVariable
、@RequestParam
以及 @RequestBody
。主要介绍这几个注解常用的使用方式和特点。
1、扩展MVC
扩展MVC其实很简单,只需要以下步骤 :
- 创建一个MVC的配置类,并且标注
@Configuration
注解。 - 实现
WebMvcConfigurer
这个接口,并且实现需要的方法。
WebMvcConfigurer
这个接口中定义了MVC相关的各种组件,比如拦截器,视图解析器等等的定制方法,需要定制什么功能,只需要实现即可。
在Spring Boot之前的版本还可以继承一个抽象类 WebMvcConfigurerAdapter ,不过在 2.3.4.RELEASE
这个版本中被废弃了,如下:
java
@Deprecated
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {}
示例:
现在要添加一个拦截器,使其在Spring Boot中生效,此时就可以在MVC的配置类重写 addInterceptors() 方法,如下:
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private RepeatSubmitInterceptor repeatSubmitInterceptor;
/**
* 重写addInterceptors方法,注入自定义的拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(repeatSubmitInterceptor).excludePathPatterns("/error");
}
}
2、如何全面接管MVC
需要在配置类上添加一个 @EnableWebMvc
注解即可。还是添加拦截器,例子如下:
java
/**
* @EnableWebMvc:全面接管MVC,导致自动配置类失效
*/
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private RepeatSubmitInterceptor repeatSubmitInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加拦截器
registry.addInterceptor(repeatSubmitInterceptor).excludePathPatterns("/error");
}
}
一个注解就能全面接口MVC,是不是很爽,不过,不建议使用。
1、为什么@EnableWebMvc一个注解就能够全面接管 MVC
@EnableWebMvc
源码如下:
java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
其实重要的就是这个@Import({DelegatingWebMvcConfiguration.class})
注解了,Spring中的注解,快速导入一个配置类DelegatingWebMvcConfiguration
,源码如下:
java
@Configuration(
proxyBeanMethods = false
)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {}
明白了, @EnableWebMvc
这个注解实际上就是导入了一个 WebMvcConfigurationSupport
子类型的配置类而已。
而WEB模块的自动配置类有这么一行注解
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
,源码如下:
java
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {}
这个注解 @ConditionalOnMissingBean
什么意思呢?简单的说就是IOC容器中没有指定的 Bean 这个配置才会生效。
一切都已经揭晓了,@EnableWebMvc
导入了一个 WebMvcConfigurationSupport
类型的配置类,导致了自动配置类WebMvcAutoConfiguration
标注的@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
判断为false
了,从而自动配置类失效了。
3、一些注解
1、@RestController
@RestController
是 Spring Boot 新增的一个注解,我们看一下该注解都包含了哪些东西。
java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
String value() default "";
}
可以看出, @RestController
注解包含了原来的 @Controller
和 @ResponseBody
注解,使用过 Spring 的朋友对 @Controller
注解已经非常了解了,这里不再赘述, @ResponseBody
注解是将返回的数据结构转换为 Json 格式。所以 @RestController
可以看作是 @Controller
和 @ResponseBody
的结合体,相当于偷个懒,我们使用 @RestController
之后就不用再使用 @Controller
了。但是需要注意一个问题:如果是前后端分离,不用模板渲染的话,比如 Thymeleaf,这种情况下是可以直接使用@RestController
将数据以 json 格式传给前端,前端拿到之后解析;但如果不是前后端分离,需要使用模板来渲染的话,一般 Controller 中都会返回到具体的页面,那么此时就不能使用@RestController
了,比如:
java
public String getUser() {
return "user";
}
其实是需要返回到 user.html 页面的,如果使用 @RestController
的话,会将 user 作为字符串返回的,所以这时候我们需要使用 @Controller
注解。这在下一节 Spring Boot 集成 Thymeleaf 模板引擎中会再说明。
2、@RequestMapping
@RequestMapping
是一个用来处理请求地址映射的注解,它可以用于类上,也可以用于方法上。在类的级别上的注解会将一个特定请求或者请求模式映射到一个控制器之上,表示类中的所有响应请求的方法都是以该地址作为父路径;在方法的级别表示进一步指定到处理方法的映射关系。
该注解有6个属性,一般在项目中比较常用的有三个属性:value、method 和 produces。
- value 属性:指定请求的实际地址,value 可以省略不写
- method 属性:指定请求的类型,主要有 GET、PUT、POST、DELETE,默认为 GET
- produces属性:指定返回内容类型,如 produces = "application/json; charset=UTF-8"
@RequestMapping
注解比较简单,举个例子:
java
@RestController
@RequestMapping(value = "/test", produces = "application/json; charset=UTF-8")
public class TestController {
@RequestMapping(value = "/get", method = RequestMethod.GET)
public String testGet() {
return "success";
}
}
这个很简单,启动项目在浏览器中输入 localhost:8080/test/get
测试一下即可。
针对四种不同的请求方式,是有相应注解的,不用每次在 @RequestMapping
注解中加 method 属性来指定,上面的 GET 方式请求可以直接使用 @GetMapping("/get")
注解,效果一样。相应地,PUT 方式、POST 方式和 DELETE 方式对应的注解分别为 @PutMapping
、@PostMapping
和 DeleteMapping
。
3、@PathVariable
@PathVariable
注解主要是用来获取 url 参数,Spring Boot 支持 restfull 风格的 url,比如一个 GET 请求携带一个参数 id 过来,我们将 id 作为参数接收,可以使用 @PathVariable
注解。如下:
java
@GetMapping("/user/{id}")
public String testPathVariable(@PathVariable Integer id) {
System.out.println("获取到的id为:" + id);
return "success";
}
这里需要注意一个问题,如果想要 url 中占位符中的 id 值直接赋值到参数 id 中,需要保证 url 中的参数和方法接收参数一致,否则就无法接收。如果不一致的话,其实也可以解决,需要用 @PathVariable
中的 value 属性来指定对应关系。如下:
java
@RequestMapping("/user/{idd}")
public String testPathVariable(@PathVariable(value = "idd") Integer id) {
System.out.println("获取到的id为:" + id);
return "success";
}
对于访问的 url,占位符的位置可以在任何位置,不一定非要在最后,比如这样也行:/xxx/{id}/user
。另外,url 也支持多个占位符,方法参数使用同样数量的参数来接收,原理和一个参数是一样的,例如:
java
@GetMapping("/user/{idd}/{name}")
public String testPathVariable(@PathVariable(value = "idd") Integer id, @PathVariable String name) {
System.out.println("获取到的id为:" + id);
System.out.println("获取到的name为:" + name);
return "success";
}
运行项目,在浏览器中请求 localhost:8080/test/user/2/zhangsan
可以看到控制台输出如下信息:
获取到的id为:2
获取到的name为:zhangsan
所以支持多个参数的接收。同样地,如果 url 中的参数和方法中的参数名称不同的话,也需要使用 value 属性来绑定两个参数。
4、@RequestParam
@RequestParam
注解顾名思义,也是获取请求参数的,上面我们介绍了 @PathValiable
注解也是获取请求参数的,那么 @RequestParam
和 @PathVariable
有什么不同呢?主要区别在于: @PathValiable
是从 url 模板中获取参数值, 即这种风格的 url:http://localhost:8080/user/{id}
;而 @RequestParam
是从 request 里面获取参数值,即这种风格的 url:http://localhost:8080/user?id=1
。我们使用该 url 带上参数 id 来测试一下如下代码:
java
@GetMapping("/user")
public String testRequestParam(@RequestParam Integer id) {
System.out.println("获取到的id为:" + id);
return "success";
}
可以正常从控制台打印出 id 信息。同样地,url 上面的参数和方法的参数需要一致,如果不一致,也需要使用 value 属性来说明,比如 url 为:http://localhost:8080/user?idd=1
java
@RequestMapping("/user")
public String testRequestParam(@RequestParam(value = "idd", required = false) Integer id) {
System.out.println("获取到的id为:" + id);
return "success";
}
除了 value 属性外,还有个两个属性比较常用:
- required 属性:true 表示该参数必须要传,否则就会报 404 错误,false 表示可有可无。
- defaultValue 属性:默认值,表示如果请求中没有同名参数时的默认值。
从 url 中可以看出,@RequestParam
注解用于 GET 请求上时,接收拼接在 url 中的参数。除此之外,该注解还可以用于 POST 请求,接收前端表单提交的参数,假如前端通过表单提交 username 和 password 两个参数,那我们可以使用 @RequestParam
来接收,用法和上面一样。
java
@PostMapping("/form1")
public String testForm(@RequestParam String username, @RequestParam String password) {
System.out.println("获取到的username为:" + username);
System.out.println("获取到的password为:" + password);
return "success";
}
我们使用 postman 来模拟一下表单提交,测试一下接口:
那么问题来了,如果表单数据很多,我们不可能在后台方法中写上很多参数,每个参数还要 @RequestParam
注解。针对这种情况,我们需要封装一个实体类来接收这些参数,实体中的属性名和表单中的参数名一致即可。
java
@Data
public class User {
private String username;
private String password;
}
使用实体接收的话,我们不能在前面加 @RequestParam
注解了,直接使用即可。
java
@PostMapping("/form2")
public String testForm(User user) {
System.out.println("获取到的username为:" + user.getUsername());
System.out.println("获取到的password为:" + user.getPassword());
return "success";
}
使用 postman 再次测试一下表单提交,观察一下返回值和控制台打印出的日志即可。在实际项目中,一般都是封装一个实体类来接收表单数据,因为实际项目中表单数据一般都很多。
5、@RequestBody
@RequestBody
注解用于接收前端传来的实体,接收参数也是对应的实体,比如前端通过 json 提交传来两个参数 username 和 password,此时我们需要在后端封装一个实体来接收。在传递的参数比较多的情况下,使用 @RequestBody
接收会非常方便。例如:
java
@Data
public class User {
private Integer id;
private String username;
private String password;
}
java
@PostMapping("/user")
public String testRequestBody(@RequestBody User user) {
System.out.println("获取到的username为:" + user.getUsername());
System.out.println("获取到的password为:" + user.getPassword());
return "success";
}
我们使用 postman 工具来测试一下效果,打开 postman,然后输入请求地址和参数,参数我们用 json 来模拟,如下图所有,调用之后返回 success。
同时看一下后台控制台输出的日志:
获取到的username为:xue
获取到的password为:123456
可以看出,@RequestBody
注解用于 POST 请求上,接收 json 实体参数。它和上面我们介绍的表单提交有点类似,只不过参数的格式不同,一个是 json 实体,一个是表单提交。在实际项目中根据具体场景和需要使用对应的注解即可。