拦截器、过滤器、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如下:
@Component
// 切面
@Aspect
public class VingAspectJ {
/**
* 切点
* 为什么切点要声明在一个方法上?目的是为了将注解写在上面而已
* pointcut是连接点的集合(就是方法的集合)
*/
@Pointcut("execution(* com.ving.dao.*.*(..))")
public void pointCut(){
}
/**
* 通知--->配置切点
*/
@After("com.ving.config.VingAspectJ.pointCut()")
public void after(){
System.out.println("after");
}
@Before("com.ving.config.VingAspectJ.pointCut()")
public void before(){
System.out.println("before");
}
}
- 代理时机:初始化的时候,已经将目标对象进行了代理,放到了Spring容器中。根据被代理对象是否是实现了接口的类,如果是,走JDK动态代理,否则走CGLIB。
- 应用场景:日志记录、权限验证、事务管理
织入(weaving):将代理逻辑加入到目标对象上的过程叫织入
拦截器与过滤器【1】
拦截器(接口org.springframework.web.servlet.HandlerInterceptor
)是Spring组件,由Spring容器管理,不依赖Tomcat等容器,可以单独使用,不仅可以使用在web程序,也可以用于Application等程序中;javax.servlet.Filter
在Servlet
规范中定义,接口功能和web容器绑定,所以是依赖于Tomcat等web容器的,所以只能在web中使用。
均体现了AOP
的编程思想,都可以实现日志记录、登陆鉴权等功能,但两者实现原理不同,过滤器是基于函数回调的,拦截器时基于Java反射实现的。
拦截器的匹配颗粒度更细致,可以到url,但相比AOP可以到方法,最细致的还是AOP
优先级:过滤器用@Order
控制,拦截器用#order()
控制,值越小越先执行
拦截器
HandlerInteceptor
Spring拦截器基于接口org.springframework.web.servlet.HandlerInterceptor
实现,内部方法定义如下:
public interface HandlerInterceptor {
/**
* 调用时机:HandlerMapping确定合适的处理程序对象之后,HandlerAdapter调用处理方法之前
**/
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
/**
* 调用时机:HandlerAdapter调用处理程序之后,DispatcherServlet呈现视图(渲染视图)之前
**/
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
/**
* 调用时机:请求处理完成之后的回调(渲染视图之后),即结果输出到客户端之后。
* 适合做一些清理工作,该方法参数包含Exception,说明无论是否抛出异常都会执行。
* 不会执行的情况:对应的preHandler没有返回true或者异常,该方法不会执行
**/
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
将自定义好的拦截器处理类进行注册即可,代码如下:
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
}
}
过滤器
Filter
过滤器的配置比较简单,直接实现Filter
接口即可,也可以通过@WebFilter
注解实现对特定URL拦截,看到Filter
接口中定义了三个方法
public interface Filter {
/**
* Servlet容器初始化过滤器时被调用,该方法必须执行成功,否则过滤器不起作用
**/
public void init(FilterConfig filterConfig) throws ServletException;
/**
* 容器中每一次请求都会调用该方法
* @param chain: FilterChain用来调用下一个过滤器Filter。filterChain.doFilter(servletRequest, servletResponse);
**/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException;
/**
* 由web容器调用,当web容器销毁过滤器实例时调用该方法。一般是在doFilter()中的所有线程都退出或超时调用,调用后Filter不再起作用
**/
public void destroy();
}
ApplicationFilterChain
过滤器的AOP实现是基于回调的,具体实现通过ApplicationFilterChain
。
public final class ApplicationFilterChain implements FilterChain {
@Override
public void doFilter(ServletRequest request, ServletResponse response) {
...//省略
internalDoFilter(request,response);
}
private void internalDoFilter(ServletRequest request, ServletResponse response){
if (pos < n) {
//获取第pos个filter
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
...
filter.doFilter(request, response, this);
}
}
}
每个xxxFilter 会先执行自身的 doFilter() 过滤逻辑,最后在执行结束前会执行filterChain.doFilter(servletRequest, servletResponse),也就是回调ApplicationFilterChain的doFilter() 方法,以此循环执行实现函数回调。
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 业务逻辑过滤
...
// 调用下一个,如果不存在则执行业务代码service()
filterChain.doFilter(servletRequest, servletResponse);
}
过滤器+监听器+拦截器+AOP(切面)
拦截顺序:ServletContextListener
> Filter
> Interception
> AOP
> 具体执行的方法 > AOP
> @ControllerAdvice
> Interception
> Filter
> ServletContextListener
参考
【1】过滤器 和 拦截器 的6个区别:https://mp.weixin.qq.com/s?src=11×tamp=1633923834&ver=3367&signature=ObMvqTqXciDv6nyxxkeZiIMqoX-MGM2YspW9NlZQacyqaRz8sX2RjGaJe0ZgHDeIJUyKiyGEfaUwRXQGkcBQ5cqsoc4B9xT8d5aPNUVQioovTgGudHTQ-Vhyk1JJfG&new=1 后端技术漫谈