深入学习SpringBoot中的应用事件和监听器

1 引言

在项SpringBoot目中我们经常需要一些监听器,通过监听一些事件做出相应的反应,并确保组件在生命周期内正常运行。比如上下文加载监听器。

ContextLoaderListener监听ServletContextEvent用于确定何时初始化和销毁应用上下文,ContextRefreshListener监听应用上下文刷新事件ContextRefreshedEvent。有时根据业务需求,作为开发人员我们也需要定义自己的事件,比如自定义用户注册、更新和删除事件,并做出相应的处理。

例如分布式组件zookeeper里面就大量用到了注册监听器,以监听节点的添加、更新、删除及其子节点的添加、删除和更新事件,在每个事件发生时做出相应的处理。因此对于Java中高级程序员来说,掌握Spring中的常用监听器以及自定义事件和监听器是一项必不可少的技能。

2 SpringBoot中与应用上下文加载过程相关的6大事件

SpringBoot项目中,启动应用后除了触发常规的spring框架事件,例如ContextRefreshedEvent外,SpringApplication还将触发一些额外的应用事件。而与SpringBoot应用相关的事件主要有以下6种:

  • 01 ApplicationStartingEvent事件:在应用刚启动时触发,发生在除了监听器注册和初始化线程之外的其他线程之前;
  • 02 ApplicationEnvironmentPreparedEvent事件:介于应用上下文中的Environment 已经准备好之后和在应用上下文创建之前的时间段触发,此时通过Spring容器可以拿到一些环境和系统变量,但还无法拿到任何bean的实例;
  • 03 ApplicationPreparedEvent 准备事件:介于spring应用上下文中的bean定义加载之后和应用上下文刷新之前的时间段触发;
  • 04 ApplicationStartedEvent 事件:介于在应用上下文刷新之后和任意application(应用)和command-line runners(命令行任务)调用之前的时间段触发;
  • 05 ApplicationReadyEvent : 在application和command-line 调用之后促发,此时表明应已经准备好服务于web请求;
  • 06 ApplicationFailedEvent: 在应用的启动过程中发生异常时触发

SpringBoot应用启动后,Spring应用中的事件将以上事件类型先后触发。

3 SpringBoot应用事件的两种注册方式
  • SpringApplication实例方法注册注册: 及通过new出来的SpringApplication实例的addListeners(ApplicationListener... listeners)API方法注册

  • spring.factories文件中键值对自动注册:在SpringBoot项目的resources目录下创建META-INF文件夹,然后在该目录下添加spring.factories文件,通过使用org.springframework.context.ApplicationListener作为键值以下面这种方式引用你自定义的监听器,示例如下:

org.springframework.context.ApplicationListener=com.example.project.MyListener

这种方式可以实现在应用启动时自动注册监听器。

注意: 一些事件会在Spring应用上下文初始话之前就触发,所以以组件的形式注册监听器对于这样的事件会失效,而通过以上两种方式注册监听器则正好避免了这种尴尬。

4 SpringBoot中事件和监听器器的用法

SpringBoot项目中所有监听以上6中监听事件的监听器都要实现ApplicationListener接口,并实现onApplicationEvent方法;并且需要将监听器注册到Spring应用中去。

4.1定义监听器

MyAppEnvPreparedListener.java

package com.example.bootdemo.listeners;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.ConfigurableEnvironment;

import java.util.Map;
import java.util.Set;

publicclass MyAppEnvPreparedListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {

    privatestatic Logger logger = LoggerFactory.getLogger(MyAppEnvPreparedListener.class);

    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
        logger.info("监听到ApplicationEnvironmentPreparedEvent事件......");
        //获取并打印事件源全类名
        String className = applicationEnvironmentPreparedEvent.getSource().getClass().getName();

        logger.info("className={}",className);
        //获取并打印环境变量
        ConfigurableEnvironment environment = applicationEnvironmentPreparedEvent.getEnvironment();
        
        String[] activeProfiles = environment.getActiveProfiles();

        for(int i=0;i<activeProfiles.length;i++){
            logger.info("activeProfiles["+i+"]="+activeProfiles[i]);
        }

        Map<String, Object> systemProperties = environment.getSystemProperties();

        Set<String> keySet = systemProperties.keySet();

        for(String key: keySet){
            logger.info(key+"="+systemProperties.get(key));
        }

    }
}

