SpringMVC之拦截器
拦截器
1、简单介绍拦截器
利用spring的AOP思想建立起来的,与之类似的就是web中的filter。这里介绍一下目标方法,目标方法是我们在controller中书写的方法。
拦截器是在目标方法执行之前执行的。
在springmvc中,要想对springmvc进行定制化,那么首先应该实现接口WebMvcConfigurer,然后去重写其中的方法,最终将该配置类添加到容器中去。
1.1、拦截器接口
接口中有三个默认方法,子类可以有选择性的去实现接口中的方法:
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 {
}
}
2、举例说明
下面举一个例子来进行说明:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerIntecepter())
// 拦截哪些请求
.addPathPatterns("/**")
// 放过哪些请求
.excludePathPatterns("/login","/");
}
}
然后写一个类来继承HandlerInterceptor,并重写其中的方法:
@Slf4j
public class LoginHandlerIntecepter implements HandlerInterceptor {
/**
* 目标方法执行之前
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
String method = request.getMethod();
log.info("请求进来的requestURI是:{},对应的方式是:{}",requestURI,method);
if ("/user/login".equals(requestURI)){
log.info("符合规则,开始来进入.....................");
return true;
}
return false;
}
/**
* 目标方法执行之后
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("进入到postHandler方法中来...............");
}
/**
* 视图渲染之后,返回结果之前
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("进入到afterCompletion方法中来...............");
}
}
controller中的目标方法:
@RestController
@RequestMapping("user")
@Slf4j
public class UserLoginController {
@GetMapping("login")
public String hello(){
log.info("进入到目标方法中来............");
Map<String,String> map = new HashMap<>();
map.put("k1","k2");
map.put("k3","k4");
return map.toString();
}
}
最终控制台打印出来对应的结果:
{k1=k2, k3=k4}
那么来探索一下其中的原理。
首先进入到DispatcherServlet类中来,看到大致的代码结构:
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
..................
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
2.1、applyPreHandle方法
那么就看下这段在执行目标方法之前的判断:
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 首先获取得到所有的拦截器(包括我们自己写的拦截器)
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
// 按照顺序开始来进行遍历。questionOne
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
// 如果某个拦截器的preHandle方法执行成功,返回为true。在if方法中就是false
// 但是如果拦截器的preHandle方法返回为false,那么if判断中是false,会立马执行triggerAfterCompletion
// QuestionTwo
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
// 每个拦截器执行完preHandle是true之后,将会执行到这里来。然后记录一下是哪个拦截器执行成功
// 如果拦截器执行为false,那么将会立即返回。也就是说这里的下标只会记录到执行完成的拦截器
this.interceptorIndex = i;
}
}
return true;
}
QuestionOne:如果有多个拦截器,会按照加载的顺序来执行,如果有数组中的顺序是【A、B、C】,那么执行顺序就是A、B、C
QuestionTwo:只要有一个拦截器的preHandle方法没有执行成功,那么就立即执行这个拦截器的triggerAfterCompletion方法
2.2、triggerAfterCompletion方法
那么看一下这个triggerAfterCompletion方法:
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
throws Exception {
// 获取得到所有的拦截器
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
// 从这里可以看到和上面的区别。上面是正序,这里是倒序
// 这里会用索引来进行记录已经执行成功了的拦截器
// QuestionOne
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
// 开始执行其afterCompletion方法
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
QuestionOne:如果有多个拦截器,会按照加载的顺序来执行,如果有数组中的顺序是【A、B、C】,那么执行顺序就是A、B、C
这里默认的是所有的拦截器中的方法执行都是正常的。
那么下面来介绍一下不正常的情况:如果数组中的顺序是【A、B、C】,如果拦截器A和B执行preHandle都是成功的,而C的执行是不成功的,那么将会立即走到B的方法,而不会继续走到C的后续方法中去。
那么继续看DispatcherServlet中的方法:
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
..................
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 目标方法执行完成调用的方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
那么跟进一下这里的方法:
2.3、applyPostHandle方法
/**
* Apply postHandle methods of registered interceptors.
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
这段代码就不在来做细致分析了,这里很显然和afterCompletion方法是一样的。如果数组中的顺序是【A、B、C】,那么applyPostHandle的执行顺序就是C、B、A(因为在applyPostHandle)方法中返回值是void,只是简单的执行而已,不做其他的操作。
2.4、triggerAfterCompletion方法
最后再来看一段代码:
catch (Throwable err) {
// 异常执行
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
// 最终执行
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
这里也即是说明了,无论拦截器的triggerAfterCompletion执行是哪一步出现了问题,最终都将会执行这里的方法
3、总结
最终以一幅图来总结:
当所有的拦截器preHandler都执行成功的时候:
当有其中一个拦截器的preHandler方法为false的时候:
因为源码中写的是:
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
// 代码执行到了这里,那么只能说明上面的是为false
return;
}
// 而这个方法为false的原因就在于其中有一个preHandle没有返回为true
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
所以结束掉整个请求