文章内容
一、四种拦截方法的执行顺序
1、测试例子
先上一个例子,看四种拦截方法并驾齐驱使用时,谁先谁后:
01 02 03 04 05 06 07 08 09 10 11 12 | /** * ============Filter过滤器============ */ @Slf4j @WebFilter (urlPatterns = "/*" ) public class DemoFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { log.info( "Filter 进入" ); filterChain.doFilter(request, response); log.info( "Filter 退出" ); } } |
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 | /** * ============Interceptor过滤器============ */ public class DemoInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info( "Interceptor preHandle 进入" ); return true ; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { log.info( "Interceptor postHandle 进入" ); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { log.info( "Interceptor afterCompletion 进入" ); } } |
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | /** * ============ControllerAdvice============ */ @ControllerAdvice public class DemoControllerAdvice { @InitBinder public void init(WebDataBinder binder) { log.info( "ControllerAdvice init 进入" ); binder.setFieldDefaultPrefix( "user." ); } @ExceptionHandler (Exception. class ) public String handleException(Exception e) { log.info( "ControllerAdvice handleException 进入" ); return "error" ; } } |
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 | /** * ============AOP============ */ @Aspect @Component public class DemoAspect { @Pointcut ( "(@target(org.springframework.web.bind.annotation.RestController)) " + "&& within(cn.demo.api..*) && execution(public * *(..))" ) public void pointcut() { } @Around (value = "pointcut()" ) public Object around(ProceedingJoinPoint pjp) throws Throwable { log.info( "Aop 进入" ); Object proceed = pjp.proceed(); log.info( "Aop 退出" ); return proceed; } } |
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 | /** * ============控制器入口============ */ @RestController @RequestMapping ( "/demo" ) public class DemoController { @RequestMapping ( "/test" ) public Map<String, String> test( @ModelAttribute ( "user" )User user) { log.info( "业务:user.name: {}" , user.getName()); Map<String, String> result = new HashMap<>( 2 ); result.put( "code" , "200" ); int i = 1 ; i = i / 0 ; return result; } } |
定义好Demo示例,接着来调用测试一下:http://localhost:8080/demo/test?user.name=Nick:
1 2 3 4 5 6 7 8 9 | 结果: Filter 进入 Interceptor preHandle 进入 ControllerAdvice init 进入 Aop 进入 业务:user.name: Nick ControllerAdvice handleException 进入 Interceptor afterCompletion 进入 Filter 退出 |
2、执行顺序
好了,我们似乎已经看到,四种拦截方法的执行顺序是这样子:

二、四种拦截方法的应用场景
那么针对这四种拦截方法,它们涉及的应用场景、实现技术、作用力度都各不相同,为了有一个比较清晰的对比,简单粗暴,直接整理如下:

三、四种拦截方法的原理
知其然并知其所以然,来研究一下它们的原理。
1、过滤器
虽然一个过滤器在一次请求中只能调用一次,但是根据具体业务需求,可以生成多个不同类型的Filter并依次执行,这就是过滤器的链式调用,可以通过指定Order排序,值越小越靠前(默认根据Filter的名称进行自然排序),新建了4个Filter,Order依次为0-3,演示一波,符合预期:

