Filter、interceptor和Aspect的使用及区别

在工作中如何选择拦截机制去处理我们的业务请求,过滤器,拦截器,还是切面的选择一直比较模糊,今天花时间整理一下。

Filter(过滤器)

拦截所有客户端对Web资源(静态资源、动态资源)的访问。Filter是服务端的一个组件,是基于Servlet实现的从客户端访问服务端Web资源的一种拦截机制,对请求request和响应response都进行过滤,可以用来完成设置字符编码,鉴权操作等。

严格意义上讲Filter只是适用于web中,依赖于Servlet容器,利用Java的回调机制进行实现。使用时实现Filter接口,在Web.xml里配置对应的class还有mapping-url,SpringBoot工程可以通FilterRegisteration配置设置要过滤的URL, *两种配置方式过滤器都是有序的,谁在前就先调用谁!定义过滤器后会重写三个方法,分别是init(),doFilter(),和destory()。

  1. init方法是过滤器的初始化方法,当web容器创建这个bean的时候就会执行,这个方法可以读取web.xml里面的参数。

  2. doFilter方法是执行过滤的请求的核心,当客户端请求访问web资源时,这个时候我们可以拿到request里面的参数,对数据进行处理后,通过filterChain方法将请求将请求放行,放行后我们也可以通过response对响应进行处理(比如压缩响应),然后会传递到下一个过滤器。

  3. destory方法是当web容器中的过滤器实例被销毁时会被执行,主要作用是释放资源。

Filter代码

//@Component
public class TimeFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) {
        System.out.println("过滤器初始化");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("过滤器执行了");
        long start2 = System.currentTimeMillis();
        filterChain.doFilter(servletRequest, servletResponse);
        long time = System.currentTimeMillis() - start2;
        System.out.println("过滤器执行的时间是 :" + time);
        System.out.println("过滤器执行结束");
    }

    @Override
    public void destroy() {
        System.out.println("过滤器销毁了");
    }
}</pre>

Web.xml配置

