拦截器、过滤器、AOP区别

三者功能类似,但各有优势,从过滤器>拦截器>切面,拦截规则越来越细致,执行顺序依次是过滤器>拦截器>切面。一般情况下数据被过滤的时机越早对服务的性能影响越小,因此我们在编写相对比较公用的代码时,优先考虑过滤器,然后是拦截器,最后是aop。
比如权限校验,一般情况下,所有的请求都需要做登陆校验,此时就应该使用过滤器在最顶层做校验;日志记录,一般日志只会针对部分逻辑做日志记录,而且牵扯到业务逻辑完成前后的日志记录,因此使用过滤器不能细致地划分模块,此时应该考虑拦截器,然而拦截器也是依据URL做规则匹配,因此相对来说不够细致,因此我们会考虑到使用AOP实现,AOP可以针对代码的方法级别做拦截,很适合日志功能。

Spring AOP

Spring AOP的底层实现是JDK动态代理(JDK Dynamic Proxy)和CGLIB。他们都是在运行期进行织入的:

  • JDK Dynamic Proxy是利用反射原理生成一个实现代理接口的匿名代理类,自定义Handler要实现InvocationHandler
  • CGLIB是使用了ASM,修改字节码生成子类,自定义拦截器要实现MethodInterceptor

    切面、切点、连接点、通知

  • @AspectJ即为切面

  • @Pointcut为切点
  • Join Point为连接点
  • Advise为通知:通知有5种类型
    • Before:前置通知,目标方法被调用前
    • After:后置通知,目标方法被调用后,不关注返回值
    • Around:环绕通知,目标方法被调用前后
    • After-returning:后置通知,目标方法被调用后,可以获得返回值, 方法正常退出时执行
    • After-throwing:目标方法抛出异常

示例Demo如下:

  1. @Component
  2. // 切面
  3. @Aspect
  4. public class VingAspectJ {
  5. /**
  6. * 切点
  7. * 为什么切点要声明在一个方法上?目的是为了将注解写在上面而已
  8. * pointcut是连接点的集合(就是方法的集合)
  9. */
  10. @Pointcut("execution(* com.ving.dao.*.*(..))")
  11. public void pointCut(){
  12. }
  13. /**
  14. * 通知--->配置切点
  15. */
  16. @After("com.ving.config.VingAspectJ.pointCut()")
  17. public void after(){
  18. System.out.println("after");
  19. }
  20. @Before("com.ving.config.VingAspectJ.pointCut()")
  21. public void before(){
  22. System.out.println("before");
  23. }
  24. }
  • 代理时机:初始化的时候,已经将目标对象进行了代理,放到了Spring容器中。根据被代理对象是否是实现了接口的类,如果是,走JDK动态代理,否则走CGLIB。
  • 应用场景:日志记录、权限验证、事务管理

    织入(weaving):将代理逻辑加入到目标对象上的过程叫织入

拦截器与过滤器【1】

image.png
拦截器(接口org.springframework.web.servlet.HandlerInterceptor)是Spring组件,由Spring容器管理,不依赖Tomcat等容器,可以单独使用,不仅可以使用在web程序,也可以用于Application等程序中;javax.servlet.FilterServlet规范中定义,接口功能和web容器绑定,所以是依赖于Tomcat等web容器的,所以只能在web中使用。
均体现了AOP的编程思想,都可以实现日志记录、登陆鉴权等功能,但两者实现原理不同,过滤器是基于函数回调的,拦截器时基于Java反射实现的。
拦截器的匹配颗粒度更细致,可以到url,但相比AOP可以到方法,最细致的还是AOP
优先级:过滤器用@Order控制,拦截器用#order()控制,值越小越先执行

拦截器

image.png

HandlerInteceptor