ApplicationReadyListener.java

package com.example.bootdemo.listeners;

import com.example.bootdemo.service.impl.UserServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;

publicclass ApplicationReadyListener implements ApplicationListener<ApplicationReadyEvent> {

    privatestatic Logger logger = LoggerFactory.getLogger(ApplicationReadyListener.class);

    @Override
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {

        logger.info("监听到ApplicationReadyEvent事件......");
        //从事件中拿到应用上下文
        ConfigurableApplicationContext applicationContext = applicationReadyEvent.getApplicationContext();
        //使用应用上下文获取单例bean
        Object bean = applicationContext.getBean(UserServiceImpl.class);

        String className = bean.getClass().getName();
        //打印单例bean的类名
        logger.info("className={}",className);

    }
}
4.2 在启动类中注册监听器
import com.example.bootdemo.listeners.*;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
publicclass BootDemoApplication {

	public static void main(String[] args) {

		SpringApplication springApplication = new SpringApplication(BootDemoApplication.class);

		MyAppEnvPreparedListener myAppEnvPreparedListener = new MyAppEnvPreparedListener();

		ApplicationReadyListener applicationReadyListener = new ApplicationReadyListener();

   //利用SpringApplication实例方法注册监听器
  springApplication.addListeners(myAppEnvPreparedListener,applicationReadyListener);
   //启动应用
  springApplication.run(args);

	}
}

通过以上代码可以看出:监听以上6种应用事件的监听器必须实现带泛型事件的应用监听器接口ApplicationListener<?extends SpringApplicationEvent>,并实现其onApplicationEvent方法

4.3 测试

启动SpringBoot应用后可以看到控制台打印出了如下日志信息:

//与MyAppEnvPreparedListener监听器相关的日志
2020-07-05 21:34:23.303  INFO 14524 --- [           main] c.e.b.l.MyAppEnvPreparedListener         : 监听到ApplicationEnvironmentPreparedEvent事件......
2020-07-05 21:34:23.307  INFO 14524 --- [           main] c.e.b.l.MyAppEnvPreparedListener         : className=org.springframework.boot.SpringApplication
2020-07-05 21:34:23.310  INFO 14524 --- [           main] c.e.b.l.MyAppEnvPreparedListener         : activeProfiles[0]=dev
2020-07-05 21:34:23.310  INFO 14524 --- [           main] c.e.b.l.MyAppEnvPreparedListener         : user.dir=D:\SpringBootProject\boot-demo
2020-07-05 21:34:23.312  INFO 14524 --- [           main] c.e.b.l.MyAppEnvPreparedListener         : intellij.debug.agent=true
2020-07-05 21:34:23.312  INFO 14524 --- [           main] c.e.b.l.MyAppEnvPreparedListener         : java.runtime.version=1.8.0_131-b11
//其余与MyAppEnvPreparedListener相关的信息不再贴上
2020-07-05 21:34:23.486  INFO 14524 --- [           main] c.example.bootdemo.BootDemoApplication   : The following profiles are active: dev
2020-07-05 21:34:25.146  INFO 14524 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
2020-07-05 21:34:25.264  INFO 14524 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 106ms. Found 2 repository interfaces.
2020-07-05 21:34:26.037  INFO 14524 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$234a6beb] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-07-05 21:34:26.852  INFO 14524 --- [           main]
o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8088 (http)
2020-07-05 21:34:26.900  INFO 14524 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-07-05 21:34:26.901  INFO 14524 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.17]
//tomcat中间件中默认使用了一个生命周期监听器AprLifecycleListener
2020-07-05 21:34:26.913  INFO 14524 --- [           main] o.a.catalina.core.AprLifecycleListener   : Loaded APR based Apache Tomcat Native library [1.2.21] using APR version [1.6.5].
2020-07-05 21:34:26.913  INFO 14524 --- [           main] o.a.catalina.core.AprLifecycleListener   : APR capabilities: IPv6 [true], sendfile [true], accept filters [false], random [true].
2020-07-05 21:34:26.913  INFO 14524 --- [           main] o.a.catalina.core.AprLifecycleListener   : APR/OpenSSL configuration: useAprConnector [false], useOpenSSL [true]
2020-07-05 21:34:26.921  INFO 14524 --- [           main] o.a.catalina.core.AprLifecycleListener   : OpenSSL successfully initialized [OpenSSL 1.1.1a  20 Nov 2018]
2020-07-05 21:34:27.284  INFO 14524 --- [           main] o.a.c.c.C.[.[localhost].[/apiBoot]       : Initializing Spring embedded WebApplicationContext
2020-07-05 21:34:27.285  INFO 14524 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 3680 ms
o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8088 (http) with context path '/apiBoot'
2020-07-05 21:34:31.290  INFO 14524 --- [           main] c.example.bootdemo.BootDemoApplication   : Started BootDemoApplication in 8.461 seconds (JVM running for 11.682)
                                                                  
//与ApplicationReadyListener监听器相关的日志
c.e.b.l.ApplicationReadyListener         : 监听到ApplicationReadyEvent事件......
2020-07-05 21:34:31.295  INFO 14524 --- [           main] c.e.b.l.ApplicationReadyListener         : className=com.example.bootdemo.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$6ce06578
2020-07-06 00:05:47.531  INFO 14524 --- [nio-8088-exec-1] o.a.c.c.C.[.[localhost].[/apiBoot]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-07-06 00:05:47.590  INFO 14524 --- [nio-8088-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-07-06 00:05:48.620  INFO 14524 --- [nio-8088-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1030 ms
---至此项目启动完成 ---
2020-07-06 00:05:50.107  INFO 14524 --- [nio-8088-exec-1]

从日志信息中可以看到: 实现接口的服务类bean是经过了SpringCGLIB增强后的类

5 自定义事件并监听
5.1 事件与监听器原理浅析

SpringBoot中的事件与监听器使用了设计模式中的观察者模式,其原理是基于订阅和发布机制。

通过查看以上6种事件的类继承关系发现他们都继承自SpringApplicationEvent类,事件源均为SpringApplication实例;而SpringApplicationEvent类又继承自ApplicationEvent,该类的构造函数中需传一个Object类型的事件源对象

package org.springframework.context;

import java.util.EventObject;

publicabstractclass ApplicationEvent extends EventObject {
    privatestaticfinallong serialVersionUID = 7099057708183571937L;
    privatefinallong timestamp = System.currentTimeMillis();

    public ApplicationEvent(Object source) {
        super(source);
    }

    public final long getTimestamp() {
        returnthis.timestamp;
    }
}

5.2 自定义用户注册事件
package com.example.bootdemo.events;

import com.example.bootdemo.pojo.UserInfo; import com.example.bootdemo.service.UserService; import org.springframework.context.ApplicationEvent;

public class UserRegisterEvent extends ApplicationEvent { //定义私有变量 private UserInfo userInfo; //够造函数中传入事件源用户服务类 public UserRegisterEvent(UserService userService, UserInfo userInfo) {

    super(userService);

    this.userInfo = userInfo;
}

public UserInfo getUserInfo() {
    return userInfo;
}
}
5.3 定义监听用户注册事件的监听器
package com.example.bootdemo.listeners;

import com.alibaba.fastjson.JSON;
import com.example.bootdemo.events.UserRegisterEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
publicclass UserRegisterListener implements ApplicationListener<UserRegisterEvent> {

    Logger logger = LoggerFactory.getLogger(UserRegisterListener.class);
    @Override
    public void onApplicationEvent(UserRegisterEvent userRegisterEvent) {

        logger.info("用户已注册,完成给新注册用户发邮件业务逻辑......");
        //打赢用户信息
        logger.info("userInfo={}", JSON.toJSON(userRegisterEvent.getUserInfo()));

    }
}

注意:监听非SpringBoot应用事件的监听器可以以组件的形式注册到Spring容器中去

5.4 在事件源类中利用Spring容器中发布事件

UserServiceImpl.java

    @Transactional
    @Override
    public UserInfo save(UserInfo userInfo) {
        try {
            //数据入库前对密码进行加密
            String password = EncryptionUtil.md5Encrypt(userInfo.getPassword());
            userInfo.setPassword(password);
        } catch (Exception e) {
            log.error("encrypt password failed",e);
            returnnull;
        }
        UserInfo userInfo1 = userRepository.save(userInfo);
        //构造和发布用户注册事件
        UserRegisterEvent userRegisterEvent = new UserRegisterEvent(this,userInfo1);
        applicationContext.publishEvent(userRegisterEvent);
        return userInfo1;
    }

UserInfoController.java

    @PostMapping("/info")
    public ServiceResponse<UserInfo> saveUserInfo(@RequestBody UserInfo userInfo){
       log.info("userInfo={}", JSON.toJSON(userInfo));
        ServiceResponse<UserInfo> response = new ServiceResponse<>();
        UserInfo userInfo1 = userInfoService.save(userInfo);
        response.setData(userInfo1);
        return response;
    }
5.5 测试效果

在postman上发送一个post请求:
http://localhost:8088/apiBoot/user/info
请求体RequestBody参数如下:

{
    "userName":"LeiFeng",
    "password": "leifeng2020",
    "userSex":"M",
    "userRole": "Admin",
    "telNum": 15200001316,
    "email": "leifeng163.com",
    "regDate": "2020-07-05",
    "birthDay": "1990-12-01",
    "userNameCn": "雷锋",
    "createdBy": "x_heshengfu",
    "createdTime": "2020-07-06 00:02:00",
    "lastUpdatedBy": "x_heshengfu",
    "lastUpdatedTime": "2020-07-06 00:02:00"
}

调用接口后可以看到服务器控制台打印出如下日志信息:

2020-07-06 00:05:50.989  INFO 14524 --- [nio-8088-exec-1] c.e.b.listeners.UserRegisterListener     : 用户已注册,完成给新注册用户发邮件业务逻辑......
2020-07-06 00:05:50.989  INFO 14524 --- [nio-8088-exec-1] c.e.b.listeners.UserRegisterListener     : userInfo={"birthDay":"1990-12-01","lastUpdatedBy":"x_heshengfu","userSex":"M","regDate":"2020-07-05","userName":"LeiFeng","userId":29,"userNameCn":"雷锋","password":"3d92f2a6b7fa372948dfaf8ceee78891","createdBy":"x_heshengfu","telNum":15200001316,"createdTime":"2020-07-06 00:02:00","lastUpdatedTime":"2020-07-06 00:02:00","userRole":"Admin","email":"leifeng163.com"}
6 小结

1) 应用程序事件是通过·Spring Framework·的事件发布机制发送的。这部分机制确保发布到子上下文中监听器的事件也被发布到任何祖先上下文中的监听器。因此,如果应用程序使用层次结构对于SpringApplication实例,监听器可以接收相同类型的多个实例应用程序事件。

2) 为了让侦听器区分·Spring·应用上下文的事件及其下级的上下文事件,要求注入它的应用程序上下文,然后进行比较注入的上下文与事件的上下文。上下文可以通过实现applicationcontextAware接口注入,如果侦听器是一个bean,则可以使用@Autowired注解实现注入

参考文章
《Spring Boot Reference Guide》(2.1.3.RELEASE版本SprigBoot开发指南)

本文完成代码已上传到gitee,克隆地址: https://gitee.com/heshengfu1211/boot-demo.git
需要查看完整代码的读可通过git自行clone下来
欢迎读者朋友们使用微信扫描下方微信二维码,关作者的微信公众号,与作者一同成长,
向着Java架构师的方向前进
在这里插入图片描述

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Springboot拦截器、过滤器和监听器是用来处理请求和响应的组件。拦截器是基于Java的反射机制,可以在请求处理前后进行一些操作,比如记录日志、权限验证等。过滤器是基于Servlet规范的,可以在请求进入Servlet之前或响应返回给客户端之前进行一些操作,比如字符编码、请求过滤等。监听器是用来监听Web应用程序事件,比如ServletContext的创建和销毁、Session的创建和销毁等。\[1\] 在Springboot,可以通过在application类上添加注解@SpringBootApplication和@ServletComponentScan来启用拦截器、过滤器和监听器。\[2\]拦截器、过滤器和监听器都是通过实现相应的接口来实现的。例如,创建一个过滤器可以通过实现Filter接口,并在@Component注解添加@Order注解来指定过滤器的执行顺序。\[3\] 拦截器、过滤器和监听器Springboot都可以起到不同的作用,可以根据具体需求选择使用哪种组件来处理请求和响应。 #### 引用[.reference_title] - *1* *2* [springBoot(6)---过滤器,监听器,拦截器](https://blog.csdn.net/yudiandemingzi/article/details/80399971)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [SpringBoot拦截器-过滤器-监听器](https://blog.csdn.net/weixin_45203607/article/details/120250823)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

heshengfu1211

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值