---
category:
  - Java企业级开发
tag:
  - Spring Boot
title: 过滤器、拦截器、监听器
---

## 前言

先说作用。

- 过滤器（Filter）：当有一堆请求，只希望符合预期的请求进来。
- 拦截器（Interceptor）：想要干涉预期的请求。
- 监听器（Listener）：想要监听这些请求具体做了什么。

再说区别。

过滤器是在请求进入容器后，但还没有进入 Servlet 之前进行预处理的。如下图所示。

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/Filter-Interceptor-Listener-2a28621b-2cc1-4d29-87a1-cf6a01d95443.png)

拦截器是在请求进入控制器（Controller） 之前进行预处理的。

虚线内就是过滤器和拦截器的作用范围：


![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/Filter-Interceptor-Listener-dd48851b-c123-4fd8-b82d-9ae87b33745d.png)


过滤器依赖于 Servlet 容器，而拦截器依赖于 Spring 的 IoC 容器，因此可以通过注入的方式获取容器当中的对象。

监听器用于监听 Web 应用中某些对象的创建、销毁、增加、修改、删除等动作，然后做出相应的处理。

## 过滤器

- 过滤敏感词汇（防止sql注入）
- 设置字符编码
- URL级别的权限访问控制
- 压缩响应信息

过滤器的创建和销毁都由 Web 服务器负责，Web 应用程序启动的时候，创建过滤器对象，为后续的请求过滤做好准备。

过滤器可以有很多个，一个个过滤器组合起来就成了 FilterChain，也就是过滤器链。



在 Spring 中，过滤器都默认继承了 OncePerRequestFilter，顾名思义，OncePerRequestFilter 的作用就是确保一次请求只通过一次过滤器，而不重复执行。

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/Filter-Interceptor-Listener-aaa1c537-c8ed-4c5d-b27f-93c1409f2748.png)

在编程喵实战项目中，我们就是通过继承 OncePerRequestFilter 来实现 JWT 登录授权过滤的。

```java
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
	@Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
        // 从客户端请求中获取 JWT
        String authHeader = request.getHeader(this.tokenHeader);
        // 该 JWT 是我们规定的格式，以 tokenHead 开头
        if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
            // The part after "Bearer "
            String authToken = authHeader.substring(this.tokenHead.length());
            // 从 JWT 中获取用户名
            String username = jwtTokenUtil.getUserNameFromToken(authToken);
            LOGGER.info("checking username:{}", username);

            // SecurityContextHolder 是 SpringSecurity 的一个工具类
            // 保存应用程序中当前使用人的安全上下文
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                // 根据用户名获取登录用户信息
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                // 验证 token 是否过期
                if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                    // 将登录用户保存到安全上下文中
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,
                            null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        chain.doFilter(request, response);
    }
}
```

我们利用 Spring Initializr 来新建一个 Web 项目 codingmore-filter-interceptor-listener。

添加一个过滤器 MyFilter ：

