Spring Boot2.x-12 Spring Boot2.1.2中Filter和Interceptor 的使用

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Spring Boot2.x-12 Spring Boot2.1.2中Filter和Interceptor 的使用

Interceptor 拦截器


所有的拦截器都需要实现 HandIerInterceptor 接口

HandlerInterceptor 源码

public interface HandlerInterceptor {
  // 处理器执行前方法
  default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {
    return true;
  }
  // 处理器处理后方法
  default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
      @Nullable ModelAndView modelAndView) throws Exception {
  }
  // 处理器完成后方法
  default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
      @Nullable Exception ex) throws Exception {
  }
}


Interceptor依赖于web框架,我们经常在Spring MVC中用到该配置,在这个场景下Interceptor 就依赖于SpringMVC框架。


Interceptor 基于Java的反射机制,属于AOP的一种运用


优点:


由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用

缺点:


只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。


拦截器中方法的执行流程


20190213170837518.png


请求到达 DispatcherServlet,DispatcherServlet 发送至 Interceptor ,执行 preHandle 方法,该方法会返回一个布尔值。如果为 false ,则结束所有流程:如果为 true , 则执行下一步。

请求达到 Controller,执行处理器逻辑,它包含控制器的功能 。

执行 postHandle 方法。

执行视图解析和视图渲染 。

执行 afterComp letion 方法。


传统项目拦截器的配置


基于Spring MVC的项目 ,我们之前的案例配置拦截器的方式如下:


20190213211303817.png

拦截器的开发还是一样的没有变化,那如何注册和实例化拦截器呢? 上面是通过xml的方式来加载的 ,那基于Spring Boot的呢?


Spring Boot2.1.2整合拦截器Interceptor 示例

20190213221948185.png

pom.xml仅需添加 spring-boot-starter-web即可

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>


Step1 实现HandlerInterceptor接口编写拦截器

继承HandlerInterceptorAdapter也可以

package com.artisan.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
 * 实现 Handlerlnterceptor接口,覆盖其对应的方法即完成了拦截器的开发
 * 
 * @author yangshangwei
 *
 */
public class MyInterceptor implements HandlerInterceptor {
  /**
   * preHandle在执行Controller之前执行 
   * 返回true:继续执行处理器逻辑,包含Controller的功能 
   * 返回false:中断请求
   * 
   * 处理器执行前方法
   */
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {
    System.out.println("MyInterceptor-处理器执行前方法preHandle,返回true则不拦截后续的处理");
    return true;
  }
  /**
   * postHandle在请求执行完之后 渲染ModelAndView返回之前执行
   * 
   * 处理器处理后方法
   */
  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
      ModelAndView modelAndView) throws Exception {
    HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    System.out.println("MyInterceptor-处理器处理后方法postHandle");
  }
  /**
   * afterCompletion在整个请求执行完毕后执行,无论是否发生异常都会执行
   * 
   * 处理器完成后方法
   */
  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
      throws Exception {
    HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    System.out.println("MyInterceptor-处理器完成后方法afterCompletion");
  }
}



Step2 实现WebMvcConfigurer接口注册拦截器

先说明下几个可以用来注册拦截器的类和接口

  • WebMvcConfigurerAdapter: 2.0以后的版本已失效
  • WebMvcConfigurationSupport: 不需要返回逻辑视图,可以选择继承此类
  • WebMvcConfigurer:返回逻辑视图,可以选择实现此方法,重写addInterceptor方法

我们这里选择使用实现WebMvcConfigurer接口

package com.artisan.conf;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.artisan.interceptor.MyInterceptor;
/**
 * 实现 WebMvcConfigurer 接 口, 最后覆盖其addInterceptors方法进行注册拦截器
 * @author yangshangwei
 *
 */
// 标注为配置类
@Configuration 
public class WebConfig implements WebMvcConfigurer {
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    //  注册拦截器到 Spring MVC 机制, 然后 它会返 回 一个拦截器注册
    InterceptorRegistration regist =  registry.addInterceptor(new MyInterceptor());
    // 指定拦截匹配模式,限制拦截器拦截请求
    regist.addPathPatterns("/artisan/interceptor/*");
    // 不拦截的路径
    regist.excludePathPatterns("/artisan/interceptor/exclude/*");
  }
}


注意设置的规则,下面测试的时候需要对应上


Step3 验证

package com.artisan.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/artisan")
public class ArtisanController {
  @GetMapping("/interceptor/test")
  public String testInterceptor() {
    System.out.println("执行处理器逻辑testInterceptor....");
    return "请观察控制台中拦截器的日志输出";
  }
  @GetMapping("/interceptor/exclude/test")
  public String testExcludeInterceptor() {
    System.out.println("执行处理器逻辑testExcludeInterceptor....");
    return "exclude排除,不会被拦截器拦截";
  }
}


