美文网首页
Web生命周期内的拦截过滤与监听

Web生命周期内的拦截过滤与监听

作者: liushiping | 来源:发表于2023-06-29 09:48 被阅读0次

一.Servlet域对象与属性变化监听

1.1 Servlet监听器定义

Servlet 监听器是 Servlet 规范中定义的一种特殊类,用于监听 ServletContext、HttpSession 和 ServletRequest 等作用域对象的创建与销毁事件,以及监听这些作用域对象中属性发生修改的事件。监听器使用了设计模式中的观察者模式,它关注特定事物的创建、销毁以及变化并做出回调动作,因此监听器具有异步的特性。
Servlet Listener 监听三大域对象的创建和销毁事件,三大对象分别是:

  1. ServletContext Listener:application 级别,整个应用只存在一个,所有用户使用一个ServletContext
  2. HttpSession Listener:session 级别,同一个用户的浏览器开启与关闭生命周期内使用的是同一个session
  3. ServletRequest Listener:request 级别,每一个HTTP请求为一个request

除了监听域对象的创建和销毁,还可以监听域对象中属性发生修改的事件。

  • HttpSessionAttributeListener
  • ServletContextAttributeListener
  • ServletRequestAttributeListener

1.2 Servlet监听器实现:

@Slf4j
@WebListener
public class CustomListener implements ServletContextListener,
        ServletRequestListener,
        HttpSessionListener,
        ServletRequestAttributeListener {

    @Override
    public void contextInitialized(ServletContextEvent se) {
        log.info("==============context创建");
    }

    @Override
    public void contextDestroyed(ServletContextEvent se) {
        log.info("==============context销毁");
    }


    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        log.info(" ++++++++++++++++++request监听器:销毁");
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        log.info(" ++++++++++++++++++request监听器:创建");
    }


    @Override
    public void sessionCreated(HttpSessionEvent se) {
        log.info("----------------session创建");
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        log.info("----------------session销毁");
    }

    @Override
    public void attributeAdded(ServletRequestAttributeEvent srae) {
        log.info("----------------attributeAdded");
    }

    @Override
    public void attributeRemoved(ServletRequestAttributeEvent srae) {
        log.info("----------------attributeRemoved");
    }

    @Override
    public void attributeReplaced(ServletRequestAttributeEvent srae) {
        log.info("----------------attributeReplaced");
    }
}

在启动类中加入@ServletComponentScan进行自动注册即可。

1.3 Servlet监听器测试:

@Slf4j
@RequestMapping("/api")
@RestController
public class DemoController {
    
    @GetMapping("/hello")
    public String hello(HttpServletRequest request, HttpSession session){
        request.setAttribute("name", "William");
        request.setAttribute("name", "Jerry");
        request.getAttribute("name");
        request.removeAttribute("age");

        session.setAttribute("name", "William");
        session.getAttribute("name");
        session.invalidate();
        return "hello";
    }
}

二.Servlet过滤器

2.1 定义及使用场景

Servlet 过滤器是可用于 Servlet 编程的 Java 类,有以下目的:

  • 在客户端的请求访问后端资源之前,拦截这些请求。
  • 在服务器的响应发送回客户端之前,处理这些响应。

在实际的应用开发中,我们经常使用过滤器做以下的一些事情

  • 基于一定的授权逻辑,对HTTP请求进行过滤,从而保证数据访问的安全。比如:判断请求的来源IP是否在系统黑名单中
  • 对于一些经过加密的HTTP请求数据,进行统一解密,方便后端资源进行业务处理
  • 或者我们社交应用经常需要的敏感词过滤,也可以使用过滤器,将触发敏感词的非法请求过滤掉

过滤器主要的特点在于:一是可以过滤所有请求,二是它能够改变请求的数据内容。

2.2 过滤器的实现

@WebFilter(filterName="customFilter",urlPatterns={"/*"})
@Slf4j
public class CustomFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("filter 初始化");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        log.info("customFilter 请求处理之前----doFilter方法之前过滤请求");
        //对request、response进行一些预处理
        // 比如设置请求编码
        // request.setCharacterEncoding("UTF-8");
        // response.setCharacterEncoding("UTF-8");

        //链路 直接传给下一个过滤器
        chain.doFilter(request, response);

        log.info("customFilter 请求处理之后----doFilter方法之后处理响应");
    }

    @Override
    public void destroy() {
        log.info("filter 销毁");
    }
}

然后在启动类加入@ServletComponentScan注解即可。

三.Spring拦截器

3.1 拦截器定义

在 Servlet 规范中并没有拦截器的概念,它是在Spring框架内衍生出来。
Spring中拦截器有三个方法:

  • preHandle 表示被拦截的URL对应的控制层方法,执行前的自定义处理逻辑
  • postHandle 表示被拦截的URL对应的控制层方法,执行后的自定义处理逻辑,此时还未将modelAndView进行页面渲染。
  • afterCompletion 表示此时modelAndView已做页面渲染,执行拦截器的自定义处理。

3.2 拦截器与过滤器的核心区别

