SpringBoot小知识点(四)

1.监听器

1.1 介绍

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

Servlet Listener 监听三大域对象的创建和销毁事件,三大对象分别是:

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

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

  • HttpSessionAttributeListener
  • ServletContextAttributeListener
  • ServletRequestAttributeListener

1.2 使用

Servlet 规范设计监听器的作用是在事件发生前、发生后进行一些处理,一般可以用来统计在线人数和在线用户、统计网站访问量、系统启动时初始化信息等。

1.增加监听器

@Slf4j
@WebListener
public class MyServletContextListener implements ServletContextListener {

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

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("==============context创建");
    }
}
@Slf4j
@WebListener
public class MyHttpSessionListener implements HttpSessionListener {

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

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        log.info("----------------session销毁");
    }
}
@Slf4j
@WebListener
public class MyServletRequestListener implements ServletRequestListener {

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        log.info("==============Request 销毁");
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        log.info("==============Request 初始化");
    }
}
@Slf4j
@WebListener
public class MyServletRequestAttributeListener implements ServletRequestAttributeListener {


    @Override
    public void attributeAdded(ServletRequestAttributeEvent srae) {
        log.info("==============Attribute增加 {}", srae.getName());
    }

    @Override
    public void attributeRemoved(ServletRequestAttributeEvent srae) {
        log.info("==============Attribute移除");
    }

    @Override
    public void attributeReplaced(ServletRequestAttributeEvent srae) {
        log.info("==============Attribute替换");
    }
}

2.启动类中加入@ServletComponentScan进行自动注册即可

@SpringBootApplication
@ServletComponentScan
public class AppMain {
    public static void main(String[] args) {
        SpringApplication.run(AppMain.class,args);
    }
}

3.运行
刚启动
在这里插入图片描述
输入 http://localhost:8887/testListener 访问

在这里插入图片描述
在这里插入图片描述

  • 实现ServletRequestListener接口,并重写requestDestroyed销毁和requestInitialized方法。一次ServletRequest的requestInitialized方法和requestDestroyed销毁方法的执行代表1次请求的接收与处理完毕。所以比较适合网站资源被访问次数的统计。
  • 实现HttpSessionListener接口,并重写sessionInitialized初始化和sessionDestroyed销毁方法,可以监听session会话的开启与销毁(用户的上线与下线)。比如:可以用来实现在线用户数量的统计。
  • 实现ServletContextListener接口,并重写contextInitialized初始化和contextDestroyed销毁方法,可以监听全局应用的初始化和销毁。比如:在系统启动的时候,初始化一些数据到内存中供后续使用。
  • 实现ServletRequestAttributeListener接口(或HttpSessionAttributeListener或ServletContextAttributeListener)。可以监听到对应的作用域内数据属性的attributeAdded新增、attributeRemoved删除、attributeReplaced替换等动作。

正常的作用域生命周期 ServletContext > HttpSession > request

2.过滤器

2.1 介绍

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

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

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

在这里插入图片描述

2.2 使用

2.2.1 利用WebFilter注解配置

@WebFilter时Servlet3.0新增的注解,原先实现过滤器,需要在web.xml中进行配置,而现在通过此注解,启动启动时会自动扫描自动注册

1.编写过滤器

/**
 * @author ZY
 * @describe 注册器名称为customFilter1,拦截的url为所有
 * @date 2020/8/7
 */
@WebFilter(filterName="customFilter1",urlPatterns={"/*"})
@Slf4j
public class MyCustomFilter1 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info(">>>>>>>>>>>>>> MyCustomFilter1-init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info(">>>>>>>>>>>>>> MyCustomFilter1-doFilter-start");
        filterChain.doFilter(servletRequest, servletResponse);
        log.info(">>>>>>>>>>>>>> MyCustomFilter1-doFilter-end");
    }

    @Override
    public void destroy() {
        log.info(">>>>>>>>>>>>>> MyCustomFilter1-destroy");
    }
}
/**
 * @author ZY
 * @describe 注册器名称为customFilter2,拦截的url为所有
 * @date 2020/8/7
 */
@WebFilter(filterName="customFilter2",urlPatterns={"/*"})
@Slf4j
public class MyCustomFilter2 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info(">>>>>>>>>>>>>> MyCustomFilter2-init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info(">>>>>>>>>>>>>> MyCustomFilter2-doFilter-start");
        filterChain.doFilter(servletRequest, servletResponse);
        log.info(">>>>>>>>>>>>>> MyCustomFilter2-doFilter-end");
    }

    @Override
    public void destroy() {
        log.info(">>>>>>>>>>>>>> MyCustomFilter2-destroy");
    }
}

2.启动类加入@ServletComponentScan注解即可

@SpringBootApplication
@ServletComponentScan
public class AppMain {
    public static void main(String[] args) {
        SpringApplication.run(AppMain.class,args);
    }
}

3.启动

在这里插入图片描述
访问请求 http://localhost:8887/testfilter
在这里插入图片描述

使用这种方法当注册多个过滤器时,无法指定过滤器的先后执行顺序。原本使用web.xml配置过滤器时,是可指定执行顺序的,但使用@WebFilter时,没有这个配置属性的(需要配合@Order进行),所以接下来介绍下通过FilterRegistrationBean进行过滤器的注册。

通过过滤器的java类名称,进行顺序的约定,比如LogFilter和AuthFilter,此时AuthFilter就会比LogFilter先执行,因为首字母A比L前面。