启动Spring Boot工程,访问

http://localhost:8080/artisan/interceptor/test 因为匹配到了拦截的规则,所以会被拦截


日志如下


2019021322323839.png

访问 http://localhost:8080/artisan/interceptor/exclude/test 因为匹配到了exclude的规则,所以不会被拦截。 日志中没有拦截器信息的输出

20190213223215474.png


多个拦截器的执行顺序

实际的开发中,多个拦截器是很正常的一个事儿,那么他们的执行顺序又是怎样的呢?


20190213225425813.png


再新建两个拦截器MyInterceptor2和MyInterceptor3,为了验证下执行顺序,不搞的太复杂,代码和MyInterceptor一样,仅仅方法中的输出为了区分改成了对应的类名,如下所示

20190213225738902.png


这3个拦截器的preHandle方法都返回true的情况下

先注册下拦截器

20190213225843244.png


启动工程,访问 http://localhost:8080/artisan/interceptor/test

日志输出


20190213230011608.png


上述结果是责任链模式的规则,对于处理器前方法采用先注册先执行,而处理器后方法和完成方法则是先注册后执行的规则

如果将第二个拦截器MyInterceptor2 的 preHandle 方法修改返回为 false 呢?

测试下,日志输出如下


20190213230334156.png


可以看出处理器前( preHandle )方法会执行,但是一旦返回 false ,则后续的拦截器、 处理器和所有拦截器的处理器后( postHandle ) 方法都不会被执行 。 完成方法 afterCompletion则不一样,它只会执行返回 true 的拦截器的完成方法,而且顺序是先注册后执行 。


Filter 过滤器


在开发传统的Spring项目时web.xml中配置的编码过滤器不知道你还记不记得?

比如这篇很久前写的这个基于Spring的SSM整合文章SSM-Spring+SpringMVC+MyBatis整合案例从0到1 中为了避免编码不一致增加了编码过滤器配置


20190212235011441.png


既然是配置在web.xml中,那肯定是依赖于servlet容器.


优点:


在实现上Filter是基于函数回调,可以对几乎所有请求进行过滤

缺点:


一个过滤器实例只能在容器初始化时调用一次 . 当容器第一次加载该过滤器时,init() 方法将被调用

使用场景:


比如设置编码、过滤敏感词汇、禁止浏览器缓存所有动态页面、实现用户自动登陆、实现URL级别的权限认证等等 ,具体案例参考Filter(过滤器)常见应用

传统的JavaEE项目开发filter的主要2个步骤


实现Filter接口,并实现其doFilter方法。

在 web.xml 文件中使用<filter>和<filter-mapping>元素对编写的filter类进行注册,并设置它所能拦截的资源

可以开发编写多个Filter,组成一个Filter链,根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter


Spring Boot中整合过滤器Filter的两种方式


2019021400043125.png


方式一 FilterRegistrationBean注册

Step1 实现Filter接口 开发过滤器

package com.artisan.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HttpFilter implements Filter {
  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    System.out.println("HttpFilter  init");
    Filter.super.init(filterConfig);
  }
  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    System.out.println("HttpFilter  doFilter begin");
    HttpServletRequest req =(HttpServletRequest) request;
    HttpServletResponse res =(HttpServletResponse) response;
    System.out.println("HttpFilter name:" + request.getParameter("name"));
    // 将请求转发给过滤器链上下一个对象。这里的下一个指的是下一个filter,如果没有filter那就是请求的资源。
    chain.doFilter(request, response);
    System.out.println("HttpFilter  doFilter end");
  }
  @Override
  public void destroy() {
    System.out.println("HttpFilter  destroy");
    Filter.super.destroy();
  }
}


Step2 在配置类中注册该过滤器

package com.artisan.conf;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.artisan.filter.HttpFilter;
@Configuration
public class FilterConfig {
  @Bean
  public FilterRegistrationBean<HttpFilter> httpFilter(){
    FilterRegistrationBean<HttpFilter> filterRegistrationBean = new FilterRegistrationBean<HttpFilter>();
    // 设置filter
    filterRegistrationBean.setFilter(new HttpFilter());
    // 拦截规则
    filterRegistrationBean.addUrlPatterns("/*");
    return filterRegistrationBean;
  }
}


Step3 启动项目 测试

观察启动日志

20190214000836527.png


然后访问 http://localhost:8080/artisan/interceptor/test?name=artisan


20190214000956881.png


方式二 @WebServlet+ @ServletComponentScan注解的方式

Step1 @WebFilter注解开发过滤器

@WebFilter 用于将一个类声明为过滤器,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器

