Servlet-Filter-And-Spring-Interceptor

一. Filter 过滤器

1. 概括

过滤器,顾名思义就是对事物进行过滤的,在 Web 中的过滤器,当然就是对请求进行过滤,我们使用过滤器,就可以对请求进行拦截,然后做相应的处理,实现许多特殊功能。如登录控制,权限管理,过滤敏感词汇等,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。

2. 原理

当我们使用过滤器时,过滤器会对浏览器的请求进行过滤,过滤器可以动态的分为 3 个部分,1.放行之前的代码,2.放行,3.放行后的代码,这 3 个部分分别会发挥不同作用。

  1. 第一部分代码会对浏览器请求进行第一次过滤,然后继续执行
  2. 第二部分代码就是将游览器请求放行,如果还有过滤器,那么就继续交给下一个过滤器
  3. 第三部分代码就是对返回的 Web 资源再次进行过滤处理
    我们使用过滤器,也就是说,不止请求会经过过滤器,我们的响应也会经过过滤器。

20241108215139

3. api

定义过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//定义一个类,实现一个标准的Filter过滤器的接口
public class DemoFilter implements Filter {
@Override //初始化方法, 只调用一次
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init 初始化方法执行了");
}

@Override //拦截到请求之后调用, 调用多次
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("Demo 拦截到了请求...放行前逻辑");
//放行
chain.doFilter(request,response);
}

@Override //销毁方法, 只调用一次
public void destroy() {
System.out.println("destroy 销毁方法执行了");
}
}
  1. init 方法:过滤器的初始化方法。在 web 服务器启动的时候会自动的创建 Filter 过滤器对象,在创建过滤器对象的时候会自动调用 init 初始化方法,这个方法只会被调用一次。
  2. doFilter 方法:这个方法是在每一次拦截到请求之后都会被调用,所以这个方法是会被调用多次的,每拦截到一次请求就会调用一次 doFilter()方法。
  3. destroy 方法: 是销毁的方法。当我们关闭服务器的时候,它会自动的调用销毁方法 destroy,而这个销毁方法也只会被调用一次。

在过滤器 Filter 中,如果不执行放行操作,将无法访问后面的资源。 放行操作:chain.doFilter(request, response);

在定义完 Filter 之后,Filter 其实并不会生效,还需要完成 Filter 的配置,Filter 的配置非常简单,只需要在 Filter类上添加一个注解:@WebFilter,并指定属性 urlPatterns,通过这个属性指定过滤器要拦截哪些请求。

1
@WebFilter(urlPatterns = "/*") //配置过滤器要拦截的请求路径( /* 表示拦截浏览器的所有请求 )

当我们在 Filter 类上面加了@WebFilter 注解之后,接下来我们还需要在启动类上面加上一个注解@ServletComponentScan,通过这个@ServletComponentScan 注解来开启 SpringBoot 项目对于 Servlet 组件的支持。

过滤器并不会管资源是否存在,而只会对配置的拦截路径进行拦截。拦截不仅会对请求进行拦截,而且还会对响应进行拦截。

配置过滤器(Filter)拦截路径

1.xml 方式

xml 方式可以说是和 Servlet 使用 xml 配置方式一样了。

1
2
3
4
5
6
7
8
9
<filter>
<filter-name>myFilter</filter-name>
<!-- <筛选器名称>myFilter</筛选器名称> -->
<filter-class>com.clucky.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

这个就是 xml 配置方式,只不过把注解换成了 xml 标签来配置,里面属性都是一样的,这个和 Servlet 的配置方式基本一样,这里就不再赘述了。

2.注解方式

使用 @WebFilter,里面的多个参数用 ,进行分隔。

参数说明
filterName该 filter 的名字
initParams初始化参数
displayNamefilter 显示名称
servletNames指定对哪些 servlet 进行过滤
asyncSupported是否支持异步模式
urlPatterns指定拦截路径
value指定拦截路径

注意:urlPatterns 和 value 是一样的。urlPatterns 和 value 只能配置一个,不能两个都配置,两个都配置就会报错。

4. 生命流程

a.执行流程

Filter 的执行流程主要包括 初始化请求处理响应处理销毁这四个阶段。放行操作,放行就是调用 FilterChain 对象当中的 doFilter()方法,在调用 doFilter()这个方法之前所编写的代码属于放行之前的逻辑。在放行后访问完 web 资源之后还会回到过滤器当中,回到过滤器之后如有需求还可以执行放行之后的逻辑,放行之后的逻辑我们写在 doFilter()这行代码之后。

b.拦截路径