2.2.2 FilterRegistrationBean方式

FilterRegistrationBean是springboot提供的,此类提供setOrder方法,可以为filter设置排序值,让spring在注册web filter之前排序后再依次注册。

1.首先要改写filter, 其实就删掉@webFilter注解即可,其他的都没有变化。然后的代码是Filter的注册代码

@Slf4j
public class MyCustomFilter1 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info(">>>>>>>>>>>>>> MyCustomFilter1-init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info(">>>>>>>>>>>>>> MyCustomFilter1-doFilter-start");
        filterChain.doFilter(servletRequest, servletResponse);
        log.info(">>>>>>>>>>>>>> MyCustomFilter1-doFilter-end");
    }

    @Override
    public void destroy() {
        log.info(">>>>>>>>>>>>>> MyCustomFilter1-destroy");
    }
}

@Slf4j
public class MyCustomFilter2 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info(">>>>>>>>>>>>>> MyCustomFilter2-init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info(">>>>>>>>>>>>>> MyCustomFilter2-doFilter-start");
        filterChain.doFilter(servletRequest, servletResponse);
        log.info(">>>>>>>>>>>>>> MyCustomFilter2-doFilter-end");
    }

    @Override
    public void destroy() {
        log.info(">>>>>>>>>>>>>> MyCustomFilter2-destroy");
    }
}

2.增加配置类,注册多个时,就注册多个FilterRegistrationBean即可,启动后,效果和第一种是一样的。可以访问应用内的任意资源进行过滤器测试,因为过滤器是针对所有的请求和响应。可以输入Filter中的log信息。

@Configuration
public class FilterConfig {


    @Bean
    public FilterRegistrationBean<MyCustomFilter1> filterRegistrationBean1() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        //Filter可以new,也可以使用依赖注入Bean
        registration.setFilter(new MyCustomFilter1());
        //过滤器名称
        registration.setName("customFilter1");
        //拦截路径
        registration.addUrlPatterns("/*");
        //设置顺序 越低越优先
        registration.setOrder(5);
        return registration;
    }

    @Bean
    public FilterRegistrationBean<MyCustomFilter2> filterRegistrationBean2() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        //Filter可以new,也可以使用依赖注入Bean
        registration.setFilter(new MyCustomFilter2());
        //过滤器名称
        registration.setName("customFilter2");
        //拦截路径
        registration.addUrlPatterns("/*");
        //设置顺序 越低越优先
        registration.setOrder(1);
        return registration;
    }

}

3.运行
在这里插入图片描述

3.拦截器

3.1 介绍

在 Servlet 规范中并没有拦截器的概念,它是在Spring框架内衍生出来。
在这里插入图片描述
Spring中拦截器有三个方法:

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

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

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

3.2 使用

1.编写拦截器

@Slf4j
@Component
public class MyHandlerInterceptor 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.编写配置类

@Configuration
public class IntercepterConfig implements WebMvcConfigurer {
    @Resource
    MyHandlerInterceptor myHandlerInterceptor;

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

3.运行
在这里插入图片描述
在这里插入图片描述

4.自定义事件的发布与监听

4.1 介绍

4.1.1 事件监听的角色

事件监听中需要的几个角色

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

4.1.2 使用场景

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

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

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

  • 使用消息队列中间件的发布订阅模式
  • JDK自带的java.util.EventListener

4.2 使用

4.2.1 自定义事件源

继承自ApplicationEvent抽象类,然后定义自己的构造器

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

    public void printMsg(String msg) {
        log.info("监听到的事件源:{}" ,msg);
    }
}

4.2.2 方式一:Component注解注入

使用Component注解将监听器装载入spring容器
1.自定义一个事件监听器

@Component
@Slf4j
public class MyListener implements ApplicationListener<MyEvent> {
    @Override
    public void onApplicationEvent(MyEvent myEvent) {
        myEvent.printMsg(MyEvent.class.getName());
    }
}

2.本次通过发送一次请求来出发事件发送

@Slf4j
@RestController
public class EventController {

    @Resource
    private ApplicationContext applicationContext;

    @GetMapping("/testevent")
    public void testfilter(){
        //发送事件源
        applicationContext.publishEvent(new MyEvent("自定义事件源"));
    }
    
}

4.发送请求触发 http://localhost:8887/testevent
在这里插入图片描述

4.2.3 方式二:application.properties中配置监听

context:
  listener:
    classes: com.zy.MyListener

4.2.4 方式三:@EventListener装饰具体方法

1.创建MyListener1类,该类无需实现ApplicationListener接口,使用@EventListener装饰具体方法

@Slf4j
@Component
public class MyListener2 {
    @EventListener
    public void listener(MyEvent myEvent) {
        myEvent.printMsg(MyEvent.class.getName()+"*MyListener2");
    }
}
@Component
@Slf4j
public class MyListener implements ApplicationListener<MyEvent> {
    @Override
    public void onApplicationEvent(MyEvent myEvent) {
        myEvent.printMsg(MyEvent.class.getName()+"*MyListener");
    }
}

2.其他代码照旧,运行
在这里插入图片描述

5.应用启动的监听

5.1 介绍

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

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

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

如下图是我实现了CommandLineRunner接口,在应用启动时将系统内常用的配置数据。从数据库加载到内存,以后使用该数据的时候只需要调用getSysConfigList方法,不需要每次使用该数据都去数据库加载。节省系统资源、缩减数据加载时间。
在这里插入图片描述

6

7

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值