话说拦截器,功力不够,体会不到哪里用到了反射或者动态代理,用反射也就是 Spring 创建拦截器对象的时候,不能说这就是基于反射了吧?那动态代理在拦截器中连个影子都找不到,哪里有?
当你不知道答案的时候,就看看源码,源码不会忽悠人
。
用户请求到 DispatherServlet 中,DispatherServlet 调用 HandlerMapping 查找 Handler,HandlerMapping 返回一个拦截器链(HandlerExecutionChain),springmvc 中的拦截器是通过 HandlerMapping 发起的。
在企业开发,使用拦截器实现用户认证(用户登陆后进行身份校验拦截),用户权限拦截和方法性能监控等。
拦截器在 SpringMVC 中是最好理解的,在创建 RequestMappingHandlerMapping 对象时候进行拦截器收集,RequestMappingHandlerMapping 对象就负责处理我们写的 Controller,Controller收集过程在他重写的 afterPropertiesSet 方法下,这个方法是 Spring 进行对象 init 阶段调用的。
创建 RequestMappingHandlerMapping 在 WebMvcConfigurationSupport 中,他是由 @Bean 方式创建的,详细可以看源码,在这个过程中,会调用子类的 addInterceptors 进行拦截器对象收集,这里的子类通常都是我们编写的了,到这里就熟悉了吧。
@Override
protected void addInterceptors(InterceptorRegistry registry) {
super.addInterceptors(registry);
registry.addInterceptor(new HandlerInterceptor(){
});
}
而 SpringMVC 调用拦截器时机有三个地方,这三个时机会分别调用下面三个方法。
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 {
}
HandlerInterceptor接口的方法说明
HandlerInterceptor接口中定义了三个方法,我们就是通过这三个方法来对用户的请求进行拦截处理的。
preHandle()
:这个方法在业务处理器处理请求之前被调用,SpringMVC 中的 Interceptor 是链式调用的,在一个应用中或者说是在一个请求中可以同时存在多个 Interceptor。每个 Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是 Interceptor 中的 preHandle 方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是布尔值 Boolean 类型的,当它返回为 false 时,表示请求结束,后续的 Interceptor 和 Controller 都不会再执行;当返回值为 true 时就会继续调用下一个 Interceptor 的 preHandle 方法,如果已经是最后一个 Interceptor 的时候就会是调用当前请求的 Controller 方法。这个阶段是在 DispatcherServlet 中调用的,DispatcherServlet 是一个 Servlet,一个请求最先进入的地方,当然前面可能还有过滤器,Dispatcher 意味分发,也就是 DispatcherServlet 负责把请求分发给对应的 Controller,并且也会先调用拦截器。
postHandle()
:这个方法在当前请求进行处理之后,也就是 Controller 方法调用之后执行,但是它会在 DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对 Controller 处理之后的 ModelAndView 对象进行操作。postHandle 方法被调用的方向跟 preHandle 是相反的,也就是说先声明的 Interceptor 的 postHandle 方法反而会后执行。
afterCompletion()
:该方法也是需要当前对应的Interceptor 的preHandle 方法的返回值为true 时才会执行。顾名思义,该方法将在整个请求结束之后,也就是在DispatcherServlet渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作的。afterCompletion方法被调用的方向和perHandle也是相反的,先声明的Interceptor的afterCompletion方法后执行。
注意看 doDispatch 方法,可以看到下面这句代码,apply(运用)、Pre(前)、Handle(处理),这里就是调用拦截器中 preHandle 方法。
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
而方法也简单,就是遍历所有拦截器,只要有一个拦截器返回 false,则请求终止。
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
下一个时机在同样在 DispatcherServlet 中的 doDispatch 方法下,调用所有拦截器的 postHandle 方法,这个时候 Controller 已经调用完毕了,如果继续用 response 对象做输出,会被追加到原本返回的信息后面,如果你使用的模板引擎,这时候也可以更改 Model 中的数据或者视图名称。
mappedHandler.applyPostHandle(processedRequest, response, mv);
最后一个时机在 processDispatchResult 方法下,调用 afterCompletion,这时候不能在更改 Model 中的数据或者视图名称,但是也可以做输出,他用来做一些资源清理,这个调用顺序和拦截顺序相反,就是先被调用 preHandle 的拦截器会最后才被调用 afterCompletion。
请问这个过程哪有什么动态代理?
多读书啊,小伙子们,要不然全是“bug”。
SpringMVC 拦截器的实现方式
第一种方式是要定义的 Interceptor 类要实现了 Spring 的 HandlerInterceptor 接口。
第二种方式是继承实现了 HandlerInterceptor 接口的类,比如 Spring 已经提供的实现了 HandlerInterceptor 接口的抽象类 HandlerInterceptorAdapter。
记住三句话
-
监听应用(应用于整个程序应用)
-
过滤请求/资源(应用于容器)
-
拦截方法(相较于过滤器更细粒化的拦截,应用于方法)
作用范围从小到大,加载顺序也是。拦截器是实现 AOP 的一种策略
。
对比与联系
-
拦截器是基于 java 反射(Spring)机制来实现的,而过滤器是基于函数回调来实现的。
-
拦截器不依赖 servlet 容器,过滤器依赖于 servlet 容器。
-
拦截器只对 Action 起作用,过滤器可以对所有请求起作用。
-
拦截器可以访问 Action 上下文和值栈中的对象,过滤器不能。
-
在 Action 的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时调用一次。