从请求处理的生命周期上看,拦截器Interceptor和过滤器filter的作用是类似的。过滤球能做的事情,拦截器几乎也都能做。
但是二者使用场景还是有一些区别的:

  • 规范不同:Filter是在Servlet规范中定义的组件,在servlet容器内生效。而拦截器是Spring框架支持的,在Spring 上下文中生效。
  • 拦截器可以获取并使用Spring IOC容器中的bean,但过滤器就不行。因为过滤器是Servlet的组件,而IOC容器的bean是Spring框架内使用,拦截器恰恰是Spring框架内衍生出来的。
  • 拦截器可以访问Spring上下文值对象,如ModelAndView,过滤器不行。基于与上一点同样的原因。
  • 过滤器在进入servlet容器之前处理请求,拦截器在servlet容器之内处理请求。过滤器比拦截器的粒度更大,比较适合系统级别的所有API的处理动作。比如:权限认证,Spring Security就大量的使用了过滤器。
  • 拦截器相比于过滤器粒度更小,更适合分模块、分范围的统一业务逻辑处理。比如:分模块的、分业务的记录审计日志。(后面在日志的管理的那一章,我们会为介绍使用拦截器实现统一访问日志的记录)

3.3 拦截器的实现

1.定义拦截器:

@Slf4j
@Component
public class CustomHandlerInterceptor implements HandlerInterceptor {

     @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
         log.info("preHandle:请求前调用");
         //返回 false 则请求中断
         return true;
     }

     @Override
     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
          log.info("postHandle:请求后调用");
     }

     @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion:请求调用完成后回调方法,即在视图渲染完成后回调");
    }
}

2.通过实现WebMvcConfigurer接口完成拦截器的注册:

@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {

        @Resource
        private CustomHandlerInterceptor customHandlerInterceptor;

        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //注册拦截器 拦截规则
            //多个拦截器时 以此添加 执行顺序按添加顺序
            registry.addInterceptor(customHandlerInterceptor);
        }
}

四.Spring事件监听

4.1.事件监听的角色

首先我们要理解事件监听中需要的几个角色

  • 事件发布者 (即事件源)
  • 事件监听者
  • 事件本身

4.2. 事件监听的使用场景

为了将技术问题简单化,为大家举一个简单的例子。比如居委会发布停水通知。居委会就是事件源、停水就是事件本身、该居委会的辖区居民就是事件监听者。大家看这个例子,有这样几个特点:

  • 异步处理:居委会工作人员发布通知之后,就可以去忙别的工作了,不会原地等待所有居民的反馈。
  • 解耦:居委会和居民之间是解耦的,互相不干扰对方的工作状态与生活状态。
  • 不规律性:对于停水的事件发生频率是不规律的,触发规则相对随机。

当你在一个系统的业务需求中,满足上面的几个特点中的2点,就应该考虑使用事件监听机制实现业务需求。当然实现事件监听机制有很的方法,比如:

  • 使用消息队列中间件的发布订阅模式
  • JDK自带的java.util.EventListener
  • 本节为大家介绍的是:Spring环境下的实现事件发布监听的方法

4.3 代码实现

1.自定义事件
继承自ApplicationEvent抽象类,然后定义自己的构造器。

public class MyEvent extends ApplicationEvent {
    public MyEvent(Object source) {
       super(source);
    }
}

2.自定义事件监听器

@Slf4j
@Component
public class MyListener implements ApplicationListener<MyEvent> {

    @Override
    public void onApplicationEvent(MyEvent event) {
        log.info(String.format("%s监听到事件源:%s.", MyListener.class.getName(), event.getSource()));
    }
}

4.4 测试监听事件的发布

@Slf4j
@RequestMapping("/api")
@RestController
public class DemoController {

    @Resource
    private ApplicationContext applicationContext;

    @GetMapping("/publish")
    public void publishEvent() {
        applicationContext.publishEvent(new MyEvent("测试事件."));
    }
}

五.SpringBoot启动监听器

5.1 简介

Spring Boot提供了两个接口:CommandLineRunner、ApplicationRunner,用于启动应用时做特殊处理,这些代码会在SpringApplication的run()方法运行完成之前被执行。相对于之前介绍的Spring的ApplicationListener接口自定义监听器、Servlet的ServletContextListener监听器。使用二者的好处在于,可以方便的使用应用启动参数,根据参数不同做不同的初始化操作。

5.2 常用场景介绍

实现CommandLineRunner、ApplicationRunner接口。通常用于应用启动前的特殊代码执行,比如:

  • 将系统常用的数据加载到内存
  • 应用上一次运行的垃圾数据清理
  • 系统启动成功后的通知的发送

5.3 启动监听器实现

1.CommandLineRunner:参数是字符串数组

@Slf4j
@Component
public class CommandLineStartupRunner implements CommandLineRunner {
    @Override
    public void run(String... args){
        log.info("CommandLineRunner传入参数:{}", Arrays.toString(args));
    }
}

2.ApplicationRunner:参数被放入ApplicationArguments,通过getOptionNames()、getOptionValues()、getSourceArgs()获取参数

@Slf4j
@Component
public class AppStartupRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args)  {
        log.info("ApplicationRunner参数名称: {}", args.getOptionNames());
        log.info("ApplicationRunner参数值: {}", args.getOptionValues("age"));
        log.info("ApplicationRunner参数: {}", Arrays.toString(args.getSourceArgs()));
    }
}

5.4总结

CommandLineRunner、ApplicationRunner的核心用法是一致的,就是用于应用启动前的特殊代码执行。ApplicationRunner的执行顺序先于CommandLineRunner;ApplicationRunner将参数封装成了对象,提供了获取参数名、参数值等方法,操作上会方便一些。
当定义多个监听器时,SpringBoot是通过遍历来启动所有的Runner,只有上一个Runner执行完成之后,才会执行下一个Runner,是同步执行的。如果在某个Runner实现是run方法体中调用了同步阻塞的API或者是一个 while(true) 循环,在遍历中处于该Runner之后的其他实现将不会被执行。

相关文章

网友评论

      本文标题:Web生命周期内的拦截过滤与监听

      本文链接:https://www.haomeiwen.com/subject/kcmaydtx.html