# 前言
先说作用。
- 过滤器(Filter):当有一堆请求,只希望符合预期的请求进来。
- 拦截器(Interceptor):想要干涉预期的请求。
- 监听器(Listener):想要监听这些请求具体做了什么。
再说区别。
过滤器是在请求进入容器后,但还没有进入 Servlet 之前进行预处理的。如下图所示。

拦截器是在请求进入控制器(Controller) 之前进行预处理的。
虚线内就是过滤器和拦截器的作用范围:

过滤器依赖于 Servlet 容器,而拦截器依赖于 Spring 的 IoC 容器,因此可以通过注入的方式获取容器当中的对象。
监听器用于监听 Web 应用中某些对象的创建、销毁、增加、修改、删除等动作,然后做出相应的处理。
# 过滤器
- 过滤敏感词汇(防止 sql 注入)
- 设置字符编码
- URL 级别的权限访问控制
- 压缩响应信息
过滤器的创建和销毁都由 Web 服务器负责,Web 应用程序启动的时候,创建过滤器对象,为后续的请求过滤做好准备。
过滤器可以有很多个,一个个过滤器组合起来就成了 FilterChain,也就是过滤器链。
在 Spring 中,过滤器都默认继承了 OncePerRequestFilter,顾名思义,OncePerRequestFilter 的作用就是确保一次请求只通过一次过滤器,而不重复执行。

在编程喵实战项目中,我们就是通过继承 OncePerRequestFilter 来实现 JWT 登录授权过滤的。
| 12
 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
 
 | public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Override
 protected void doFilterInternal(HttpServletRequest request,
 HttpServletResponse response,
 FilterChain chain) throws ServletException, IOException {
 
 String authHeader = request.getHeader(this.tokenHeader);
 
 if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
 
 String authToken = authHeader.substring(this.tokenHead.length());
 
 String username = jwtTokenUtil.getUserNameFromToken(authToken);
 LOGGER.info("checking username:{}", username);
 
 
 
 if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
 
 UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
 
 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 :
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | @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。

# 拦截器
- 登录验证,判断用户是否登录
- 权限验证,判断用户是否有权限访问资源,如校验 token
- 日志记录,记录请求操作日志(用户 ip,访问时间等),以便统计请求访问量
- 处理 cookie、本地化、国际化、主题等
- 性能监控,监控请求处理时长等
我们来写一个简单的拦截器 LoggerInterceptor:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | @Slf4jpublic 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 对拦截器进行配置:
| 12
 3
 4
 5
 6
 7
 
 | @Configurationpublic class InterceptorConfig implements WebMvcConfigurer {
 @Override
 public void addInterceptors(InterceptorRegistry registry) {
 registry.addInterceptor(new LoggerInterceptor()).addPathPatterns("/**");
 }
 }
 
 | 
@Configuration 注解用于定义配置类,干掉了以往 Spring 繁琐的 xml 配置文件。
编写一个用于被拦截的控制器 MyInterceptorController:
| 12
 3
 4
 5
 6
 7
 8
 
 | @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://localhost:8080/myinterceptor/hello 。

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

无论是过滤器还是拦截器,都属于 AOP(面向切面编程)思想的具体实现。除了这两种实现之外,还有另一种更灵活的 AOP 实现技术,即 Aspect,在编程喵实战项目里,你可以看到 Aspect 具体实现。
比如说统一日志切面 WebLogAspect,就是用来记录请求信息的。
| 12
 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
 
 | @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) {
 }
 }
 
 | 
通过拦截后的请求信息大概是这样的:
