当前位置:   article > 正文

Spring Boot 使用过滤器、拦截器、监听器

Spring Boot 使用过滤器、拦截器、监听器

前言

作用

  • 过滤器(Filter):当有一堆请求,只希望符合预期的请求进来。
  • 拦截器(Interceptor):想要干涉预期的请求。
  • 监听器(Listener):想要监听这些请求具体做了什么。

区别

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

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

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

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

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

过滤器

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

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

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

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

接下来我们通过继承 OncePerRequestFilter 来实现 JWT 登录授权过滤。

 

  1. public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
  2. @Override
  3. protected void doFilterInternal(HttpServletRequest request,
  4. HttpServletResponse response,
  5. FilterChain chain) throws ServletException, IOException {
  6. // 从客户端请求中获取 JWT
  7. String authHeader = request.getHeader(this.tokenHeader);
  8. // 该 JWT 是我们规定的格式,以 tokenHead 开头
  9. if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
  10. // The part after "Bearer "
  11. String authToken = authHeader.substring(this.tokenHead.length());
  12. // 从 JWT 中获取用户名
  13. String username = jwtTokenUtil.getUserNameFromToken(authToken);
  14. LOGGER.info("checking username:{}", username);
  15. // SecurityContextHolder 是 SpringSecurity 的一个工具类
  16. // 保存应用程序中当前使用人的安全上下文
  17. if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
  18. // 根据用户名获取登录用户信息
  19. UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
  20. // 验证 token 是否过期
  21. if (jwtTokenUtil.validateToken(authToken, userDetails)) {
  22. // 将登录用户保存到安全上下文中
  23. UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,
  24. null, userDetails.getAuthorities());
  25. authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
  26. SecurityContextHolder.getContext().setAuthentication(authentication);
  27. }
  28. }
  29. }
  30. chain.doFilter(request, response);
  31. }
  32. }

新建一个 Web 项目 codingmore-filter-interceptor-listener。

添加一个过滤器 MyFilter :

  1. @WebFilter(urlPatterns = "/*", filterName = "myFilter")
  2. public class MyFilter implements Filter {
  3. @Override
  4. public void init(FilterConfig filterConfig) throws ServletException {
  5. Filter.super.init(filterConfig);
  6. }
  7. @Override
  8. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  9. long start = System.currentTimeMillis();
  10. chain.doFilter(request,response);
  11. System.out.println("Execute cost="+(System.currentTimeMillis()-start));
  12. }
  13. @Override
  14. public void destroy() {
  15. Filter.super.destroy();
  16. }
  17. }

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

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

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

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

拦截器

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

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

  1. @Slf4j
  2. public class LoggerInterceptor implements HandlerInterceptor {
  3. @Override
  4. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  5. log.info("preHandle{} ...." ,request.getRequestURI());
  6. }
  7. @Override
  8. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
  9. HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
  10. }
  11. @Override
  12. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  13. HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
  14. }
  15. }

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

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

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

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

  1. /**
  2. * @ClAssName InterceptorConfig
  3. * @Description TOOO
  4. * @Author soshi是神仙
  5. * @Date 2024/3/27 18:{MINUTE}
  6. */
  7. @Configuration
  8. public class InterceptorConfig implements WebMvcConfigurer {
  9. @Override
  10. public void addInterceptors(InterceptorRegistry registry) {
  11. WebMvcConfigurer.super.addInterceptors(registry);
  12. registry.addInterceptor(new LoggerInterceptor()).addPathPatterns("/**");
  13. }
  14. }

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

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

  1. @RestController
  2. @RequestMapping("/myinterceptor")
  3. public class MyInterceptorController {
  4. @RequestMapping("/hello")
  5. public String hello(){
  6. return "测试拦截器的使用";
  7. }
  8. }

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

启动服务器,访问 http://localhost:8080/myinterceptor/hello

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

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

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

  1. @Aspect
  2. @Component
  3. @Order(1)
  4. public class WebLogAspect {
  5. private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);
  6. @Pointcut("execution(public * com.codingmore.controller.*.*(..))")
  7. public void webLog() {
  8. }
  9. @Before("webLog()")
  10. public void doBefore(JoinPoint joinPoint) throws Throwable {
  11. }
  12. @AfterReturning(value = "webLog()", returning = "ret")
  13. public void doAfterReturning(Object ret) throws Throwable {
  14. }
  15. @Around("webLog()")
  16. public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
  17. long startTime = System.currentTimeMillis();
  18. //获取当前请求对象
  19. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  20. webLog.setStartTime(startTime);
  21. webLog.setUri(request.getRequestURI());
  22. logMap.put("parameter",webLog.getParameter());
  23. logMap.put("spendTime",webLog.getSpendTime());
  24. logMap.put("description",webLog.getDescription());
  25. LOGGER.info("{}", JSONUtil.parse(webLog));
  26. return result;
  27. }
  28. /**
  29. * 根据方法和传入的参数获取请求参数
  30. */
  31. private Object getParameter(Method method, Object[] args) {
  32. }
  33. }

监听器

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

  • ServletContext:对应应用 application,整个 Web 服务器中只有一个,Web 服务器关闭时销毁。可用于数据缓存,例如结合redis,在Web服务创建时从数据库拉取数据到缓存服务器。
  • HttpSession: 对应会话 session,在会话建立时创建,一端会话关闭时销毁。可用于获取在线用户数量。
  • ServletRequest:对应 request,客户端发送请求时创建,一同创建的还有 response,用于封装请求数据,在一次请求处理完成时销毁。可用于封装用户信息。

新建一个 MyListener:

  1. @WebListener
  2. public class MyListener implements ServletContextListener {
  3. @Override
  4. public void contextInitialized(ServletContextEvent sce) {
  5. ServletContextListener.super.contextInitialized(sce);
  6. System.out.println("上下文创建");
  7. }
  8. @Override
  9. public void contextDestroyed(ServletContextEvent sce) {
  10. System.out.println("上下文销毁");
  11. }

@WebListener 注解用于将一个类声明为监听器,同样干掉了 web.xml 文件。

ServletContextListener 能够监听整个 Web 应用程序的生命周期。当 Web 应用启动时触发 contextInitialized 方法,关闭时触发 contextDestroyed 方法。

在 Intellij IDEA 中重启服务的时候,可以在控制台看到如下信息:

不过需要注意的是,在 Intellij IDEA 中直接关闭进程无法看到 contextDestroyed 被调用的消息。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/346751
推荐阅读
相关标签
  

闽ICP备14008679号