一
前言
在后端项目开发中,会有一些需要基于全局处理的程序。传统基于 Servlet 容器的程序中,我们可以使用过滤器和监听器,在 Java 框架中还可以使用拦截器,而面向切面编程 AOP 更是作为 Spring 框架中的核心思想被大家所关注。 本文一方面从概念上讲解 Filter 、 Listen er 、 Interceptor 和 aop 的区别,另一方面更是从代码层面讲解如何在 SpringBoot 中应用开发。 它们的执行顺序如下 (@ControllerAdvice 本文暂不做介绍) : 拦截顺序: ServletContextListener > Filter > Interception > AOP > 具体执行的方法 > AOP > @ControllerAdvice > Interception > Filter > ServletContextListener
根据实现原理分成下面两大类: 1. Filter 和 Listener
:依赖 Servlet 容器,基于函数回调实现。可以拦截所有请求,覆盖范围更广,但无法获取 ioc 容器中的 bean 。 2.Interceptor 和 aop
:依赖
spring
框架,基于
java
反射和动态代理实现。只能拦截
controller
的请求,可以获取
ioc
容器中的
bean
。
从
Filter -> Interceptor -> aop
,拦截的功能越来越细致、强大,尤其是
Interceptor
和
aop
可以更好的结合
spring
框架的上下文进行开发。
但是拦截顺序也是越来越靠后,请求是先进入
Servlet
容器的,越早的过滤和拦截对系统性能的消耗越少。
具体选用哪种方法,就需要开发人员根据实际业务情况综合考虑了。
二
过滤器(Filter)
Filter
过滤器是
Servlet
容器层面的,在实现上基于函数回调,可以对几乎所有请求进行过滤。
过滤器是对数据进行过滤,预处理过程,当我们访问网站时,有时候会发布一些敏感信息,发完以后有的会用
*
替代,还有就是登陆权限控制等,一个资源,没有经过授权,肯定是不能让用户随便访问的,这个时候,也可以用到过滤器。
过滤器的功能还有很多,例如实现
URL
级别的权限控制、压缩响应信息、编码格式等等。
SpringBoot
实现过滤器,常见有三种方式,越复杂功能越强大。
2.1 无路径无顺序 @Component
这种方式最简单,直接实现
Filter
接口,并使用
@Component
注解标注为组件自动注入
bean
。
但是缺点是没办法设置过滤的路径,默认是
/*
过滤所有。
Filter
接口有
init、doFilter、destroy
三个方法,但
init、destroy
是有默认方法实现,可以不重写。
import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;
@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);
}
}
import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;
@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);
}
}
2.2 有路径无顺序 @WebFilter+@ServletComponentScan
这种方式要稍微复杂一点,但更全面。使用
@WebFilter
替代
@Component
,可以通过该注解设置过滤器的匹配路径。不过需要在启动类中使用
@ServletComponentScan
。
@ServletComponentScan
扫描带
@WebFilter、@WebServlet、@WebListener
并将帮我们注入
bean
。
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@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);
}
}
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@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);
}
}
2.3 有路径有顺序 @Configuration
这种方式完全通过配置类来实现,在只实现过滤器的接口,并不需要通过任何注解注入 IOC 容器,都通过配置类来注入。 AFilter.java
(BFilter.java 也类似)
import javax.servlet.*;
import java.io.IOException;
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);
}
}
import javax.servlet.*;
import java.io.IOException;
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 配置类
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import pers.kerry.demo.webfilter.filter.AFilter;
import pers.kerry.demo.webfilter.filter.BFilter;
@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;
}
}
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import pers.kerry.demo.webfilter.filter.AFilter;
import pers.kerry.demo.webfilter.filter.BFilter;
@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;
}
}
三
监听器(Listener )
Listener 监听器也是 Servlet 层面的,可以用于监听 Web 应用中某些对象、信息的创建、销毁和修改等动作发生,然后做出相应的响应处理。 根据监听对象,将监听器分为 3 类: 1.Ser
vletContext
: 对应 application ,实现接口 ServletContextListener 。 在整个 Web 服务中只有一个,在 Web 服务关闭时销毁。 可用于做数据缓存,例如结合 redis ,在 Web 服务创建时从数据库拉取数据到缓存服务器。 2. HttpSession
: 对应 session 会话,实现接口 HttpSessionListener 。 在会话起始时创建,一端关闭会话后销毁。 可用作获取在线用户数量。 3. ServletRequest
:
对应
request
,实现接口
ServletRequestListener
。
request
对象是客户发送请求时创建的,用于封装请求数据,请求处理完毕后销毁。
可用作封装用户信息。
在写
Listener
的类时,实现方式和
Filter
一样,同样有两种实时方式。一种是只加
@Component
;另一种是
@WebListener
和
@ServletComponentScan
配合使用。不过实现接口则根据监听对象区分,如:
ServletContextListener、HttpSessionListener
和
ServletRequestListener
。
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@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 销毁");
}
}
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@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 销毁");
}
}
四
拦截器(Interceptor )
Interceptor 拦截器和 Filter 和 Listener 有本质上的不同,前面二者都是依赖于 Servlet 容器,而 Interceptor 则是依赖于 Spring 框架,是 aop 的一种表现,基于 Java 的动态代理实现的。在 SpringBoot 中实现拦截器的方式,有点类似于实现过滤器的第三种方式,所以要通过下面两个步骤。 1.声明
拦截器的类
:通过实现 HandlerInterceptor 接口,实现 preHandle、postHandle 和 afterCompletion 方法。 2. 通
过配置类配置拦截器
: 通过实现 WebMvcConfigurer 接口,实现 addInterceptors 方法。 DemoInterceptor.java
import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
public class DemoInterceptor implements HandlerInterceptor{
/**
* 预处理回调方法,实现处理器的预处理(如检查登陆),第三个参数为响应的处理器
* 返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应
* @throws Exception
*/
@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;
}
/**
* 后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
System.out.println("====postHandle====");
}
/**
*整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
System.out.println("====afterCompletion====");
}
}
import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
public class DemoInterceptor implements HandlerInterceptor{
/**
* 预处理回调方法,实现处理器的预处理(如检查登陆),第三个参数为响应的处理器
* 返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应
* @throws Exception
*/
@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;
}
/**
* 后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
System.out.println("====postHandle====");
}
/**
*整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
System.out.println("====afterCompletion====");
}
}
InterceptorConfig.java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import pers.kerry.demo.webfilter.interceptor.DemoInterceptor;
/**
* spring 2.x 以前,通过继承 WebMvcConfigurerAdapter 类
* spring 2.x 之后,实现 WebMvcConfigurer 接口
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/**");
}
}
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import pers.kerry.demo.webfilter.interceptor.DemoInterceptor;
/**
* spring 2.x 以前,通过继承 WebMvcConfigurerAdapter 类
* spring 2.x 之后,实现 WebMvcConfigurer 接口
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/**");
}
}
五
AOP
相比较于拦截器, Spring 的 aop 则功能更强大,封装的更细致,需要单独引用 jar 包。 pom.xml
org.springframework.boot
spring-boot-starter-aop
org.springframework.boot
spring-boot-starter-aop
在定义
AOP
的类时,不需要和前面拦截器一样麻烦了,只需要通过注解,底层实现逻辑都通过
IOC
框架实现好了,涉及到的注解如下:
- @Aspect:将一个 java 类定义为切面类。
- @Pointcut:定义一个切入点,可以是一个规则表达式,比如下例中某个 package 下的所有函数,也可以是一个注解等。
- @Before:在切入点开始处切入内容。
- @After:在切入点结尾处切入内容。
- @AfterReturning:在切入点 return 内容之后处理逻辑。
- @Around:在切入点前后切入内容,并自己控制何时执行切入点自身的内容。原则上可以替代 @Before 和 @After。
- @AfterThrowing:用来处理当切入内容部分抛出异常之后的处理逻辑。
- @Order(100):AOP 切面执行顺序, @Before数值越小越先执行,@After 和 @AfterReturning 数值越大越先执行。
DemoAspect.java
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @Aspect 注解 使之成为切面类
* @Component 注解 把切面类加入到IOC容器中
*/
@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==");
}
/**
* 返回值类型Object,对应所有被拦截方法的返回值类型,不能为void
*/
@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;
}
}
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @Aspect 注解 使之成为切面类
* @Component 注解 把切面类加入到IOC容器中
*/
@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==");
}
/**
* 返回值类型Object,对应所有被拦截方法的返回值类型,不能为void
*/
@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;
}
}