接下来我们关注整个链式调用的核心: FilterChain接口,内部定义了doFilter接口方法,tomcat容器提供了ApplicationFilterChain作为FilterChain的具体实现:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 | public final class ApplicationFilterChain implements FilterChain { // private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[ 0 ]; private Servlet servlet; public void doFilter(ServletRequest request, ServletResponse response) { internalDoFilter(request, response); } private void internalDoFilter(ServletRequest request, ServletResponse response) { if ( this .pos < this .n) { ApplicationFilterConfig filterConfig = this .filters[ this .pos++]; Filter filter = filterConfig.getFilter(); filter.doFilter(request, response, this ); } else { this .servlet.service(request, response); } } } |
内部定义了ApplicationFilterConfig[] filters 过滤器配置列表,每一个ApplicationFilterConfig内部持有一个Filter实例,另一个比较重要的是Servlet,实例化后对应原生HttpServlet或SpringMVC的DispatcherServlet,当拦截器链路执行完成后,会调用Servlet中service方法做后续的Url路由映射、业务处理以及视图响应等流程了(这个后面研究SpringMVC的请求流程来详细再分析), 好了,我们通过Debug可以看到,filters中除了服务默认的一些请求filter,我们自己定义的4个filter也以定义好的顺序排入其中了:

过滤器整体执行流程如下:

2、拦截器
拦截器调用流程比较复杂,这里根据源码梳理了核心请求流程和简要说明,源码位于:org.springframework.web.servlet.DispatcherServlet#doDispatch方法

注意:拦截器是基于反射、动态代理来实现的,通过对源码分析,反射倒是有用到,主要从Spring的IOC容器中获取了拦截器对象,并放在AbstractHandlerMapping的adaptedInterceptors全局对象中,在上图的第二步就匹配满足的拦截器作为当前请求的拦截器列表,没有动态代理就没有影子!。
3、ControllerAdvice
其实,ControllerAdvice和拦截器实现有异曲同工之处,要是说用什么技术手段,那应该也只能说是反射吧,在也主要在上一步的doDispatch方法中,它主要是在分布第4步t通过反射对InitBinder的参数的设置和第6步进行统一的异常捕获,重点看看第6步:在processDispatchResult处理结果方法内部,调用processHandlerException方法进行异常相关的处理逻辑,我们可以看到它的主要工作就是遍历handlerExceptionResolvers来进行异常对应的处理,我们自定义的全局异常ExceptionHandlerExceptionResolver实例控制着所有的Controller类,debug源码:

那什么时候进行设置的以及如何设置的呢?点开ExceptionHandlerExceptionResolver来看看,原来重点是实现了InitializingBean的afterPropertiesSet方法,在容器启动时候检测带ControllerAdvice注解的类和类中带ExceptionHandler注解的方法,并加入到我们刚才看到的exceptionHandlerCache中:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver implements ApplicationContextAware, InitializingBean { private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap( 64 ); // 重点实现了afterPropertiesSet public void afterPropertiesSet() { this .initExceptionHandlerAdviceCache(); } private void initExceptionHandlerAdviceCache() { if ( this .getApplicationContext() != null ) { // 寻找所有带有ControllerAdvice注解的类 List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans( this .getApplicationContext()); Iterator var2 = adviceBeans.iterator(); while (var2.hasNext()) { ControllerAdviceBean adviceBean = (ControllerAdviceBean)var2.next(); Class<?> beanType = adviceBean.getBeanType(); // 寻找该类下面的方法是否有ExceptionHandler注解,如果有,则收集到mappedMethods列表中 ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType); // 如果不为空,则加入到全局异常拦截缓存中 if (resolver.hasExceptionMappings()) { this .exceptionHandlerAdviceCache.put(adviceBean, resolver); } } } } } |
4、AOP
前面说到,AOP是有两种实现方式,相信大家都耳熟能详:JDK动态代理和CGLib,先来一个JDK动态代理的例子:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | //1. 动态代理是基于接口访问的,先定义用户服务接口 public interface UserService { User getUser(String name); } // 2. 具体用户服务实现类,也即被代理对象 public class UserServiceImpl implements UserService{ @Override public User getUser(String name) { User user = new User(); user.setName( "Nick" ); System.out.println( "用户名:" + user.getName()); return user; } } // 3. 代理工具,用于服务增强 public class JDKProxyr implements InvocationHandler { private Object target; public JDKProxy(Object target) { this .target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println( "before-------JDK Proxy" ); Object invoke = method.invoke(target, args); //通过反射执行,目标类的方法 System.out.println( "after-------JDK Proxy" ); return invoke; } } // 4. 测试方法 public class JDKProxyTest { public static void main(String[] args) { UserServiceImpl userService = new UserServiceImpl(); JDKProxy handler = new JDKProxy(userService); UserService proxyInstance = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass() .getInterfaces(), handler); User user = proxyInstance.getUser( "Nick" ); } } |
测试结果:
1 2 3 | before-------JDK Proxy 用户名:Nick after-------JDK Proxy |
CGLib的代理方式是为我们需要被代理的具体类生成一个子类,即将需被代理的方法进行Override重写:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // CGLib代理工具,用于生成业务增强 public class CGLibProxy implements MethodInterceptor { public Object intercept(Object arg0, Method method, Object[] objects, MethodProxy proxy) throws Throwable { System.out.println( "before-------CGLib Proxy" ); Object result = proxy.invokeSuper(arg0, objects); System.out.println( "after-------CGLib Proxy" ); return result; } } // 测试方法 public class CGLibProxyTest { public static void main(String[] args) { CGLibProxy proxy = new CGLibProxy(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserServiceImpl. class ); //回调方法的参数为代理类对象CglibProxy,最后增强目标类调用的是代理类对象CglibProxy中的intercept方法 enhancer.setCallback(proxy); UserServiceImpl userService = (UserServiceImpl) enhancer.create(); userService.getUser( "Nick" ); // 打印增强效果 System.out.println( "打印userService的增强结果:" ); ReflectUtils.printMethods(userService.getClass()); } } |
打印结果,我们可以看到被代理的方法已经执行了增强逻辑:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | before-------CGLib Proxy 用户名:Nick after-------CGLib Proxy 打印userService的增强结果: public final getUser(java.lang.String); public setCallback(int,net.sf.cglib.proxy.Callback); public static CGLIB$findMethodProxy(net.sf.cglib.core.Signature); public static CGLIB$SET_THREAD_CALLBACKS([Lnet.sf.cglib.proxy.Callback;); public static CGLIB$SET_STATIC_CALLBACKS([Lnet.sf.cglib.proxy.Callback;); public getCallback(int); public getCallbacks(); public setCallbacks([Lnet.sf.cglib.proxy.Callback;); private static final CGLIB$BIND_CALLBACKS(java.lang.Object); static CGLIB$STATICHOOK1(); final CGLIB$hashCode$3(); final CGLIB$clone$4(); final CGLIB$toString$2(); final CGLIB$equals$1(java.lang.Object); final CGLIB$getUser$0(java.lang.String); public final equals(java.lang.Object); public final toString(); public final hashCode(); protected final clone(); public newInstance([Lnet.sf.cglib.proxy.Callback;); public newInstance([Ljava.lang.Class;,[Ljava.lang.Object;,[Lnet.sf.cglib.proxy.Callback;); public newInstance(net.sf.cglib.proxy.Callback); |
好了,我们看完了基于Jdk动态代理和CGLib代理的两种Demo,那我们继续看看Spring是怎么玩的吧。 在Spring的启动过程中,有很多的扩展点,比较让我们熟知的BeanPostPorcessor,主要在Bean进行initializeBean初始化后,调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization,内部对所有的BeanPostPorcessor对象进行遍历,调用postProcessAfterInitialization进行处理,而Aop就是基于该扩展点实现的:

沿着这条链路Debug调试,最终可以定位到具体决定调用JDK动态代理还是使用CGLib,我这代理的是DemoController,那生成的就是CGLig代理啦:
