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都执行成功的时候:

SpringMVC之拦截器

当有其中一个拦截器的preHandler方法为false的时候:

SpringMVC之拦截器

因为源码中写的是:

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

所以结束掉整个请求

上一篇:Qml 对象的属性


下一篇:使用golang实现一个简单地httpServer