```java
@WebFilter(urlPatterns = "/*", filterName = "myFilter")
public class MyFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        long start = System.currentTimeMillis();
        chain.doFilter(request,response);
        System.out.println("Execute cost="+(System.currentTimeMillis()-start));
    }

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

@WebFilter 注解用于将一个类声明为过滤器，urlPatterns 属性用来指定过滤器的 URL 匹配模式，filterName 用来定义过滤器的名字。

MyFilter 过滤器的逻辑非常简单，重写了 Filter 的三个方法，在 doFilter 方法中加入了时间戳的记录。

然后我们在项目入口类上加上 @ServletComponentScan 注解，这样过滤器就会自动注册。

启动服务器，访问任意的 URL。

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/Filter-Interceptor-Listener-c865b6d2-30d3-435b-a930-c732caed17ce.png)



## 拦截器

- 登录验证，判断用户是否登录
- 权限验证，判断用户是否有权限访问资源，如校验token
- 日志记录，记录请求操作日志（用户ip，访问时间等），以便统计请求访问量
- 处理cookie、本地化、国际化、主题等
- 性能监控，监控请求处理时长等


我们来写一个简单的拦截器 LoggerInterceptor：

```java
@Slf4j
public class LoggerInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("preHandle{}...",request.getRequestURI());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}
```

一个拦截器必须实现 HandlerInterceptor 接口，preHandle 方法是 Controller 方法调用前执行，postHandle  是 Controller 方法正常返回后执行，afterCompletion 方法无论 Controller 方法是否抛异常都会执行。

只有 preHandle 返回 true 的话，其他两个方法才会执行。

如果 preHandle 返回 false 的话，表示不需要调用Controller方法继续处理了，通常在认证或者安全检查失败时直接返回错误响应。

再来一个 InterceptorConfig 对拦截器进行配置：

```java
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoggerInterceptor()).addPathPatterns("/**");
    }
}
```

@Configuration 注解用于定义配置类，干掉了以往 Spring 繁琐的 xml 配置文件。

编写一个用于被拦截的控制器 MyInterceptorController：

```java
@RestController
@RequestMapping("/myinterceptor")
public class MyInterceptorController {
    @RequestMapping("/hello")
    public String hello() {
        return "沉默王二是傻X";
    }
}
```

@RestController 注解相当于 @Controller + @ResponseBody 注解，@ResponseBody 注解用于将 Controller 方法返回的对象，通过适当的 HttpMessageConverter 转换为指定格式后，写入到 Response 对象的 body 数据区，通常用来返回 JSON 或者 XML 数据，返回 JSON 数据的情况比较多。


![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/Filter-Interceptor-Listener-27c3f03f-8cca-4cbe-84cb-005075c0b8c9.png)


启动服务器，访问 `http://localhost:8080/myinterceptor/hello`。


![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/Filter-Interceptor-Listener-dcd99eeb-c00e-4a7a-a1c2-f8c5ca952aed.png)

在控制台可以看到拦截器中的日志信息：

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/Filter-Interceptor-Listener-20bb0987-77c8-4069-a59f-bbd2d5584d8c.png)

无论是过滤器还是拦截器，都属于AOP（面向切面编程）思想的具体实现。除了这两种实现之外，还有另一种更灵活的AOP实现技术，即 Aspect，在编程喵实战项目里，你可以看到 Aspect 具体实现。

比如说统一日志切面 WebLogAspect，就是用来记录请求信息的。

```java
@Aspect
@Component
@Order(1)
public class WebLogAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);

    @Pointcut("execution(public * com.codingmore.controller.*.*(..))")
    public void webLog() {
    }

    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
    }

    @AfterReturning(value = "webLog()", returning = "ret")
    public void doAfterReturning(Object ret) throws Throwable {
    }

    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        //获取当前请求对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        webLog.setStartTime(startTime);
        webLog.setUri(request.getRequestURI());
        logMap.put("parameter",webLog.getParameter());
        logMap.put("spendTime",webLog.getSpendTime());
        logMap.put("description",webLog.getDescription());
        LOGGER.info("{}", JSONUtil.parse(webLog));
        return result;
    }

    /**
     * 根据方法和传入的参数获取请求参数
     */
    private Object getParameter(Method method, Object[] args) {
    }
}
```

通过拦截后的请求信息大概是这样的：


![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/Filter-Interceptor-Listener-7a4b219d-bd3e-435e-a2dc-93f4fe4e8cc2.png)


## 监听器

根据监听对象可以把监听器分为 3 类：

----

更多内容，只针对《Java 程序员进阶之路》星球用户开放，需要的小伙伴可以[戳链接🔗](https://tobebetterjavaer.com/zhishixingqiu/)加入我们的星球，一起学习，一起卷。。**编程喵**🐱是一个 Spring Boot+Vue 的前后端分离项目，融合了市面上绝大多数流行的技术要点。通过学习实战项目，你可以将所学的知识通过实践进行检验、你可以拓宽自己的技术边界，你可以掌握一个真正的实战项目是如何从 0 到 1 的。

----

## 源码路径：

> - 编程喵：[https://github.com/itwanger/coding-more](https://github.com/itwanger/coding-more)
> - 过滤器，拦截器、监听器专用：[https://github.com/itwanger/coding-more](https://github.com/itwanger/codingmore-learning/tree/main/codingmore-filter-interceptor-listener)

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png)