package com.artisan.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebFilter(filterName = "HttpFilter2", urlPatterns = "/*")
public class HttpFilter2 implements Filter {
  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    System.out.println("HttpFilter2  init");
    Filter.super.init(filterConfig);
  }
  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    System.out.println("HttpFilter2  doFilter begin");
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;
    System.out.println("HttpFilter2 name:" + request.getParameter("name"));
    // 将请求转发给过滤器链上下一个对象。这里的下一个指的是下一个filter,如果没有filter那就是请求的资源。
    chain.doFilter(request, response);
    System.out.println("HttpFilter2  doFilter end");
  }
  @Override
  public void destroy() {
    System.out.println("HttpFilter2  destroy");
    Filter.super.destroy();
  }
}


Step2 启动类增加@ServletComponentScan注解


在 SpringBootApplication 上使用@ServletComponentScan 注解后,Servlet、Filter、Listener 可以直接通过 @WebServlet、@WebFilter、@WebListener 注解自动注册


20190214001336709.png


Step3 启动测试

观察启动日志


20190214001404918.png


访问 http://localhost:8080/artisan/interceptor/test?name=artisan


20190214001452909.png

注意下filter的执行在interceptor之前


小结


Filter的执行顺序在Interceptor之前 。 拦截器(Interceptor)是基于Java的反射机制,而过滤器(Filter)是基于函数回调。从灵活性上说拦截器功能更强大些,Filter能做的事情,Interceptor都能做,而且可以在请求前,请求后执行,比较灵活。


20190214001924306.png


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
21天前
|
监控 Java 应用服务中间件
SpringBoot是如何简化Spring开发的,以及SpringBoot的特性以及源码分析
Spring Boot 通过简化配置、自动配置和嵌入式服务器等特性,大大简化了 Spring 应用的开发过程。它通过提供一系列 `starter` 依赖和开箱即用的默认配置,使开发者能够更专注于业务逻辑而非繁琐的配置。Spring Boot 的自动配置机制和强大的 Actuator 功能进一步提升了开发效率和应用的可维护性。通过对其源码的分析,可以更深入地理解其内部工作机制,从而更好地利用其特性进行开发。
42 6
|
1月前
|
缓存 安全 Java
Spring Boot 3 集成 Spring Security + JWT
本文详细介绍了如何使用Spring Boot 3和Spring Security集成JWT,实现前后端分离的安全认证概述了从入门到引入数据库,再到使用JWT的完整流程。列举了项目中用到的关键依赖,如MyBatis-Plus、Hutool等。简要提及了系统配置表、部门表、字典表等表结构。使用Hutool-jwt工具类进行JWT校验。配置忽略路径、禁用CSRF、添加JWT校验过滤器等。实现登录接口,返回token等信息。
343 12
|
1月前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
81 8
|
2月前
|
缓存 前端开发 Java
【Spring】——SpringBoot项目创建
SpringBoot项目创建,SpringBootApplication启动类,target文件,web服务器,tomcat,访问服务器
|
2月前
|
负载均衡 Java 开发者
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
206 5
|
27天前
|
XML Java 应用服务中间件
Spring Boot 两种部署到服务器的方式
本文介绍了Spring Boot项目的两种部署方式:jar包和war包。Jar包方式使用内置Tomcat,只需配置JDK 1.8及以上环境,通过`nohup java -jar`命令后台运行,并开放服务器端口即可访问。War包则需将项目打包后放入外部Tomcat的webapps目录,修改启动类继承`SpringBootServletInitializer`并调整pom.xml中的打包类型为war,最后启动Tomcat访问应用。两者各有优劣,jar包更简单便捷,而war包适合传统部署场景。需要注意的是,war包部署时,内置Tomcat的端口配置不会生效。
200 17
Spring Boot 两种部署到服务器的方式
|
27天前
|
Dart 前端开发 JavaScript
springboot自动配置原理
Spring Boot 自动配置原理:通过 `@EnableAutoConfiguration` 开启自动配置,扫描 `META-INF/spring.factories` 下的配置类,省去手动编写配置文件。使用 `@ConditionalXXX` 注解判断配置类是否生效,导入对应的 starter 后自动配置生效。通过 `@EnableConfigurationProperties` 加载配置属性,默认值与配置文件中的值结合使用。总结来说,Spring Boot 通过这些机制简化了开发配置流程,提升了开发效率。
59 17
springboot自动配置原理
|
4月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
354 2
|
1月前
|
XML JavaScript Java
SpringBoot集成Shiro权限+Jwt认证
本文主要描述如何快速基于SpringBoot 2.5.X版本集成Shiro+JWT框架,让大家快速实现无状态登陆和接口权限认证主体框架,具体业务细节未实现,大家按照实际项目补充。
80 11
|
2月前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)