Spring 监听器、过滤器、拦截器、AOP 比较
前言:
在后端项目开发中,会有一些需要基于全局处理的程序。传统基于Servlet容器的程序中,我们可以使用过滤器和监听器,在Java 框架中还可以使用拦截器,而面向切面编程AOP更是作为Spring框架中的核心思想被大家所关注。本文一方面从概念上讲解Filter、Listener、Interceptor和aop的区别,另一方面更是从代码层面讲解如何在SpringBoot中应用开发。
它们的执行顺序如下(@ControllerAdvice本文暂不做介绍):
拦截顺序:ServletContextListener> Filter > Interception > AOP > 具体执行的方法 > AOP > @ControllerAdvice > Interception > Filter > ServletContextListener
根据实现原理分成下面两大类:
Filter和Listener:依赖Servlet容器,基于函数回调实现。可以拦截所有请求,覆盖范围更广,但无法获取ioc容器中的bean。
Interceptor和aop:依赖spring框架,基于java反射和动态代理实现。只能拦截controller的请求,可以获取ioc容器中的bean。
从 Filter -> Interceptor -> aop ,拦截的功能越来越细致、强大,尤其是Interceptor和aop可以更好的结合spring框架的上下文进行开发。但是拦截顺序也是越来越靠后,请求是先进入Servlet容器的,越早的过滤和拦截对系统性能的消耗越少。具体选用哪种方法,就需要开发人员根据实际业务情况综合考虑了。
监听器(Listener)
Listener监听器也是Servlet层面的,可以用于监听Web应用中某些对象、信息的创建、销毁和修改等动作发生,然后做出相应的响应处理。根据监听对象,将监听器分为3类:
- ServletContext:对应application,实现接口ServletContextListener。在整个Web服务中只有一个,在Web服务关闭时销毁。可用于做数据缓存,例如结合redis,在Web服务创建时从数据库拉取数据到缓存服务器。
- HttpSession:对应session会话,实现接口HttpSessionListener。在会话起始时创建,一端关闭会话后销毁。可用作获取在线用户数量。
- ServletRequest:对应request,实现接口ServletRequestListener。request对象是客户发送请求时创建的,用于封装请求数据,请求处理完毕后销毁。可用作封装用户信息。
在写Listener的类时,实现方式和Filter一样,同样有两种实时方式。一种是只加@Component;另一种是 @WebListener 和 @ServletComponentScan 配合使用。不过实现接口则根据监听对象区分,如:ServletContextListener、HttpSessionListener和ServletRequestListener。
1 2 3 4 5 6 7 8 9 10 11 12 13
| @WebListener public class DemoListener implements ServletContextListener{
@Override public void contextInitialized(ServletContextEvent sce) { System.out.println("ServletContextListener 初始化上下文"); }
@Override public void contextDestroyed(ServletContextEvent sce) { System.out.println("ServletContextListener 销毁"); } }
|
过滤器(Filter)
Filter过滤器是Servlet容器层面的,在实现上基于函数回调,可以对几乎所有请求进行过滤。过滤器是对数据进行过滤,预处理过程,当我们访问网站时,有时候会发布一些敏感信息,发完以后有的会用*替代,还有就是登陆权限控制等,一个资源,没有经过授权,肯定是不能让用户随便访问的,这个时候,也可以用到过滤器。过滤器的功能还有很多,例如实现URL级别的权限控制、压缩响应信息、编码格式等等。
SpringBoot实现过滤器,常见有三种方式,越复杂功能越强大。
无路径无顺序@Component
这种方式最简单,直接实现Filter接口,并使用@Component注解标注为组件自动注入bean。但是缺点是没办法设置过滤的路径,默认是 /* 过滤所有。
Filter接口有 init、doFilter、destroy 三个方法,但 init、destroy 是有默认方法实现,可以不重写。
1 2 3 4 5 6 7 8 9 10
| @Component public class DemoFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException{ HttpServletRequest httpServletRequest=(HttpServletRequest)servletRequest; HttpServletResponse httpServletResponse=(HttpServletResponse)servletResponse; System.out.println("filter1----------"+httpServletResponse.getStatus()); filterChain.doFilter(servletRequest,servletResponse); } }
|
有路径无顺序@WebFilter+@ServletComponentScan
这种方式要稍微复杂一点,但更全面。使用 @WebFilter替代 @Component,可以通过该注解设置过滤器的匹配路径。不过需要在启动类中使用@ServletComponentScan。@ServletComponentScan扫描带@WebFilter、@WebServlet、@WebListener并将帮我们注入bean。
1 2 3 4 5 6 7 8 9 10
| @WebFilter(filterName = "filter1",urlPatterns = {"/hello/*"}) public class DemoFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException{ HttpServletRequest httpServletRequest=(HttpServletRequest)servletRequest; HttpServletResponse httpServletResponse=(HttpServletResponse)servletResponse; System.out.println("filter1----------"+httpServletResponse.getStatus()); filterChain.doFilter(servletRequest,servletResponse); } }
|
有路径有顺序@Configuration
这种方式完全通过配置类来实现,在只实现过滤器的接口,并不需要通过任何注解注入IOC容器,都通过配置类来注入。
AFilter.java(BFilter.java也类似)
1 2 3 4 5 6 7 8
| public class AFilter implements Filter {
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("filterA----------"); filterChain.doFilter(servletRequest,servletResponse); } }
|
FilterConfig.java 配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Configuration public class FilterConfig { @Bean public FilterRegistrationBean AFilter() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); AFilter aFilter=new AFilter(); filterRegistrationBean.setFilter(aFilter); filterRegistrationBean.addUrlPatterns("*"); filterRegistrationBean.setName("AFilter"); filterRegistrationBean.setOrder(1); return filterRegistrationBean; }
@Bean public FilterRegistrationBean BFilter() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); BFilter bFilter=new BFilter(); filterRegistrationBean.setFilter(bFilter); filterRegistrationBean.addUrlPatterns("*"); filterRegistrationBean.setName("BFilter"); filterRegistrationBean.setOrder(2); return filterRegistrationBean; } }
|
拦截器(Interceptor)
Interceptor拦截器和Filter和Listener有本质上的不同,前面二者都是依赖于Servlet容器,而Interceptor则是依赖于Spring框架,是aop的一种表现,基于Java的动态代理实现的。在SpringBoot中实现拦截器的方式,有点类似于实现过滤器的第三种方式,所以要通过下面两个步骤。
- 声明拦截器的类:通过实现 HandlerInterceptor接口,实现preHandle、postHandle和afterCompletion方法。
- 通过配置类配置拦截器:通过实现WebMvcConfigurer接口,实现addInterceptors方法。
DemoInterceptor.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class DemoInterceptor implements HandlerInterceptor{
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); String methodName = method.getName(); System.out.println("====拦截到了方法:"+methodName+",preHandle===="); return true; }
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { System.out.println("====postHandle===="); }
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { System.out.println("====afterCompletion===="); } }
|
InterceptorConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13
|
@Configuration public class InterceptorConfig implements WebMvcConfigurer {
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/**"); }
}
|
AOP
相比较于拦截器,Spring 的aop则功能更强大,封装的更细致,需要单独引用 jar包。
pom.xml
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
|
在定义AOP的类时,不需要和前面拦截器一样麻烦了,只需要通过注解,底层实现逻辑都通过IOC框架实现好了,涉及到的注解如下:
- @Aspect:将一个 java 类定义为切面类。
- @Pointcut:定义一个切入点,可以是一个规则表达式,比如下例中某个 package 下的所有函数,也可以是一个注解等。
- @Before:在切入点开始处切入内容。
- @After:在切入点结尾处切入内容。
- @AfterReturning:在切入点 return 内容之后处理逻辑。
- @Around:在切入点前后切入内容,并自己控制何时执行切入点自身的内容。原则上可以替代@Before和@After。
- @AfterThrowing:用来处理当切入内容部分抛出异常之后的处理逻辑。
- @Order(100):AOP 切面执行顺序, @Before 数值越小越先执行,@After 和 @AfterReturning 数值越大越先执行。
DemoAspect.java
1 2 3 4 5 6 7 8 9 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
|
@Aspect @Component public class DemoAspect {
@Pointcut( "execution( public * pers.kerry.demo.webfilter.controller.DemoController.*(..) )") public void doPointcut(){ }
@Before("doPointcut()") public void doBefore(){ System.out.println("==doBefore=="); }
@After("doPointcut()") public void doAfter(){ System.out.println("==doAfter=="); }
@AfterReturning("doPointcut()") public void doAfterReturning(){ System.out.println("==doAfterReturning=="); }
@Around("doPointcut()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{ System.out.println("==doAround.before=="); Object ret=proceedingJoinPoint.proceed(); System.out.println("==doAround.after=="); return ret; } }
|