一篇文章说清楚Filter(过滤器)、Interceptor(拦截器)和AOP(切面儿)

news/2024/7/7 20:43:59 标签: java, 架构

文章目录

    • 前言
    • 一、Filter(过滤器)
      • 1.说明
      • 2.实现
        • filterChain.doFilter()
      • 3.order优先级
      • 4.解决跨域
      • 5.拦截返回错误信息JSON
    • 二、Interceptor(拦截器)
      • 1.说明
      • 2.实现
        • preHandle
        • postHandle
        • afterCompletion
      • 3.执行顺序图
      • 4.排除特定路径拦截 or 加入指定路径拦截
      • 5.拦截返回错误信息JSON
    • 三、AOP(切面儿)
      • 1.说明
      • 2.实现
        • Pointcut切面节点定义
        • doBefore
        • doAfter
        • doAfterReturning
        • doAfterThrowing
        • doAround
      • 3.基于注解在特定方法上实现
      • 4.拦截返回错误信息JSON
    • 四、三者共同点及不同点
      • 1.共同点
      • 2.不同点
    • 五、三者横切节点分析

前言

        Filter、Interceptor、AOP都是用于实现应用横切关注点的技术手段,通过这些技术,可以将横切关注点的代码从核心业务逻辑中解耦,使得代码更加清晰和可维护,同时也提高了代码的复用性。但是三者的应用场景还是有些区别的,Filter主要用于处理HTTP请求和响应,在Servlet容器中工作,可以实现如日志记录、安全性过滤、跨域请求处理等;Interceptor主要用于在Spring MVC中拦截方法调用,允许在方法执行前后添加额外逻辑,用于实现权限拦截、日志记录、事务管理等;AOP用于处理复杂的横切关注点,在不修改核心业务逻辑的情况下增加或调整功能,用于日志记录、事务管理、性能监控等。

一、Filter(过滤器)

1.说明

        Filter主要用于Web应用开发中,基于Servlet规范工作,处理HTTP请求和响应,可以在请求到达Servlet之前进行预处理,也可以在响应返回给客户端之前进行后处理。Filter可以组成过滤链,按照配置的顺序依次处理请求,每个Filter可以在请求进入Servlet之前进行拦截,也可以在响应返回之前对响应进行处理。需要实现Filter接口,重写init()、doFilter()和destroy()方法,其中init()和destroy()分别是初始化和销毁方法,重点是doFilter()方法,实现对请求的处理和转发或对响应的处理。

2.实现

java">/**
 * 过滤器
 */
@Component
public class WsFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String url = request.getRequestURI(); // 相对方法名路径
        System.out.println("\n【过滤器】请求到达:" + url);
        // 如果没有filterChain.doFilter方法,请求的方法就直接被过滤了,执行不到下面的Controller方法中
        filterChain.doFilter(request, response);
        System.out.println("【过滤器】请求结束:" + url);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}
filterChain.doFilter()

项目启用了过滤器,doFilter方法下如果没有filterChain.doFilter(request, response)方法,则程序请求的方法就直接被过滤了,执行不到下面的Controller方法中。

3.order优先级

配置多个Filter,根据不同的order来决定过滤执行的顺序,order越小优先级越高,越大优先级越低。

4.解决跨域

与前端项目交互时,尤其是前后端分离项目,经常会出现跨域问题,这个时候需要配置跨域参数,设置Access-Control允许各类请求通过,解决跨平台交互的跨域问题。如下:

java">	@Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String url = request.getRequestURI(); // 相对方法名路径
        System.out.println("\n【过滤器】请求到达:" + url);

        // 跨域配置
        if (request.getHeader("Origin") != null) {
            response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
        }
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, remember-me");

        // 如果没有filterChain.doFilter方法,请求的方法就直接被过滤了,执行不到下面的Controller方法中
        filterChain.doFilter(request, response);
        System.out.println("【过滤器】请求结束:" + url);
    }

5.拦截返回错误信息JSON

如果是void无返回值的方法,直接拦截是没有问题的,直接不执行filterChain.doFilter(request, response)方法即可。但是如果是有返回值的,需要在过滤器上构造JSON进行返回,如下:

java">	@Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String url = request.getRequestURI(); // 相对方法名路径
        System.out.println("\n【过滤器】请求到达:" + url);

        returnJson(response, 401, "Filter拦截了");

        // 如果没有filterChain.doFilter方法,请求的方法就直接被过滤了,执行不到下面的Controller方法中
        // filterChain.doFilter(request, response);
        // System.out.println("【过滤器】请求结束:" + url);
    }

    private void returnJson(HttpServletResponse response, int code, String msg) throws IOException {
        // 设置响应的内容类型为JSON
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        // 创建JSON字符串
        Map<String, Object> map = new HashMap<>();
        map.put("code", code);
        map.put("msg", msg);
        String json = JSON.toJSONString(map);
        // 获取PrintWriter来写入响应
        PrintWriter out = response.getWriter();
        out.print(json);
        out.flush();
    }

二、Interceptor(拦截器)

1.说明

        Interceptor主要用于Spring MVC框架中拦截方法的调用,用于框架中方法调用的拦截和处理,拦截器与过滤器(Filter)类似,但更加灵活,通常用于框架级别的功能扩展和定制化。通常用于框架级别的功能扩展,如事务管理、日志记录、权限检查等,使得应用程序可以更加模块化、可维护和可扩展。

2.实现

java">/**
 * 拦截器
 */
@Component
public class WsInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String url = request.getRequestURI(); // 相对方法名路径
        System.out.println("【拦截器】到达:"+ url);
        return true; // 放开,继续执行
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 当前请求进行处理之后,也就是Controller方法调用之后执行,但是它会在DispatcherServlet进行视图返回渲染之前被调用
        System.out.println("【拦截器】请求处理完成,DispatcherServlet准备进行视图返回渲染");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 在整个请求结束之后,也就是在DispatcherServlet渲染了对应的视图之后执行
        System.out.println("【拦截器】请求结束,DispatcherServlet渲染了对应的视图");
    }
}
java">@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {

    @Bean
    public WsInterceptor getWsInterceptor() {
        return new WsInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getWsInterceptor());
    }
}
preHandle

请求处理前preHandle,是在请求处理器(如Controller方法)执行前被调用的方法,允许开发者在实际请求处理前执行特定的预处理逻辑。必须【return true】时,才能执行到后续的controller方法中,如果【return false】则不往下继续执行。

postHandle

请求处理后postHandle,当前请求进行处理之后,也就是Controller方法调用之后执行,但是它会在DispatcherServlet进行视图返回渲染之前被调用。

afterCompletion

视图渲染后afterCompletion,在整个请求结束之后,也就是在DispatcherServlet渲染了对应的视图之后执行。

3.执行顺序图

在这里插入图片描述

4.排除特定路径拦截 or 加入指定路径拦截

如果什么都不处理,默认拦截所有的请求
registry.addPathPatterns(URL)为加入指定路径拦截,则其他路径都放行

java">	@Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getWsInterceptor()).addPathPatterns("/test/test02");
    }

registry.excludePathPatterns(URL)为排除特定路径拦截,其他路径都拦截

java">	@Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getWsInterceptor()).excludePathPatterns("/test/test02");
    }

5.拦截返回错误信息JSON

如果是void无返回值的方法,直接拦截是没有问题的,直接【return false】即可。但是如果是有返回值的,需要在拦截器上构造JSON进行返回,如下:

java">	@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String url = request.getRequestURI(); // 相对方法名路径
        System.out.println("【拦截器】到达:"+ url);
        returnJson(response, 402, "Interceptor拦截了");
        return false;
        // return true; // 放开,继续执行
    }

    private void returnJson(HttpServletResponse response, int code, String msg) throws IOException {
        // 设置响应的内容类型为JSON
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        // 创建JSON字符串
        Map<String, Object> map = new HashMap<>();
        map.put("code", code);
        map.put("msg", msg);
        String json = JSON.toJSONString(map);
        // 获取PrintWriter来写入响应
        PrintWriter out = response.getWriter();
        out.print(json);
        out.flush();
    }

三、AOP(切面儿)

1.说明

        AOP切面是一种编程范式,用于通过将横切关注点从核心业务逻辑中分离出来,使得这些关注点能够被模块化、重用,并且能够有效地降低代码的重复性。它定义了在何处(Pointcut)以及如何(Advice)应用横切关注点到目标对象的行为,增强系统的模块化程度,提高代码的可维护性和可扩展性。

2.实现

java">@Aspect
@Component
public class WsAop {
    // 定义切面儿的切入点,参数是定义在哪个包。哪个类、哪个方法切入,关于切入点如何定义(对应路径下的某类、某方法,*代表的是全部)
    @Pointcut("execution(* com.jon.demo.controller.*.*(..))")
    public void wsLog(){}

    @Before("wsLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // 在切⼊点开始处切⼊内容
        System.out.println("【AOP】before 在切⼊点开始处切⼊内容");
    }

    @After("wsLog()")
    public void doAfter() {
        // 在切⼊点结尾处切⼊内容
        System.out.println("【AOP】doAfter 在切⼊点结尾处切⼊内容");
    }

    @AfterReturning(pointcut = "wsLog()", returning = "result")
    public void doAfterReturning(Object result) {
        // 在切⼊点return内容之后切⼊内容,可以对返回值做⼀些加⼯处理
        System.out.println("【AOP】doAfterReturning 在切⼊点return内容之后切⼊内容");
    }

    @AfterThrowing(pointcut = "wsLog()", throwing = "exception")
    public void doAfterThrowing(Exception exception) {
        // 处理当切⼊内容部分抛出异常之后的逻辑
        System.out.println("【AOP】doAfterThrowing 处理当切⼊内容部分抛出异常之后的逻辑");
    }

    // 环绕通知wsLog方法,切面方法需要有返回值,来代替被代理方法返回结果
    @Around("wsLog()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        // 方法要有返回,如果有多个参数ProceedingJoinPoint要放在第一个
        System.out.println("【AOP】doAround");
        return point.proceed();
    }
}
Pointcut切面节点定义

定义切面儿的切入点,参数是定义在哪个包。哪个类、哪个方法切入,关于切入点如何定义(对应路径下的某类、某方法,*代表的是全部)

doBefore

在切⼊点开始处切⼊内容

doAfter

在切⼊点结尾处切⼊内容

doAfterReturning

在切⼊点return内容之后切⼊内容,可以对返回值做⼀些加⼯处理。

doAfterThrowing

处理当切⼊内容部分抛出异常之后的逻辑

doAround

环绕通知wsLog方法,切面方法需要有返回值,来代替被代理方法返回结果。方法要有返回,如果有多个参数ProceedingJoinPoint要放在第一个。

3.基于注解在特定方法上实现

        不在Pointcut指定特意的类、方法作为切入点,直接自定义注解,并且指定注解的引用地址,这样在对应的方法中加上注解就会引入切面了,这样更加方便和灵活。具体的使用方式,在之前的文章自定义注解(一)——统一请求拦截中有定义!

4.拦截返回错误信息JSON

如果是void无返回值的方法,直接拦截是没有问题的,直接around方法不执行point.proceed()即可。但是如果是有返回值的,需要在around方法中构造JSON,并且在point.proceed()之前进行返回,如下:

java">	// 环绕通知wsLog方法,切面方法需要有返回值,来代替被代理方法返回结果
    @Around("wsLog()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        // 方法要有返回,如果有多个参数ProceedingJoinPoint要放在第一个
        System.out.println("【AOP】doAround");
        Map<String, Object> map = new HashMap<>();
        map.put("code", 403);
        map.put("msg", "AOP拦截了");
        return map;
//        return point.proceed();
    }

四、三者共同点及不同点

1.共同点

三者都是处理横切关注点,即那些不能分散在核心业务逻辑中处理的功能或需求,例如日志记录、性能监控、事务管理等。它们都支持在运行时动态地将横切关注点的代码织入到目标对象的执行流程中,而不需要修改目标对象的源代码。

2.不同点

Filter工作在Servlet容器中,可以对请求链进行处理,例如处理编码、安全性、日志等。对比于,Filter更多用于整个请求和响应的处理。

Interceptor主要工作Spring容器中,用于框架中方法调用的拦截和处理,通常用于面向对象的框架中,在方法调用前后可以添加额外逻辑。对比于Filter,Interceptor更加专注于方法调用前后的处理。

AOP用于处理复杂的横切关注点,允许在运行时动态地将横切逻辑织入到应用中,可以跨越多个类和模块,不仅限于单个方法或请求处理。可以在代码中定义关注点(如事务、日志、安全性),并在需要时将其应用到目标对象中。

五、三者横切节点分析

在这里插入图片描述
    三者横切的节点不一样,客户端端发起请求后,请求顺序为:请求入口 -> 过滤器filterChain.doFilter之前 -> 拦截器preHandle方法 -> 切面doAround方法 -> 切面before方法 -> Controller下的方法

    返回顺序为:Controller下的方法执行结束 -> 切面doAfterReturning方法 -> 切面doAfter方法 -> 拦截器postHandle方法 -> 拦截器afterCompletion方法 -> 过滤器filterChain.doFilter之后 -> 请求入口

可看执行结果如下:
在这里插入图片描述


http://www.niftyadmin.cn/n/5535318.html

相关文章

第5篇 区块链的技术架构:节点、网络和数据结构

区块链技术听起来很高大上&#xff0c;但其实它的核心架构并不难理解。今天我们就用一些简单的例子和有趣的比喻&#xff0c;来聊聊区块链的技术架构&#xff1a;节点、网络和数据结构。 节点&#xff1a;区块链的“细胞” 想象一下&#xff0c;区块链就像是一个大型的组织&a…

球形气膜:现代娱乐场馆的最佳选择—轻空间

随着科技的发展和人们对高品质生活的追求&#xff0c;娱乐场馆的建设迎来了新的变革。球形气膜结构凭借其独特的优势&#xff0c;逐渐成为现代娱乐场馆建设的最佳选择。轻空间将介绍球形气膜的优势&#xff0c;并探讨其在不同应用场景中的广泛应用。 球形气膜的优势 1. 独特的建…

LabVIEW风机跑合监控系统

开发了一种基于LabVIEW的风机跑合监控系统&#xff0c;提高风机测试的效率和安全性。系统通过自动控制风机的启停、实时监控电流和功率数据&#xff0c;并具有过流保护功能&#xff0c;有效减少了人工操作和安全隐患&#xff0c;提升了工业设备测试的自动化和智能化水平。 项目…

Python——面向对象编程(类和对象)2

目录 私有属性和私有方法 01.应用场景及定义方式 02.伪私有属性和私有方法 继承 1.1继承的概念、语法和特点 1.继承的语法&#xff1a; 2.专业术语&#xff1a; 3.继承的传递性 1.2方法的重写 1.覆盖父类的方法 2.对父类方法进行扩展 关于super 1.3 父类的私有属性和…

T113基于评估板SDK配置PD引脚异常

使用PD0/PD1/PD2作为IO输入时,发现输入检测到的值异常,断开输入的信号,直接示波器打IO口,还能发现波形信号,猜测该引脚存在引脚复用情况。 原因 这三个引脚在默认系统是作为显示相关引脚功能。 解决方法 1 ) Uboot修改

go Channel原理 (二)

Channel 设计原理 不要通过共享内存的方式进行通信&#xff0c;而是应该通过通信的方式共享内存。 在主流编程语言中&#xff0c;多个线程传递数据的方式一般都是共享内存。 Go 可以使用共享内存加互斥锁进行通信&#xff0c;同时也提供了一种不同的并发模型&#xff0c;即通…

[Microsoft Office]Word设置页码从第二页开始为1

目录 第一步&#xff1a;设置页码格式 第二步&#xff1a;设置“起始页码”为0 第三步&#xff1a;双击页码&#xff0c;出现“页脚”提示 第四步&#xff1a;选中“首页不同” 第一步&#xff1a;设置页码格式 第二步&#xff1a;设置“起始页码”为0 第三步&#xff1a;双…

Elasticsearch集群部署(下)

目录 上篇&#xff1a;Elasticsearch集群部署&#xff08;上&#xff09;-CSDN博客 七. Filebeat 部署 八. 部署Kafka 九. 集群测试 链接&#xff1a;https://pan.baidu.com/s/1AFXSmDdY5xBb7g35ipKoaw?pwdfa9m 提取码&#xff1a;fa9m 七. Filebeat 部署 为什么用 F…