过滤器与拦截器

news/2024/9/19 5:09:32/文章来源:https://www.cnblogs.com/pyb999/p/18367332

过滤器 与拦截器

参考

https://www.cnblogs.com/Black-Ice/p/16248535.html

过滤器 Filter

Filter 基本介绍

过滤器 Filter 是 Sun 公司在 Servlet 2.3 规范中添加的新功能,其作用是对客户端发送给 Servlet 的请求以及对 Servlet 返回给客户端的响应做一些定制化的处理,例如校验请求的参数、设置请求/响应的 Header、修改请求/响应的内容等。

Filter 引入了过滤链(Filter Chain)的概念,一个 Web 应用可以部署多个 Filter,这些 Filter 会组成一种链式结构,客户端的请求在到达 Servlet 之前会一直在这个链上传递,不同的 Filter 负责对请求/响应做不同的处理。 Filter 的处理流程如下图所示:

image

AOP 编程思想

在深入理解 Filter 之前,我们先聊一聊面向切面编程(Aspect Oriented Programming,AOP)。AOP 不是一种具体的技术,而是一种编程思想,它允许我们在不修改源码的基础上实现方法逻辑的增强,也就是在方法执行前后添加一些自定义的处理。

Filter 是 AOP 编程思想的一种体现,其作用可认为是对 Servlet 功能的增强。Filter 可以对用户的请求做预处理,也可以对返回的响应做后处理,且这些处理逻辑与 Servlet 的处理逻辑是分隔开的,这使得程序中各部分业务逻辑之间的耦合度降低,从而提高了程序的可维护性和可扩展性。

创建 Filter

创建 Filter 需要实现 javax.servlet.Filter 接口,或者继承实现了 Filter 接口的父类。Filter 接口中定义了三个方法:

  • init:在 Web 程序启动时被调用,用于初始化 Filter。
  • doFilter:在客户端的请求到达时被调用,doFilter 方法中定义了 Filter 的主要处理逻辑,同时该方法还负责将请求传递给下一个 Filter 或 Servlet。
  • destroy:在 Web 程序关闭时被调用,用于销毁一些资源。

下面我们通过实现 Filter 接口来创建一个自定义的 Filter:

public class TestFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println(filterConfig.getFilterName() + " 被初始化");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;System.out.println("Filter 拦截到了请求: " + request.getRequestURL());System.out.println("Filter 对请求做预处理...");filterChain.doFilter(servletRequest, servletResponse);System.out.println("Filter 修改响应的内容...");}@Overridepublic void destroy() {System.out.println("Filter 被回收");}
}
  • init 方法的 filterConfig 参数封装了当前 Filter 的配置信息,在 Filter 初始化时,我们将 Filter 的名称打印在控制台。
  • doFilter 方法定义了 Filter 拦截到用户请求后的处理逻辑,filterChain.doFilter(servletRequest, servletResponse); 指的是将请求传递给一下个 Filter 或 Servlet,如果不添加该语句,那么请求就不会向后传递,自然也不会被处理。在该语句之后,可以添加对响应的处理逻辑(如果要修改响应的 Header,可直接在该语句之前修改;如果要修改响应的内容,则需要在该语句之后,且需要自定义一个 response)。
  • destroy 方法中,我们输出 "Filter 被回收" 的提示信息。

配置 Filter

Spring 项目中,我们可以使用 @Configuration + @Bean + FilterRegistrationBean 对 Filter 进行配置:

@Configuration
public class FilterConfig {@Beanpublic FilterRegistrationBean<TestFilter> registryFilter() {FilterRegistrationBean<TestFilter> registration = new FilterRegistrationBean<>();registration.setFilter(new TestFilter());registration.addUrlPatterns("/*");registration.setName("TestFilter");registration.setOrder(0);return registration;}
}

上述代码中,setFilter 方法用于设置 Filter 的类型;addUrlPatterns 方法用于设置拦截的规则;setName 方法用于设置 Filter 的名称;setOrder 方法用于设置 Filter 的优先级,数字越小优先级越高。

测试 Filter

接下来,我们定义一个简单的 Web 服务,测试 Filter 是否生效:

@RestController
public class UserController {@RequestMapping(path = "/hello", method = RequestMethod.GET)public String sayHello() {System.out.println("正在处理请求...");System.out.println("请求处理完成~");return "I'm fine, thank you.";}
}

创建 Filter 的其它方式

1. @WebFilter 注解 + 包扫描

除了 FilterRegistrationBean 外,Servlet 3.0 引入的注解 @WebFilter 也可用于配置 Filter。我们只需要在自定义的 Filter 类上添加该注解,就可以设置 Filter 的名称和拦截规则:

实现 Filter 接口

添加 @WebFilter 注解 设置拦截规则

在spring配置类添加 @ServletComponetSca并指定扫描的包

@WebFilter(urlPatterns = "/*", filterName = "TestFilter")
public class TestFilter implements Filter {// 省略部分代码
}

由于@WebFilter 并非 Spring 提供,因此若要使自定义的 Filter 生效,还需在配置类上添加 @ServletComponetScan 注解,并指定扫描的包:

@SpringBootApplication
@ServletComponentScan("com.example.filter")
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}
}

需要注意的是,@WebFilter 注解并不允许我们设置 Filter 的执行顺序,且在 Filter 类上添加 @Order 注解也是无效的。如果项目中有多个被 @WebFilter 修饰的 Filter,那么这些 Filter 的执行顺序由其 "类名的字典序" 决定,例如类名为 "Axx" 的 Filter 的执行顺序要先于类名为 "Bxx" 的 Filter。

添加了 @WebFilter 注解后就不要再添加 @Component 注解了,如果都添加,那么系统会创建两个 Filter。

2. @Component 注解

Spring 项目中,我们可以通过添加 @Component 注解将自定义的 Bean 交给 Spring 容器管理。同样的,对于自定义的 Filter,我们也可以直接添加 @Component 注解使其生效,而且还可以添加 @Order 注解来设置不同 Filter 的执行顺序。

实现 Filter 接口

添加注解@Component 将其注册到 Spring 容器

@Component
@Order(1)
public class TestFilter implements Filter {// 省略部分代码
}

此种配置方式一般不常使用,因为其无法设置 Filter 的拦截规则,默认的拦截路径为 /*

虽然不能配置拦截规则,但我们可以在 doFilter 方法中定义请求的放行规则,例如当请求的 URL 匹配我们设置的规则时,直接将该请求放行,也就是立即执行 filterChain.doFilter(servletRequest, servletResponse);

**3. 继承 OncePerRequestFilter (推荐使用) **

OncePerRequestFilter 是一个由 Spring 提供的抽象类,在项目中,我们可以采用继承 OncePerRequestFilter 的方式创建 Filter,然后重写 doFilterInternal 方法定义 Filter 的处理逻辑,重写 shouldNotFilter 方法设置 Filter 的放行规则。对于多个 Filter 的执行顺序,我们也可以通过添加 @Order 注解进行设置。当然,若要使 Filter 生效,还需添加 @Component 注解将其注册到 Spring 容器。

继承OncePerRequestFilter

添加注解@Component 将其注册到 Spring 容器

@Component
@Order(1)
public class CSpringFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {// 处理逻辑}@Overrideprotected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {// 放行规则}
}

实际上,方式 2 和方式 3 本质上并没有什么区别,因为 OncePerRequestFilter 底层也是通过实现 Filter 接口来达到过滤请求/响应的目的,只不过 Spring 在 OncePerRequestFilter 中帮我们封装了许多功能,因此更推荐采用此种方式创建 Filter。

Filter 的优先级

上文中提到,使用配置类或添加 @Order 注解可以显式的设置 Filter 的执行顺序,修改类名可以隐式的设置 Filter 的执行顺序。如果项目中存在多个 Filter,且这些 Filter 由不同的方式创建,那么它们的执行顺序是怎样的呢?

能够确定的是,Spring 根据 Filter 的 order 决定其优先级,如果我们通过配置类或者通过 @Order 注解设置了 Filter 的 order,那么 order 值越小的 Filter 的优先级越高,无论 Filter 由何种方式创建。如果多个 Filter 的优先级相同,那么执行顺序为:

  1. 配置类中配置的 Filter 优先执行,如果配置类中存在多个 Filter,那么 Spring 按照其在配置类中配置的顺序依次执行。
  2. @WebFilter 注解修饰的 Filter 之后执行,如果存在多个 Filter,那么 Spring 按照其类名的字典序依次执行。
  3. @Component 注解修饰的 Filter 最后执行,如果存在多个 Filter,那么 Spring 按照其类名的字典序依次执行。

注意,以上优先级顺序仅适用于 order 相同的特殊情况。如果我们不配置 Filter 的 order,那么 Spring 默认将其 order 设置为 LOWEST_PRECEDENCE = Integer.MAX_VALUE,也就是最低优先级。由于被 @WebFilter 注解修饰的 Filter 无法显式配置优先级,因此其 order 为 Integer.MAX_VALUE。本文所说的 Filter 的优先级指的是 Filter 对请求做预处理的优先级,对响应做后处理的优先级与之相反。

Filter 的应用场景

Filter 的常见应用场景包括:

  • 解决跨域访问:前后端分离的项目往往存在跨域访问的问题,Filter 允许我们在 response 的 Header 中设置 "Access-Control-Allow-Origin"、"Access-Control-Allow-Methods" 等头域,以此解决跨域失败问题。
  • 设置字符编码:字符编码 Filter 可以在 request 提交到 Servlet 之前或者在 response 返回给客户端之前为请求/响应设置特定的编码格式,以解决请求/响应内容乱码的问题。
  • 记录日志:日志记录 Filter 可以在拦截到请求后,记录请求的 IP、访问的 URL,拦截到响应后记录请求的处理时间。当不需要记录日志时,也可以直接将 Filter 的配置注释掉。
  • 校验权限:Web 服务中,客户端在发送请求时会携带 cookie 或者 token 进行身份认证,权限校验 Filter 可以在 request 提交到 Servlet 之前对 cookie 或 token 进行校验,如果用户未登录或者权限不够,那么 Filter 可以对请求做重定向或返回错误信息。
  • 替换内容:内容替换 Filter 可以对网站的内容进行控制,防止输入/输出非法内容和敏感信息。例如在请求到达 Servlet 之前对请求的内容进行转义,防止 XSS 攻击;在 Servlet 将内容输出到 response 时,使用 response 将内容缓存起来,然后在 Filter 中进行替换,最后再输出到客户浏览器(由于默认的 response 并不能严格的缓存输出内容,因此需要自定义一个具备缓存功能的 response)。

Filter 应用场景的相关内容参考自《Java Web 整合开发之王者归来》,好中二的书名 🤣,关于自定义具备缓存功能的 response 可参考该书的 P175。

Filter继承图

image

过滤器应用

过滤请求参数以及请求体中的数据、 过滤低俗文字、危险字符 、XSS 注入等

参考

https://blog.csdn.net/weixin_40017062/article/details/124507418

使用过滤器获取request中的数据

获取请求路径或者请求体中键值数据

1.自定义ModifyParametersWrapper 继承HttpServletRequestWrapper

image

{//    定义接收的参数列表private Map<String,String[]> params;//    构造方法public ModifyParametersWrapper(HttpServletRequest request) {super(request);
//        接收传入的参数params = request.getParameterMap();log.info("===================请求参数Parameter===============");log.info(JSON.toJSONString(params));}//    获取所有的 key@Overridepublic Enumeration<String> getParameterNames() {Vector<String> vector = new Vector<>(params.keySet());log.info("===================请求参数的Parameter Key============");log.info(JSON.toJSONString(vector));return vector.elements();}//    获取 parameter,但是这个好像获取不到值,@Overridepublic String getParameter(String name) {log.info("===================请求参数getParameter============");log.info(name);String[] results = params.get(name);if (results == null || results.length <= 0)return null;else {System.out.println("修改之前: " + results[0]);return updateParams(results[0]);}}//    遍历获取所有的键@Overridepublic String[] getParameterValues(String name) {String[] results = params.get(name);log.info("===================请求参数values============");log.info(JSON.toJSONString(results));if (results == null || results.length <= 0)return null;else {int length = results.length;for (int i = 0; i < length; i++) {results[i] = updateParams(results[i]);}return results;}}//    自定义方法,为每个键的值都 加 1 包括普通键值和数组private String updateParams(String str){return "1" + str;}}

2.创建ParamFilter继承OncePerRequestFilter并加上@Component标记为Bean交给Spring管理

image

{@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {ModifyParametersWrapper modifyParametersWrapper = new ModifyParametersWrapper(httpServletRequest);filterChain.doFilter(modifyParametersWrapper,httpServletResponse);}
}

获取请求体中Json数据

1.自定义ModifyBodyWrapper类继承HttpServletRequestWrapper

image

{// 存放JSON数据主体private String body;public ModifyBodyWrapper(HttpServletRequest request) {super(request);/* 1、获取Request的body数据 */String requestBody = getBody(request);log.info("requestBody=" + requestBody);/* 2、更改Request的body数据 */JSONObject jsonObject = JSONObject.parseObject(requestBody);Set<Map.Entry<String, Object>> entries = jsonObject.entrySet();for (Map.Entry<String, Object> entry : entries) {log.info("key=" + entry.getKey());log.info("value=" + entry.getValue());if (entry.getValue() instanceof String) {
//                去除value前后空格entry.setValue(((String) entry.getValue()).trim());}}body = jsonObject.toJSONString();}/* 3、重写getInputStream方法,将流返回 */@Overridepublic ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes("UTF-8"));ServletInputStream servletInputStream = new ServletInputStream() {@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener readListener) {}@Overridepublic int read() throws IOException {return byteArrayInputStream.read();}};return servletInputStream;}private String getBody(HttpServletRequest request){String data = null;try {BufferedReader bufferedReader = new BufferedReader(request.getReader());StringBuilder sb = new StringBuilder();String line = null;while ((line = bufferedReader.readLine()) != null) {sb.append(line);}data = sb.toString();} catch (IOException e) {e.printStackTrace();}return data;}}

2.自定义ParamBodyFilter类继承OncePerRequestFilter并标记@Component交给Spring管理

image

{@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {ModifyBodyWrapper modifyBodyWrapper = new ModifyBodyWrapper(httpServletRequest);filterChain.doFilter(modifyBodyWrapper,httpServletResponse);}
}

测试

定义接口

@ResponseBody@RequestMapping("/filter")public String filter(@RequestBody Student student,@RequestParam("arr1") ArrayList<String> arr1,@RequestParam("arr2") String[] arr2,Student stu){log.info("接收ArrayList");log.info(Arrays.toString(arr1.toArray()));log.info("接收数组");log.info(Arrays.toString(arr2));log.info("接收对象");log.info(student.toString());log.info("接收RequestBody");log.info(JSON.toJSONString(student));return JSON.toJSONString(student);}

2.使用apifox发送请求

image

image

拦截器 Interceptor

Interceptor 基本介绍

本文所说的拦截器指的是 Spring MVC 中的拦截器。

拦截器 Interceptor 是 Spring MVC 中的高级组件之一,其作用是拦截用户的请求,并在请求处理前后做一些自定义的处理,如校验权限、记录日志等。这一点和 Filter 非常相似,但不同的是,Filter 在请求到达 Servlet 之前对请求进行拦截,而 Interceptor 则是在请求到达 Controller 之前对请求进行拦截,响应也同理。

与 Filter 一样,Interceptor 也是 AOP 编程思想的体现,且 Interceptor 也具备链式结构,我们在项目中可以配置多个 Interceptor,当请求到达时,每个 Interceptor 根据其声明的顺序依次执行。

创建 Interceptor

创建 Interceptor 需要实现 org.springframework.web.servlet.HandlerInterceptor 接口,HandlerInterceptor 接口中定义了三个方法:

  • preHandle:在 Controller 方法执行前被调用,可以对请求做预处理。该方法的返回值是一个 boolean 变量,只有当返回值为 true 时,程序才会继续向下执行。
  • postHandle:在 Controller 方法执行结束,DispatcherServlet 进行视图渲染之前被调用,该方法内可以操作 Controller 处理后的 ModelAndView 对象。
  • afterCompletion:在整个请求处理完成(包括视图渲染)后被调用,通常用来清理资源。

注意,postHandle 方法和 afterCompletion 方法执行的前提条件是 preHandle 方法的返回值为 true。如果 Controller 抛出异常,那么 postHandle 方法将不会执行,afterCompletion 方法则一定执行,详见 DispatcherServlet 类中的 doDispatch 方法。

下面我们创建一个 Interceptor:

@Component
public class TestInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("Interceptor 拦截到了请求: " + request.getRequestURL());return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("Interceptor 操作 modelAndView...");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("Interceptor 清理资源...");}
}

配置 Interceptor

Interceptor 需要注册到 Spring 容器才能够生效,注册的方法是在配置类中实现 WebMvcConfigurer 接口,并重写 addInterceptors 方法:

@Configuration
public class TestInterceptorConfig implements WebMvcConfigurer {@Autowiredprivate TestInterceptor testInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(testInterceptor).addPathPatterns("/*").excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg").order(1);}
}

上述代码中,addInterceptor 方法用于注册 Interceptor;addPathPatterns 方法用于设置拦截规则;excludePathPatterns 方法用于设置放行规则,order 方法用于设置 Interceptor 的优先级,数字越小优先级越高。

测试 Interceptor

下面我们通过一个简单的 Web 服务,来测试 Interceptor 是否生效:

@RestController
public class UserController {@RequestMapping(path = "/hello", method = RequestMethod.GET)public String sayHello() {System.out.println("正在处理请求...");System.out.println("请求处理完成~");return "I'm fine, thank you.";}
}

启动项目,在浏览器中访问 localhost:8080/hello,请求处理完成后,控制台打印了如下信息:

image

可以看到,Interceptor 成功拦截到了访问 Controller 的 /hello 请求和访问静态资源的 /favicon.ico 请求,并在请求处理前后执行了相应的处理逻辑。

当需要设置多个 Interceptor 时,可以直接在配置类中添加 Interceptor 的配置规则,例如增加 TestInterceptor2:

@Configuration
public class TestInterceptorConfig implements WebMvcConfigurer {@Autowiredprivate TestInterceptor testInterceptor;@Autowiredprivate TestInterceptor2 testInterceptor2;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(testInterceptor).addPathPatterns("/*").excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg").order(1);registry.addInterceptor(testInterceptor2).addPathPatterns("/*").excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg").order(2);}
}

Interceptor 的执行顺序由其配置的 order 决定,order 越小越先执行,注意这里指的是 preHandle 方法的执行顺序,postHandle 和 afterCompletion 的执行顺序与 preHandle 相反

如果我们不配置 order,那么 Spring 默认将 order 设置为 0(可以查看 InterceptorRegistration 类的源码)。如果不同 Interceptor 具有相同的 order,那么其执行顺序为配置类中的注册顺序

Interceptor 的应用场景

Interceptor 的应用场可以参考上文中介绍的 Filter 的应用场景,可以说 Filter 能做到的事 Interceptor 都能做。由于 Filter 在 Servlet 前后起作用,而 Interceptor 可以在 Controller 方法前后起作用,例如操作 Controller 处理后的 ModelAndView,因此 Interceptor 更加灵活,在 Spring 项目中,如果能使用 Interceptor 的话尽量使用 Interceptor。

Filter 和 Interceptor 的区别

Filter 和 Interceptor 都是 AOP 编程思想的提现,且都能实现权限检查、日志记录等功能,但二者也有许多不同之处:

1. 规范不同

Filter 在 Servlet 规范中定义,依赖于 Servlet 容器(如 Tomcat);Interceptor 由 Spring 定义,依赖于 Spring 容器(IoC 容器)。

2. 适用范围不同

Filter 仅可用于 Web 程序,因为其依赖于 Servlet 容器;Interceptor 不仅可以用于 Web 程序,还可以用于 Application、Swing 等程序。

3. 实现原理不同

Filter 是基于函数回调来实现的,Interceptor 则是基于 Java 的反射机制(动态代理)来实现的。

下文中我们重点介绍一下 Filter 的回调机制。

4. 触发时机不同

Filter 在请求进入 Servlet 容器,且到达 Servlet 之前对请求做预处理;在 Servlet 处理完请求后对响应做后处理。

Interceptor 在请求进入 Servlet,且到达 Controller 之前对请求做预处理;在 Controller 处理完请求后对 ModelAndView 做后处理,在视图渲染完成后再做一些收尾工作。

下图展示了二者的触发时机:

image

当 Filter 和 Interceptor 同时存在时,Filter 对请求的预处理要先于 Interceptor 的 preHandle 方法;Filter 对响应的后处理要后于 Interceptor 的 postHandle 方法和 afterCompletion 方法。

关于 Filter 和 Interceptor 的补充说明

1. Filter 的回调机制

在介绍 Filter 的回调机制之前,我们先了解一下回调函数的概念。如果将函数(C++ 中的函数指针,Java 中的匿名函数、方法引用等)作为参数传递给主方法,那么这个函数就称为回调函数,主方法会在某一时刻调用回调函数。

为了便于区分,我们使用 "主方法" 和 "函数" 来分辨主函数和回调函数。

使用回调函数的好处是能够实现函数逻辑的解耦,主方法内可以定义通用的处理逻辑,部分特定的操作则交给回调函数来完成。例如 Java 中 Arrays 类的 sort(T[] a, Comparator<? super T> c) 方法允许我们传入一个比较器来自定义排序规则,这个比较器的 compare 方法就属于回调函数,sort 方法会在排序时调用 compare 方法。

接下来介绍 Filter 的回调机制,上文中提到,我们自定义的 xxFilter 类需要实现 Filter 接口,且需要重写 doFilter 方法:

public class TestFilter implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// ...filterChain.doFilter(servletRequest, servletResponse);}
}

Filter 接口的 doFilter 方法接收一个 FilterChain 类型的参数,这个 FilterChain 对象可认为是传递给 doFilter 方法的回调函数,严格来说应该是这个 FilterChain 对象的 doFilter 方法,注意这里提到了两个 doFilter 方法。Filter 接口的 doFilter 方法在执行结束或执行完某些步骤后会调用 FilterChain 对象的 doFilter 方法,即调用回调函数。

FilterChain 对象的实际类型为 ApplicationFilterChain,其 doFilter() 方法的处理逻辑如下(省略部分代码):

public final class ApplicationFilterChain implements FilterChain {@Overridepublic void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {// ...internalDoFilter(request,response);}private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {if (pos < n) {// 获取第 pos 个 filter, 即 xxFilter  ApplicationFilterConfig filterConfig = filters[pos++];       Filter filter = filterConfig.getFilter();// ...// 调用 xxFilter 的 doFilter 方法filter.doFilter(request, response, this);}}
}

可见,ApplicationFilterChain 的 doFilter 方法首先根据索引查询到我们定义的 xxFilter,然后调用 xxFilter 的 doFilter 方法,在调用时,ApplicationFilterChain 会将自己作为参数传递进去。xxFilter 的 doFilter 方法执行完某些步骤后,会调用回调函数,即 ApplicationFilterChain 的 doFilter 方法,这样 ApplicationFilterChain 就可以获取到下一个 xxFilter,并调用下一个 xxFilter 的 doFilter 方法,如此循环下去,直到所有的 xxFilter 全部被调用。

xxFilter 执行回调函数的过程就像是给了 ApplicationFilterChain 一个通知,即通知 ApplicationFilterChain 可以执行下一个 xxFilter 的处理逻辑了。

2. 在 Filter 和 Interceptor 注入 Bean 的注意事项

有些文章在介绍 Filter 和 Interceptor 的区别时强调 Filter 不能通过 IoC 注入 Bean,如果我们采用本文中的第一种创建 Filter,那么确实不能注入成功:

// 自定义的 Filter, 未添加 @Component 注解
public class TestFilter implements Filter {@Autowiredprivate UserService userService;@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;System.out.println(userService);filterChain.doFilter(servletRequest, servletResponse);}// ...
}// 配置类
@Configuration
public class FilterConfig {@Beanpublic FilterRegistrationBean<TestFilter> registryFilter() {FilterRegistrationBean<TestFilter> registration = new FilterRegistrationBean<>();registration.setFilter(new TestFilter());registration.addUrlPatterns("/*");registration.setName("TestFilter");registration.setOrder(0);return registration;}
}

上述代码执行后,userService 输出为 null,因为注册到 IoC 容器中的是 new 出来的一个 TestFilter 对象(registration.setFilter(new TestFilter());),并不是 Spring 自动装配的。若要使 userService 注入成功,可改为如下写法:

// 自定义的 Filter, 未添加 @Component 注解
@Component
public class TestFilter implements Filter {@Autowiredprivate UserService userService;@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;System.out.println(userService);filterChain.doFilter(servletRequest, servletResponse);}// ...
}// 配置类
@Configuration
public class FilterConfig {@Autowiredprivate TestFilter testFilter;@Beanpublic FilterRegistrationBean<TestFilter> registryFilter() {FilterRegistrationBean<TestFilter> registration = new FilterRegistrationBean<>();registration.setFilter(testFilter);registration.addUrlPatterns("/*");registration.setName("TestFilter");registration.setOrder(0);return registration;}
}

与第一种写法的区别在于,TestFilter 类上添加了 @Component 注解,且配置类中通过 @Autowired 注入 TestFilter 对象。除了使用配置类外,本文介绍的其它几种方式(添加 @Component 注解或 @WebFilter 注解)都可以直接注入 Bean。

所以还是采用继承 OncePerRequestFilter 的方式创建 Filter 比较方便。

另外,使用本文介绍的创建 Interceptor 的写法是可以直接注入 Bean 的,该写法也是先在自定义的 Interceptor 上添加 @Component 注解,然后在配置类中使用 @Autowired 注入自定义的 Interceptor。

3. Interceptor 拦截静态请求

有文章提到 Interceptor 不能拦截静态请求,其实在 Spring 1.x 的版本中确实是这样的,但 Spring 2.x 对静态资源也进行了拦截,例如上文中我们在测试 TestInterceptor 是否生效时,发现其拦截到了 /favicon.ico 请求,该请求是一个由浏览器自动发送的静态请求。

过滤器、拦截器、切面(AOP),及其之间的区别和执行顺序

https://blog.csdn.net/zzwpublic/article/details/111571569

首先了解一下SpringMVC的执行流程

1.用户发起请求到前端控制器(Controller)
2.前端控制器没有处理业务逻辑的能力,需要找到具体的模型对象处理(Handler),到处理器映射器(HandlerMapping)中查找Handler对象(Model)。
3.HandlerMapping返回执行链,包含了2部分内容: ① Handler对象、② 拦截器数组
4.前端处理器通过处理器适配器包装后执行Handler对象。
5.处理业务逻辑。
6.Handler处理完业务逻辑,返回ModelAndView对象,其中view是视图名称,不是真正的视图对象。
7.将ModelAndView返回给前端控制器。
8.视图解析器(ViewResolver)返回真正的视图对象(View)。
9.(此时前端控制器中既有视图又有Model对象数据)前端控制器根据模型数据和视图对象,进行视图渲染。
10.返回渲染后的视图(html/json/xml)返回。
11.给用户产生响应。

核心就是DispatcherServlet核心控制器,我们看源码可知道DispatcherServlet是Servlet的子类

下面用一张图说一下过滤器、Servlet容器、拦截器、AOP、Controller之间的关系

image

然后具体执行流程如下:
image

spring项目推荐使用 拦截器 Interceptor

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/783820.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

独立站是什么?独立站的优势是什么?为什么要做独立站?一键三问

独立站是指一个完全独立的网站,由公司自主搭建和运营,包括独立的服务器、网站程序和单独的域名等等,完全不依赖于任何第三方平台。它最初用于区分与亚马逊、eBay、速卖通等各种第三方电商平台的区别。市面上有三种建站系统:全自主开发、基于开源软件建设的独立站和基于SaaS…

AP9196 DC-DC 输入3-40V 6A升压恒流电源管理芯 太阳能路灯方案

产品说明 AP9196 是一系列外围电路简洁的宽调光比升压调光恒流驱动器,适用于3-40V输入电压范围的LED照明领域。 AP9196 采用我司专利算法,可以实现高精度的恒流效果,输出电流恒流精度≤3%,电压工作范围为5-40V,可以轻松满足锂电池及中低压的应用需求,输出耐压仅由MOS 耐…

Camera MIPI 协议理解

D-PHY 1、传输模式 1.LP(Low-Power) 模式:用于传输控制信号,最高速率 10 MHz HS(High-Speed)模式:用于高速传输数据,速率范围 [80 Mbps, 1Gbps] per Lane 传输的最小单元为 1 个字节,采用小端(低位字节放到内存的低地址端,高位字节放到内存的高地址端)的方式及 LS…

nvm---安装

安装流程:https://blog.csdn.net/qq_22182989/article/details/125387145 第一步:下载安装 nvm

易优CMS网站prenext 获取上一篇、下一篇内容

【基础用法】 名称:prenext 功能:获取当前文档上一篇、下一篇内容。 语法: {eyou:prenext get=pre} 上一篇:{$field.title}{eyou:else /} 上一篇:暂无{/eyou:prenext} {eyou:prenext get=next} 下一篇:{$field.title}{eyou:else /} 下一篇:暂无{/eyou:pre…

易优CMS网站likearticle 功能:通过前3个TAG标签或前3个关键词,检索整站文档标题中含有tag标签或者关键词的相关文档,进行关联

likearticle 相关文档 [基础用法] 名称:likearticle 功能:通过前3个TAG标签或前3个关键词,检索整站文档标题中含有tag标签或者关键词的相关文档,进行关联。在没有tag标签情况下,就以前3个关键词检索文档标题进行关联。这个标签随着数据量的增加可能会比较影响检索性能。 …

vue 数组和对象更新检测

vue如果要更新v-for渲染出来的数据,它是不会操作dom元素的。 而是就地更新需要操作的元素,并且确保它们在每个索引位置正确渲染。为了给vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有的元素,你需要为每项 提供一个唯一Key attribute;<div v-for=&quo…

易优CMS插件route.php路由配置

插件route.php路由配置 只针对网站前台进行路由配置,全面支持TP5.0.10本身的路由规则扫码添加技术【解决问题】专注中小企业网站建设、网站安全12年。熟悉各种CMS,精通PHP+MYSQL、HTML5、CSS3、Javascript等。承接:企业仿站、网站修改、网站改版、BUG修复、问题处理、二次开…

定位遇阻?合宙模组GNSS排障宝典01

使用合宙GNSS定位模组时,总有客户因为各种原因遇到无法定位的情况。本文总结了无法定位最常见的四种情况,希望能帮到有类似定位应用项目的朋友们,更快地排查出问题所在。使用合宙GNSS定位模组时,总有客户因为各种原因遇到无法定位的情况。 本文总结了无法定位最常见的四种情…

python入门教程(非常详细!3w+ 文字)

先序: 学习编程语言要先学个轮廓,刚开始只用学核心的部分,一些细节、不常用的内容先放着,现用现查即可;把常用的东西弄熟练了在慢慢补充。 1、 安装 Python 解释器 为什么需要安装 Python Python 语言本身是由解释器执行的,因此你需要在你的计算机上安装 Python 解释器。这…

cnetos 9 安装巨坑!!! ssh无法登录

不管任何软件登录 或任何形式的ssh登录 仅开启了密钥的登录 没有账号密码具体步骤:找到合适的插入位置:在 /etc/ssh/sshd_config 文件中查找类似以下的段落,然后在附近添加新配置:# Authentication: #PermitRootLogin prohibit-password #PasswordAuthentication no添加或修…