在SpringBoot开发中,我们可以快速的配置实现Spring MVC开发,但是我们了解整个运行流程吗?Spring Boot为我们配置了什么?
Spring MVC执行流程分析
先看看Spring MVC启动时默认创建了哪些对象
DispatcherServlet.properties
路径:spring-webmvc-5.2.1.RELEASE.jar包下org.springframework.web.servlet.DispatcherServlet.properties
这个文件中定义的对象会在Spring MVC开始时就初始化,并且会存放到SpringIoC容器中。
# 看下DispatcherServlet.properties都定义了哪些对象
# 国际化解析器
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
# 主题解析器
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
# HandlerMapping实例
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping
# 处理器适配器
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
org.springframework.web.servlet.function.support.HandlerFunctionAdapter
# 处理器异常解析器
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
# 策略视图名称转换器,当你没有返回视图逻辑名称时,通过它可以生成默认的视图名称
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
# 视图解析器
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
# FlashMap管理器,不常用
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
通过代码分析运行流程
//表明这是一个控制器
@Controller
//@RequestMapping表示请求路径和控制器的映射关系
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/details")
public ModelAndView details() {
//1.获取用户数据
User user = userService.findUserById(1L);
//2.模型和视图
ModelAndView mv = new ModelAndView();
mv.setViewName("/user/details");
mv.addObject("user", user);
return mv;
}
}
1.启动Spring MVC的时候,UserController控制器就会被扫描到HanderMapping中存储
2.访问/user/details,会被DispatcherServlet拦截,然后通过HanderMapping找到对应的控制器并进行响应
3.HanderMapping返回的并不是一个UserController对象,而是一个HandlerExecutionChain对象,可以看下源码
public class HandlerExecutionChain {
//日志
private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);
//处理器
private final Object handler;
//拦截器数组
@Nullable
private HandlerInterceptor[] interceptors;
//拦截器列表
@Nullable
private List<HandlerInterceptor> interceptorList;
//拦截器当前下标
private int interceptorIndex;
其他的省略.....
}
4.在HandlerExecutionChain对象中包含一个handler处理器,这个处理器是对控制器的包装,处理器在请求的时候读取http和上下文的相关参数,然后传递给控制器方法,控制器执行完成后,处理器又可以通过配置信息对控制器的返回结果进行处理。
5.在HandlerExecutionChain对象中还定义了拦截器(interceptor),这些拦截器可以通过拦截处理器进一步增强处理器的功能。
6.客户端有http请求、websocket请求等,所以还需要一个适配器去运行HandlerExecutionChain对象包含的处理器,这个就是处理适配器(HandlerAdapter的实现类),我们最常用的实现类就是HttpRequestHandlerAdapter。
7.处理器调用控制器,并返回模型和视图(ModeAndView)对象,这个时候就会走到ViewResolver视图解析器,去解析视图逻辑名称。在DispatcherServlet.properties中我们可以看到,InternalResourceViewResolver视图解析器已经自动初始化了,不过我们可以在配置文件application.properties中进行配置,这样SpringMVC就会初始化我们自定义的InternalResourceViewResolver视图解析器了。
# /templates/jsp/user/details.jsp
# 前缀
spring.mvc.view.prefix=/templates/jsp/
# 后缀
spring.mvc.view.suffix=.jsp
8.到这里,视图解析器就定位到视图了,视图的作用就是将数据模型渲染给用户看。
流程图
SpringBoot启动SpringMVC
上面我们讲了Spring MVC的执行流程,那SpringMVC是怎么启动的呢?
在Servlet3.0规范中,web.xml不再是一个必须的配置文件,为了适应这个规范,Spring MVC从3.1版本开始>也开始对这个规范进行了支持,也就是我们不再需要在任何xml中配置Spring MVC运行环境。
为了支持这种规范,Spring提供了WebMvcConfigurer接口,这是一个基于Java8的接口,大部分方法都是>default类型的,但是它们都是空实现,开发者只需要实现这个接口,重写需要自定义的方法即可。
在Springboot中,自定义类是通过配置WebMvcAutoConfiguration定义的,它有一个静态的内部类
WebMvcAutoConfigurationAdapter,通过它Spring Boot就自动配置了Spring MVC穿的初始化。在WebMvcAutoConfigurationAdapter类中,它会读取Spring配置Spring MVC的属性来初始化对应组件。
SpringMVC使用
路径映射
@Controller
@RequestMapping("/user")
public class UserController {
/**
* @RequestParam默认参数不能为空,将其required属性设置为false即可为空
*/
@RequestMapping(path = "/details", method = RequestMethod.GET)
public void details() {
}
}
控制器首先要指定请求url,这里由@RequestMapping来完成,该注解可以标注到类或方法上,配置请求url后,Spring MVC扫描机制就可以将其扫描,并装载到HandlerMapping。
Spring4.3之后,为了简化method配置项,新增了@GetMapping、@PostMapping、@PatchMapping、@PutMapping、@DeleteMapping。
请求参数
无注解获取参数
/**
* 要求请求参数名称和http请求参数名称必须一致
* 参数值允许为空
*/
@RequestMapping("/details")
public void details(Integer intVal, Long longVal, String str) {
}
使用@RequestParam获取请求参数
@RequestParam将Http请求的参数和方法的参数进行绑定
/**
* @RequestParam默认参数不能为空,将其required属性设置为false即可为空
*/
@RequestMapping("/details")
public void details(@RequestParam(value = "name", required = false) String name) {
}
数组参数
Spring MVC内部支持逗号分隔的数组参数
//http://localhost:8080/user/details?intArr=1,2,3&longArr=4,5,6&strArr=aaa,bbb,ccc
@RequestMapping("/details")
public void details(int[] intArr, Long[] longArr, String[] strArr) {
}
JSON格式参数
接收到JSON数据后,@RequestBody会将JSON数据转换为User对象,前提是JSON数据和User对象的属性名称是一致的。
@RequestMapping("/details")
public void details(@RequestBody User user) {
}
通过URL传递参数
可以通过@PathVariable通过名称来获取URL参数
//http://localhost:8080/user/1
@GetMapping("/{id}")
public void details(@PathVariable("id") Long id) {
}
获取请求头参数
通过@RequestHeader接收请求参数
@GetMapping("/details")
public User details(@RequestHeader("id") Long id) {
return new User();
}
获取格式化参数
@DateTimeFormat:对日期进行格式化
@NumberFormat:对数字进行格式化
@GetMapping("/details")
public void details(
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date date,
@NumberFormat(pattern = "#,###.##") Double number) {
}
返回结果
@ResponseBody注解可以把返回结果转为json格式的数据
原理:当被@ResponseBody标注后,在控制器返回后,处理器会启动结果解析器(ResultResolver)去解析这个结果,它会去轮询注册给SpringMVC的HttpMessageConverter接口的实现类,因为MappingJackson2HttpMessageConverter这个实现类已经被Spring MVC所注册,通过它在处理器内部就把结果转换成了JSON。
@GetMapping("/details")
@ResponseBody
public User details() {
return new User();
}
数据校验
JSR-303规范验证参数的合法性
public class User {
@NotNull(message = "id不能为空")
private Long id;
@Future(message = "需要是一个未来的日子")
private Date date1;
@Past(message = "需要是一个过去的日子")
private Date date2;
@DecimalMin(value = "0.1") //最小值0.1
@DecimalMax(value = "10000.00") //最大值10000
private Double money;
@Min(value = 1, message = "最小值为1")
@Max(value = 100, message = "最大值为100")
private Integer num;
@Range(min = 1, max = 100, message = "范围为1到100")
private Long range;
@Email(message = "邮箱格式错误")
private String email;
@Size(min = 10, max = 20, message = "备注内容长度要求大于10小于20")
private String desc;
}
@Valid表示启动验证机制,开启后,会自动将错误结果放入到Errors对象中
@GetMapping("/details")
public void details(@Valid @RequestBody User user, Errors errors) {
List<ObjectError> allErrors = errors.getAllErrors();
}
拦截器
上面我们讲到,当请求到达DispatcherServlet时,会根据HandlerMapping返回一个HandlerExecutionChain对象,这个对象包含处理器和拦截器,这里的拦截器会对处理器进行拦截。
自定义拦截器
需要实现HandlerInterceptor接口
执行流程:
1.执行preHandle方法,返回false,结束所有流程,返回true,执行下一步。
2.执行处理器逻辑,包含控制器的功能
3.执行postHandle方法
4.执行视图解析和渲染
5.执行afterCompletion方法
public class MyHandlerInterceptor implements HandlerInterceptor {
/**
* 处理器执行前执行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
/**
* 处理器执行后执行
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/**
* 处理器完成后执行
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
注册拦截器
什么我们定义了拦截器,但是Spring MVC并不会知道它的存在,它需要进行注册才能够进行拦截,这里我们实现WebMvcConfigurer接口,覆盖其addInterceptors方法进行注册拦截器。
@SpringBootApplication
public class SpringmvcDemoApplication implements WebMvcConfigurer {
public static void main(String[] args) {
SpringApplication.run(SpringmvcDemoApplication.class, args);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//1.添加自定义拦截器
InterceptorRegistration ir = registry.addInterceptor(new MyHandlerInterceptor());
//拦截模式, 会拦截与正则式"/interceptor/*"匹配的请求
ir.addPathPatterns("/interceptor/*");
}
}
异常处理
在SpringAOP中,可以通过通知来增强Bean的功能,同样,SpringMVC也可以给控制器增加通知,并给我们提供了@ControllerAdvice 、@InitBinder 、@ExceptionHandler 和@ModelAttribute 4个注解。
@ControllerAdvice :定义一个控制器通知类
@InitBinder:定义控制器参数绑定规则
@ExceptionHandler:定义控制器异常后的操作
@ModelAttribute:在控制器方法执行前,对数据模型进行操作
@ControllerAdvice(basePackages = "com._54programer.controller")
public class MyControllerAdvice {
/**
* 异常处理, 发生异常时, 返回统一视图
*/
@ExceptionHandler(value = Exception.class)
public String exception(Model model, Exception e){
//给数据模型增加异常消息
model.addAttribute("系统异常", e.getMessage());
//返回异常视图
return "exception";
}
}