当请求来到DispatcherServlet时,它会根据HandlerMapping的机制找到处理器,这样就会返回一个HandlerExecutionChain对象,这个对象包含处理器和拦截器。
这里的拦截器会对处理器进行拦截,这样通过拦截器就可以增强处理器的功能。
1.拦截器的设计
首先所有的拦截器都需要实现HandlerInterceptor接口。
HandlerInterceptor源码
package org.springframework.web.servlet;
/**** imports ****/
public interface HandlerInterceptor {
// 处理器执行前方法
default boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
return true;
}
// 处理器处理后方法
default void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
// 处理器完成后方法
default void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, @Nullable Exception ex) throws Exception {
}
}
拦截器执行过程:
•执行preHandle方法,该方法会返回一个布尔值。如果为false,则结束所有流程;如果为true,则执行下一步。
•执行处理器逻辑,它包含控制器的功能。
•执行postHandle方法。
•执行视图解析和视图渲染。
•执行afterCompletion方法。
因为这个接口是Java 8的接口,所以3个方法都被声明为default,并且提供了空实现。当我们需要自己定义方法的时候,只需要实现HandlerInterceptor,覆盖其对应的方法即可。
2.开发拦截器
自定义简单拦截器
/******** 拦截器1********/
package com.springboot.chapter10.interceptor;
/**** imports ****/
public class MulitiInterceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {
System.out.println("【" + this.getClass().getSimpleName()
+"】处理器前方法");
// 返回true,不会拦截后续的处理
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("【" + this.getClass().getSimpleName()
+"】处理器后方法");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("【" + this.getClass().getSimpleName()
+"】处理器完成方法");
}
}
这里的代码实现了HandlerInterceptor,然后按照自己的需要重写了3个具体的拦截器方法。
有了这个拦截器,Spring MVC并不会发现它,它还需要进行注册才能够拦截处理器,为此需要在配置文件中实现WebMvcConfigurer接口,最后覆盖其addInterceptors方法进行注册拦截器。
注册拦截器
package com.springboot.chapter10.main;
/**** imports ****/
// 声明配置类
@Configuration
// 定制扫描路径
@SpringBootApplication(scanBasePackages = "com.springboot.chapter10")
/****其他注解****/
public class Chapter10Application implements WebMvcConfigurer {
public static void main(String[] args) {
SpringApplication.run(Chpter10Application.class, args);
}
......
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器到Spring MVC机制,然后它会返回一个拦截器注册
InterceptorRegistration ir = registry.addInterceptor(new Interceptor1());
// 指定拦截匹配模式,限制拦截器拦截请求
ir.addPathPatterns("/interceptor/*");
}
}
这里通过实现WebMvcConfigurer接口,重写其中的addInterceptors方法,进而加入自定义拦截器——Interceptor1,然后指定其拦截的模式,所以它只会拦截与正则式“/interceptor/*”匹配的请求。
拦截控制器
package com.springboot.chapter10.controller;
/**** imports ****/
@Controller
@RequestMapping("/interceptor")
public class InterceptorController {
@GetMapping("/start")
public String start() {
System.out.println("执行处理器逻辑");
return "/welcome";
}
}
这里的控制器的start方法只是打开一个欢迎页面,十分简单,同时它定义了拦截“/interceptor/start”,而这个请求显然会被所创建的拦截器所拦截,所以只需要请求这个方法,请求就会被我们的拦截器拦截。
为了更好地测试这个拦截器,我们在欢迎页面也打印一下后台的信息,这个页面如代码清单10-38所示。
测试:http://localhost:8080/interceptor/start
后台打印的日志:
【MulitiInterceptor1】处理器前方法
执行处理器逻辑
【MulitiInterceptor1】处理器后方法
【MulitiInterceptor1】处理器完成方法
显然处理器被拦截器拦截了,这里需要注意的是拦截器方法的执行顺序。有兴趣的读者可以把拦截器的preHandle方法返回修改为false,或者让控制器抛出异常,然后重新测试,从而进一步掌握整个拦截器的流程。
3.多个拦截器的顺序
定义多个拦截器
拦截器2 ,拦截器3除类名外,内容同拦截器1。
注册多个拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器到Spring MVC机制中
InterceptorRegistration ir
= registry.addInterceptor(new MulitiInterceptor1());
// 指定拦截匹配模式
ir.addPathPatterns("/interceptor/*");
// 注册拦截器到Spring MVC机制中
InterceptorRegistration ir2 = registry.addInterceptor(new MulitiInterceptor2());
// 指定拦截匹配模式
ir2.addPathPatterns("/interceptor/*");
// 注册拦截器到Spring MVC机制中
InterceptorRegistration ir3 = registry.addInterceptor(new MulitiInterceptor3());
// 指定拦截匹配模式
ir3.addPathPatterns("/interceptor/*");
}
这样这些拦截器都会拦截与"/interceptor/*"匹配的请求。这里使用浏览器再次请求代码清单10-37中的start方法,于是可以看到如下的日志打印出来:
【MulitiInterceptor1】处理器前方法
【MulitiInterceptor2】处理器前方法
【MulitiInterceptor3】处理器前方法
执行处理器逻辑
【MulitiInterceptor3】处理器后方法
【MulitiInterceptor2】处理器后方法
【MulitiInterceptor1】处理器后方法
【MulitiInterceptor3】处理器完成方法
【MulitiInterceptor2】处理器完成方法
【MulitiInterceptor1】处理器完成方法
这个结果是责任链模式的规则,对于处理器前方法采用先注册先执行,而处理器后方法和完成方法则是先注册后执行的规则。
只是上述仅测试了处理器前(preHandle)方法返回为true的场景,在某些时候还可能返回为false,这个时候又如何呢?为此,可以将MulitiInterceptor2的preHandle方法修改返回为false,
然后再进行测试,其日志如下:
【MulitiInterceptor1】处理器前方法
【MulitiInterceptor2】处理器前方法
【MulitiInterceptor1】处理器完成方法
从上面的日志可以看出,处理器前(preHandle)方法会执行,但是一旦返回false,则后续的拦截器、处理器和所有拦截器的处理器后(postHandle)方法都不会被执行。
完成方法afterCompletion则不一样,它只会执行返回true的拦截器的完成方法,而且顺序是先注册后执行。
@ResponseBody转换为JSON的秘密
一直以来,当想把某个控制器的返回转变为JSON数据集时,只需要在方法上标注@ResponseBody注解即可,那么Spring MVC是如何做到的呢?
在进入控制器方法前,当遇到标注的@ResponseBody后,处理器就会记录这个方法的响应类型为JSON数据集。当执行完控制器返回后,处理器会启用结果解析器(ResultResolver)去解析这个结果,
它会去轮询注册给Spring MVC的HttpMessageConverter接口的实现类。
因为MappingJackson2HttpMessageConverter这个实现类已经被Spring MVC所注册,加上Spring MVC将控制器的结果类型标明为JSON,所以就匹配上了,于是通过它就在处理器内部把结果转换为了JSON。
当然有时候会轮询不到匹配的HttpMessageConverter,那么它就会交由Spring MVC后续流程去处理。
如果控制器返回结果被MappingJackson2HttpMessageConverter进行了转换,那么后续的模型和视图(ModelAndView)就返回null,这样视图解析器和视图渲染将不再被执行,其流程如下: