SpringBoot整合SpringMVC分析

在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";
    }

}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容