Spring拦截器基于接口org.springframework.web.servlet.HandlerInterceptor实现,内部方法定义如下:

  1. public interface HandlerInterceptor {
  2. /**
  3. * 调用时机:HandlerMapping确定合适的处理程序对象之后,HandlerAdapter调用处理方法之前
  4. **/
  5. default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
  6. throws Exception {
  7. return true;
  8. }
  9. /**
  10. * 调用时机:HandlerAdapter调用处理程序之后,DispatcherServlet呈现视图(渲染视图)之前
  11. **/
  12. default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
  13. @Nullable ModelAndView modelAndView) throws Exception {
  14. }
  15. /**
  16. * 调用时机:请求处理完成之后的回调(渲染视图之后),即结果输出到客户端之后。
  17. * 适合做一些清理工作,该方法参数包含Exception,说明无论是否抛出异常都会执行。
  18. * 不会执行的情况:对应的preHandler没有返回true或者异常,该方法不会执行
  19. **/
  20. default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
  21. @Nullable Exception ex) throws Exception {
  22. }
  23. }

将自定义好的拦截器处理类进行注册即可,代码如下:

  1. @Configuration
  2. public class MyMvcConfig implements WebMvcConfigurer {
  3. @Override
  4. public void addInterceptors(InterceptorRegistry registry) {
  5. registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
  6. registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
  7. }
  8. }

过滤器

image.png

Filter

过滤器的配置比较简单,直接实现Filter接口即可,也可以通过@WebFilter注解实现对特定URL拦截,看到Filter接口中定义了三个方法

  1. public interface Filter {
  2. /**
  3. * Servlet容器初始化过滤器时被调用,该方法必须执行成功,否则过滤器不起作用
  4. **/
  5. public void init(FilterConfig filterConfig) throws ServletException;
  6. /**
  7. * 容器中每一次请求都会调用该方法
  8. * @param chain: FilterChain用来调用下一个过滤器Filter。filterChain.doFilter(servletRequest, servletResponse);
  9. **/
  10. public void doFilter(ServletRequest request, ServletResponse response,
  11. FilterChain chain)
  12. throws IOException, ServletException;
  13. /**
  14. * 由web容器调用,当web容器销毁过滤器实例时调用该方法。一般是在doFilter()中的所有线程都退出或超时调用,调用后Filter不再起作用
  15. **/
  16. public void destroy();
  17. }

ApplicationFilterChain

过滤器的AOP实现是基于回调的,具体实现通过ApplicationFilterChain

  1. public final class ApplicationFilterChain implements FilterChain {
  2. @Override
  3. public void doFilter(ServletRequest request, ServletResponse response) {
  4. ...//省略
  5. internalDoFilter(request,response);
  6. }
  7. private void internalDoFilter(ServletRequest request, ServletResponse response){
  8. if (pos < n) {
  9. //获取第pos个filter
  10. ApplicationFilterConfig filterConfig = filters[pos++];
  11. Filter filter = filterConfig.getFilter();
  12. ...
  13. filter.doFilter(request, response, this);
  14. }
  15. }
  16. }

每个xxxFilter 会先执行自身的 doFilter() 过滤逻辑,最后在执行结束前会执行filterChain.doFilter(servletRequest, servletResponse),也就是回调ApplicationFilterChain的doFilter() 方法,以此循环执行实现函数回调。

  1. @Override
  2. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  3. // 业务逻辑过滤
  4. ...
  5. // 调用下一个,如果不存在则执行业务代码service()
  6. filterChain.doFilter(servletRequest, servletResponse);
  7. }

过滤器+监听器+拦截器+AOP(切面)

拦截顺序:ServletContextListener > Filter > Interception > AOP > 具体执行的方法 > AOP > @ControllerAdvice > Interception > Filter > ServletContextListener

参考

【1】过滤器 和 拦截器 的6个区别:https://mp.weixin.qq.com/s?src=11&timestamp=1633923834&ver=3367&signature=ObMvqTqXciDv6nyxxkeZiIMqoX-MGM2YspW9NlZQacyqaRz8sX2RjGaJe0ZgHDeIJUyKiyGEfaUwRXQGkcBQ5cqsoc4B9xT8d5aPNUVQioovTgGudHTQ-Vhyk1JJfG&new=1 后端技术漫谈