<filter>
    <filter-name>encoding</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>encoding</filter-name>
    <servlet-name>/*</servlet-name>
</filter-mapping>


SpringBoot工程可以通过加@Component注解添加进Spring管理,也可以通过下面注册的方式去执行,推荐用下方的方式:

@Bean
public FilterRegistrationBean timeFilter() {
    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
    TimeFilter filter = new TimeFilter();
    filterRegistrationBean.setFilter(filter);
    filterRegistrationBean.addUrlPatterns("/user","/users");
    return filterRegistrationBean;
}

如下图所示过滤器执行流程和生命周期。

执行流程

img

生命周期

img

Interceptor(拦截器)

拦截以 .action结尾的url拦截Action的访问。过滤器依赖serverlet容器,获取request和response处理,是基于函数回调,简单说就是“去取你想取的”;Interfactor是基于Java的反射机制(AOP思想)进行实现,不依赖Servlet容器。如下图所示拦截器执行过程:

img

拦截器可以在方法执行之前(preHandle)和方法执行之后(afterCompletion)进行操作。回调操作(postHandle)可以获取执行的方法的名称,请求(HttpServletRequest)
Interceptor,可以控制请求的控制器和方法,但控制不了请求方法里的参数(只能获取参数的名称,不能获取到参数的值,用于处理页面提交的请求响应并进行处理,例如做国际化,做主题更换,过滤等)。如下代码所示:

@Component
public class FirstHandlerInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object hanlder) {
        out.println("拦截器.preHandle 开始执行。。。");
        out.println(hanlder.getClass().getSimpleName());
        out.println(((HandlerMethod) hanlder).getBean().getClass().getName());
        httpServletRequest.setAttribute("start", currentTimeMillis());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object hanlder, ModelAndView modelAndView) {
        out.println("拦截器.postHandle 开始执行。。。");
        long start = (long) httpServletRequest.getAttribute("start");
        out.println("postHandle执行时间为:" + (currentTimeMillis() - start));

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object hanlder, Exception e) {
        //会打印两次 spring里面的basic error 也会被拦截
        out.println("拦截器.afterCompletion 开始执行。。。");
        long start = (long) httpServletRequest.getAttribute("start");
        out.println("afterCompletion执行时间为:" + (currentTimeMillis() - start));
        out.println("\n ex is :" + e+"\n");
    }
}

再来看看拦截器再spring boot里面的配置:

@Configuration
public class InterceptorConfigure implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new FirstHandlerInterceptor()).addPathPatterns("/**").order(0);
    }
}

HandlerInterceptor三个方法:preHandle、postHandle和afterCompletion的执行时间点如下:

preHandle:在HandlerMapping确定使用哪个Handler处理请求之后,HandlerAdapter调用Handler之前,在该阶段HandlerInterceptor可以修改Request和Response。

postHandle:在HandlerAdapter调用Handler之后,DispatcherServlet渲染视图之前。实际上在调用postHandler之前HandlerAdapter已经完成Response写并提交,因此postHandler无法修改Response。如果有修改Response的场景,可以使用ResponseBodyAdvice接口。

afterCompletion:请求处理完成之后调用,适合做一些资源清理工作。

与业务相关的细粒度任务适合首选HandlerInterceptor,下面是一些可以选择使用HandlerInterceptor的场景:

  1. 鉴权,可以使用HandlerInterceptor但是Filter是更佳的选择。
  2. 审计日志,记录每一个请求。
  3. Token解析或校验。
  4. Handler执行时间统计。

如果在没有显示的指定Interceptor的顺序时,Interceptor的执行顺序将以registry时的顺序执行。可以通过.order()方法指定Interceptor的执行顺序,如下所示:

@Configuration
public class InterceptorConfigure implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new FirstHandlerInterceptor()).addPathPatterns("/**").order(0);
        registry.addInterceptor(new SecondHandlerInterceptor()).addPathPatterns("/**").order(1);
    }
}

如下图所示多个Interceptor的执行顺序说明,如下:
img

总结一下Filter和Interceptor的执行顺序说明,如下:

img

Filter与Interceptor区别

  1. 拦截器是基于java的反射机制,使用代理模式,而过滤器是基于函数回调。

  2. 拦截器不依赖servlet容器,过滤器依赖于servlet容器。

  3. 拦截器只能对action起作用,而过滤器可以对几乎所有的请求起作用(可以保护资源)。

  4. 拦截器可以访问action上下文,堆栈里面的对象,而过滤器不可以。

  5. 执行顺序:过滤前-拦截前-Action处理-拦截后-过滤后。

作用域不同

  1. 过滤器依赖于servlet容器,只能在 servlet容器,web环境下使用
  2. 拦截器依赖于spring容器,可以在spring容器中调用,不管此时Spring处于什么环境

细粒度的不同

  1. 过滤器的控制比较粗,只能在请求进来时进行处理,对请求和响应进行包装
  2. 拦截器提供更精细的控制,可以在controller对请求处理之前或之后被调用,也可以在渲染视图呈现给用户之后调用

中断链执行的难易程度不同

  1. 拦截器可以 preHandle方法内返回 false 进行中断
  2. 过滤器就比较复杂,需要处理请求和响应对象来引发中断,需要额外的动作,比如将用户重定向到错误页面

Spring AOP

在使用Filter的时能获取request和response对象,对请求和响应进行处理。使用Interfactor时我们可以通过handler来获取当前请求控制器的方法名称。但是这两者都有一个弊端,我们拿不到控制器要接收的参数,先看下servlet源码的执行顺序,如下:

img

img

img

从DispatherServlet分发请求时,进入doService()方法内部,在方法参数封装之前,添加了判断,applyPreHandle()方法就时判断拦截器里面的preHandler()方法,根据返回的true或者false,判断是否执行真正的handler,所以我们在拦截器的handler参数里面是获取不到请求的参数的,因此,我们要引入Spring AOP,也就是切片编程,它可以在控制器的执行之前,执行之后,抛出异常等等,进行控制!

Spring声明式事务管理(切面)
AOP操作可以对Spring管理的Bean的访问(业务层Controller、Service等)进行横向的拦截,最大的优势在于它可以获取执行方法的参数(ProceedingJoinPoint.getArgs() ),并对方法进行统一的处理。常用于日志记录、事务管理、请求参数安全验证等。

如下是SpringAOP提供的常用注解:

@Aspect:作用是把当前类标识为一个切面供容器读取
@Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。
@Around:环绕增强,相当于MethodInterceptor
@AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行
@Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有
@AfterThrowing:异常抛出增强,相当于ThrowsAdvice
@After: final增强,不管是抛出异常或者正常退出都会执行

如下是一个SpringAOP参考案例:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAspect {
    @Pointcut("execution(public * com.zq..controller..*Controller.*(..) ) && @annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object function(ProceedingJoinPoint joinPoint) throws Throwable {
        // do something
        // 获取增强的类
        // 获取增强的方法名
        // 获取增强方法的参数

        // 继续执行被拦截方法
        return joinPoint.proceed();
    }
}

总结

filter和interceptor和aspect对比如下:

-filterinterceptoraspect
入参ServletRequest, ServletResponseHttpServletRequest , HttpServletResponse ,Object handlerProceedingJoinPoint
原理依赖于servlet容器,与框架无关Spring框架拦截器,基于Java反射机制动态代理(jdk动态代理/cglib)
范围RESTful apiRESTful apiSpring Bean

filter和interceptor和aspect执行顺序如下:

img

三者功能类似,但各有优势,从过滤器–》拦截器–》切面,拦截规则越来越细致,执行顺序依次是过滤器、拦截器、切面。

一般情况下数据被过滤的时机越早对服务的性能影响越小,因此我们在编写相对比较公用的代码时,优先考虑过滤器,然后是拦截器,最后是aop。

比如权限校验,一般情况下所有的请求都需要做登录校验,此时就应该使用过滤器在最顶层做校验;日志记录,一般日志只会针对部分逻辑做日志记录,而且牵扯到业务逻辑完成前后的日志记录,因此使用过滤器不能细致地划分模块,此时应该考虑拦截器,然而拦截器也是依据URL做规则匹配,因此相对来说不够细致,因此我们会考虑到使用AOP实现,AOP可以针对代码的方法级别做拦截,很适合日志功能。

完!

参考资料:

https://www.jianshu.com/p/2ec6a5f24a33

https://www.jianshu.com/p/10c468cfb671

https://blog.csdn.net/weixin_48052161/article/details/111025970

https://www.jianshu.com/p/a7f5707b72a4

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员古德

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值