拦截路径urlPatterns 值含义
拦截具体路径/login只有访问/login 路径是,才会被拦截
目录拦截/emps/*访问/emps 下的所有资源,都会被拦截
拦截所有/*访问所有资源,都会被拦截

c.过滤器链

所谓过滤器链指的是在一个 web 应用程序当中,可以配置多个过滤器,多个过滤器就形成了一个过滤器链。以注解方式配置的 Filter 过滤器,它的执行优先级是按时过滤器类名的自动排序确定的,类名排名越靠前,优先级越高

20241108220149

它用于将请求传递给过滤器链中的下一个过滤器或目标资源(如 Servlet)。在 Filter 的实现类中,通常会重写 doFilter方法来处理请求和响应。当一个过滤器完成其处理后,它会调用 doFilter方法,并将控制权传递给下一个过滤器。如果没有其他过滤器需要处理该请求,那么控制权将传递给目标资源。

5. 登录校验-Filter

登录校验过滤器:LoginCheckFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@Slf4j
@WebFilter(urlPatterns = "/*") //拦截所有请求
public class LoginCheckFilter implements Filter {

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
//前置:强制转换为http协议的请求对象、响应对象 (转换原因:要使用子类中特有方法)
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;

//1.获取请求url
String url = request.getRequestURL().toString();
log.info("请求路径:{}", url); //请求路径:http://localhost:8080/login


//2.判断请求url中是否包含login,如果包含,说明是登录操作,放行
if(url.contains("/login")){
chain.doFilter(request, response);//放行请求
return;//结束当前方法的执行
}


//3.获取请求头中的令牌(token)
String token = request.getHeader("token");
log.info("从请求头中获取的令牌:{}",token);


//4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
if(!StringUtils.hasLength(token)){
log.info("Token不存在");

Result responseResult = Result.error("NOT_LOGIN");
//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
String json = JSONObject.toJSONString(responseResult);
response.setContentType("application/json;charset=utf-8");
//响应
response.getWriter().write(json);

return;
}

//5.解析token,如果解析失败,返回错误结果(未登录)
try {
JwtUtils.parseJWT(token);
}catch (Exception e){
log.info("令牌解析失败!");

Result responseResult = Result.error("NOT_LOGIN");
//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
String json = JSONObject.toJSONString(responseResult);
response.setContentType("application/json;charset=utf-8");
//响应
response.getWriter().write(json);

return;
}


//6.放行
chain.doFilter(request, response);

}
}

二. Interceptor 拦截器

1. 概述

拦截器(Interceptor)是一种特殊的组件,它可以在请求处理的过程中对请求和响应进行拦截和处理。拦截器可以在请求到达目标处理器之前、处理器处理请求之后以及视图渲染之前执行特定的操作。拦截器的主要目的是在不修改原有代码的情况下,实现对请求和响应的统一处理。(统⼀拦截所有的请求, 并进⾏ Session 校验)
拦截器可以用于实现以下功能:

  1. 权限控制:拦截器可以在请求到达处理器之前进行权限验证,从而实现对不同用户的访问控制。
  2. 日志记录:拦截器可以在请求处理过程中记录请求和响应的详细信息,便于后期分析和调试。
  3. 接口幂等性校验:拦截器可以在请求到达处理器之前进行幂等性校验,防止重复提交。
  4. 数据校验:拦截器可以在请求到达处理器之前对请求数据进行校验,确保数据的合法性。
  5. 缓存处理:拦截器可以在请求处理之后对响应数据进行缓存,提高系统性能。

2. 拦截器与过滤器的区别

拦截器和过滤器都可以实现对请求和响应的拦截和处理,但它们之间存在以下区别:

  1. 执行顺序:过滤器在拦截器之前执行,拦截器在处理器之前执行。
  2. 功能范围:过滤器可以对所有请求进行拦截,而拦截器只能对特定的请求进行拦截。
  3. 生命周期:过滤器由 Servlet 容器管理,拦截器由 Spring 容器管理。
  4. 使用场景:过滤器适用于对请求和响应的全局处理,拦截器适用于对特定请求的处理。

20241108223652

3. api

a. 实现 HandlerInterceptor 接口

要在 SpringBoot 中实现拦截器,首先需要创建一个类并实现 HandlerInterceptor 接口。HandlerInterceptor 接口包含以下三个方法:

方法作用
preHandle在请求到达处理器之前执行,可以用于权限验证、数据校验等操作。如果返回 true,则继续执行后续操作;如果返回 false,则中断请求处理。
postHandle在处理器处理请求之后执行,可以用于日志记录、缓存处理等操作。
afterCompletion在视图渲染之后执行,可以用于资源清理等操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println("preHandle: " + request.getRequestURI());
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
System.out.println("postHandle: " + request.getRequestURI());
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("afterCompletion: " + request.getRequestURI());
}
}

b, 注册拦截器到 InterceptorRegistry

要让拦截器生效,需要将其注册到 InterceptorRegistry 中。这可以通过实现 WebMvcConfigurer 接口并重写 addInterceptors 方法来实现。以下是一个简单的注册示例:

1
2
3
4
5
6
7
8
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor());
}
}

c. 配置拦截器的拦截规则

在注册拦截器时,可以通过 addPathPatterns 和 excludePathPatterns 方法来配置拦截器的拦截规则。addPathPatterns 方法用于指定需要拦截的请求路径,excludePathPatterns 方法用于指定不需要拦截的请求路径。以下是一个配置示例:

1
2
3
4
5
6
7
8
9
10
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/login", "/register");
}
}

4. 执行顺序

当有多个拦截器时,它们的执行顺序取决于注册顺序。先注册的拦截器先执行,后注册的拦截器后执行。在请求处理过程中,拦截器的 preHandle 方法 按注册顺序执行,而 postHandle 和 afterCompletion 方法 按注册顺序的逆序执行

当有多个拦截器时,它们的执行流程如下:

  1. 执行所有拦截器的 preHandle 方法,按注册顺序执行。如果某个拦截器的 preHandle 方法返回 false,则中断请求处理,直接执行已执行拦截器的 afterCompletion 方法。
  2. 执行处理器的处理方法。
  3. 执行所有拦截器的 postHandle 方法,按注册顺序的逆序执行。
  4. 渲染视图。
  5. 执行所有拦截器的 afterCompletion 方法,按注册顺序的逆序执行。

5. 生命周期

拦截器的生命周期由 Spring 容器管理。当 Spring 容器启动时,拦截器会被实例化并初始化;当 Spring 容器关闭时,拦截器会被销毁。

6. 高级应用

a. 权限控制

拦截器可以在请求到达处理器之前进行权限验证,从而实现对不同用户的访问控制。以下是一个简单的权限控制示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class AuthInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
HttpSession session = request.getSession();
User user = (User) session.getAttribute("user");
if (user == null) {
response.sendRedirect("/login");
return false;
}
return true;
}
}

在上述示例中,我们在 preHandle 方法中检查用户是否已登录,如果未登录,则重定向到登录页面并中断请求处理。

b. 日志记录

拦截器可以在请求处理过程中记录请求和响应的详细信息,便于后期分析和调试。以下是一个简单的日志记录示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class LogInterceptor implements HandlerInterceptor {

private static final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
logger.info("Request URI: {}", request.getRequestURI());
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
logger.info("Response status: {}", response.getStatus());
}
}

在上述示例中,我们在 preHandle 方法中记录请求 URI,在 postHandle 方法中记录响应状态。

接口幂等性校验

拦截器可以在请求到达处理器之前进行幂等性校验,防止重复提交。以下是一个简单的幂等性校验示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class IdempotentInterceptor implements HandlerInterceptor {

private static final String IDEMPOTENT_TOKEN = "idempotentToken";

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader(IDEMPOTENT_TOKEN);
if (StringUtils.isEmpty(token)) {
throw new RuntimeException("Idempotent token is missing");
}
if (!checkIdempotentToken(token)) {
throw new RuntimeException("Duplicate request");
}
return true;
}

private boolean checkIdempotentToken(String token) {
// Check the token in the cache or database
// Return true if the token is valid, false otherwise
}
}

在上述示例中,我们在 preHandle 方法中检查请求头中的幂等性令牌,如果令牌无效,则抛出异常并中断请求处理。

7. 优化

A. 性能优化策略

拦截器在请求处理过程中可能会影响系统性能,以下是一些性能优化策略:

  1. 减少拦截器数量:尽量将相关功能集中到一个拦截器中,避免创建过多的拦截器。
  2. 精确配置拦截规则:通过 addPathPatterns 和 excludePathPatterns 方法精确配置拦截规则,避免不必要的拦截。
  3. 使用异步处理:在拦截器中使用异步处理,避免阻塞请求处理过程。
  4. 使用缓存:在拦截器中使用缓存,减少对数据库或其他资源的访问。

B. 拦截器的常见问题和解决方案

拦截器是一种用于处理请求和响应的中间件,它可以在请求到达目标处理器之前或响应返回客户端之前执行一些操作。然而,在实际使用过程中,我们可能会遇到一些问题,如拦截器不生效、执行顺序错误或影响性能等。接下来,我们将逐一分析这些问题的原因及解决方法。

  1. 拦截器不生效:拦截器不生效的可能原因有很多,其中最常见的包括拦截器未注册到 InterceptorRegistry、拦截规则配置错误等。为了解决这个问题,我们需要首先检查拦截器是否已经正确注册到 InterceptorRegistry 中,然后再检查拦截规则是否配置正确。如果发现问题,需要及时进行调整和修复。
  2. 拦截器执行顺序错误:拦截器执行顺序错误的主要原因是拦截器的注册顺序错误。在实际应用中,拦截器的执行顺序是根据它们在 InterceptorRegistry 中的注册顺序来决定的。因此,为了解决这个问题,我们需要调整拦截器在 InterceptorRegistry 中的注册顺序,确保它们按照预期的顺序执行。
  3. 拦截器影响性能:拦截器影响性能的主要原因是拦截器中的处理逻辑过于复杂或资源消耗过大。为了解决这个问题,我们需要对拦截器的处理逻辑进行优化,尽量减少不必要的计算和资源消耗。同时,我们还可以考虑使用一些性能监控工具,如 JProfiler 等,来对拦截器的性能进行实时监控和分析,从而找到性能瓶颈并进行优化。