Spring的事件
对于SpringApplicationContext(BeanFactory)而言,在整个应用运行过程中(包括应用的启动、销毁), 会发布各种应用事件。开发者也可以实现自己的事件, 从而起到扩展spring框架的作用 。
Spring的事件(Application Event)为 Bean与 Bean之间的消息通信提供了支持。当一个Bean处理完一个任务之后,希望另外一个 Bean知道并能做相应的处理, 这时我们就需要让另外一个 Bean监听当前 Bean所发送的事件。
sprjng借助于 org.springframewofk.context.event.ApplicationEvent抽象类及其子类实现事件的发布,与此同时, 借助于 org.springframework.context.ApplicationListener接口及其实现者实现事件的监听,这两者构成了观察者 ( observer) 模式 。
- ApplicationEvent就是Spring的事件接口
- ApplicationListener就是Spring的事件监听器接口,所有的监听器都实现该接口
- ApplicationEventPublisher是Spring的事件发布接口,ApplicationContext实现了该接口
- ApplicationEventMulticaster就是Spring事件机制中的事件广播器,默认实现SimpleApplicationEventMulticaster
在Spring中通常是ApplicationContext本身担任监听器注册表的角色,在其子类AbstractApplicationContext中就聚合了事件广播器ApplicationEventMulticaster和事件监听器ApplicationListnener,并且提供注册监听器的addApplicationListnener方法。
其执行的流程大致为:
当一个事件源产生事件时,它通过事件发布器ApplicationEventPublisher发布事件,然后事件广播器ApplicationEventMulticaster会去事件注册表ApplicationContext中找到事件监听器ApplicationListnener,并且逐个执行监听器的onApplicationEvent方法,从而完成事件监听器的逻辑。
在Spring中,使用注册监听接口,除了继承ApplicationListener接口外,还可以使用注解@EventListener来监听一个事件,同时该注解还支持SpEL表达式,来触发监听的条件,比如只接受编码为001的事件,从而实现一些个性化操作。
SpringBoot的默认启动事件
在SpringBoot的1.5.x中,提供了几种事件,供我们在开发过程中进行更加便捷的扩展及差异化操作。
- ApplicationStartingEvent:springboot启动开始的时候执行的事件
- ApplicationEnvironmentPreparedEvent:spring boot对应Enviroment已经准备完毕,但此时上下文context还没有创建。在该监听中获取到ConfigurableEnvironment后可以对配置信息做操作,例如:修改默认的配置信息,增加额外的配置信息等等。
- ApplicationPreparedEvent:spring boot上下文context创建完成,但此时spring中的bean是没有完全加载完成的。在获取完上下文后,可以将上下文传递出去做一些额外的操作。值得注意的是:在该监听器中是无法获取自定义bean并进行操作的。
- ApplicationReadyEvent:springboot加载完成时候执行的事件。
- ApplicationFailedEvent:spring boot启动异常时执行事件。
从官网文档中,我们可以知道,由于一些事件是在上下文为加载完触发的,所以无法使用注册bean的方式来声明,文档中可以看出,可以通过SpringApplication.addListeners(…)或者SpringApplicationBuilder.listeners(…)来添加,或者添加META-INF/spring.factories文件中添加监听类也是可以的,这样会自动加载。
org.springframework.context.ApplicationListener=com.example.project.MyListener
来用代码说明一切,我们创建一个ApplicationStartingEvent事件监听类。
@Slf4j
public class MyApplicationStartingEventListener implements ApplicationListener<ApplicationStartingEvent> {
@Override
public void onApplicationEvent(ApplicationStartingEvent event) {
// TODO Auto-generated method stub
//由于 log相关还未加载 使用了也输出不了的
log.info("ApplicationStartingEvent事件发布:{}", event);
System.out.println("ApplicationStartingEvent事件发布:" + event.getTimestamp());
}
}
启动类中添加:
@SpringBootApplication
public class Application {
public static void main(String[] args){
SpringApplication app =new SpringApplication(Application.class);
app.addListeners(new MyApplicationStartingEventListener());//加入自定义的监听类
app.run(args);
}
}
启动应用,控制台可以看出,在启动时,我们监听到了ApplicationStartingEvent事件
所以在需要的时候,可以通过适当的监听以上事件,来完成一些业务操作。
自定义事件发布和监听
为实现 ApplicationEvent事件的发布,开发者需要借助于 ApplicationContext,它提供了 publishEvent方法。Spring 的事件需要遵循如下流程:
(1) 自定义事件, 继承 ApplicaltionEvent。
(2) 定义事件监听器,实现ApplicationListener。
(3) 使用容器发布事件。
通过以上的介绍,我们来定义一个自定义事件的发布和监听。
自定义事件源
public class DemoEvent extends ApplicationEvent{
private static final long serialVersionUID = 1L;
private String msg;
public DemoEvent(Object source,String msg) {
super(source);
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
当然实际应用中,我们的消息可能不仅是字符串,所以我们也可以定义一个消息实体
spring提供了如下三种常见 ApplicationEvent事件实现,它们的含义如下:
- org.sprjngframework.web.context.support.RequestHandledEvent: 开发者必须注意到,在 spring 的 webApplicationContext 中, 一旦客户请求处理完成, 将发布RequestHandledEvent事件。 当然, 如果需要, 开发者也可以在企业应用代码中抛出这种事件。
- org.springframework,context.event.ContextRefreshedEvent: 开发者必须注意到, 在spring Applicationcontext 容器初始化完成或刷新时, Spring 框架本身将发布ContextRefreshedEvent事件。
- org.springframework.context.event.ContextClosedEvent:开发者必须注意到,在关闭spring Appljcatjoncontext容器时, Spring框架本身将发布 ContextRefreshedEvent事件 。
事件监听器
1、继承ApplicationListener接口
/**
* 事件监听器
* 实现 ApplicationListener接口, 并指定监听的事件类型
*/
@Component
public class DemoListener implements ApplicationListener<DemoEvent> {
/**
* @param event
* 使用 onApplicationEvent方法对消息进行接受处理
*/
public void onApplicationEvent(DemoEvent event) {
String msg = event.getMsg();
System.out.println("我(bean-demoListener)接受到了bean-demoPublisher发布的消息:"
+ msg);
}
}
在Spring中,使用注册监听接口,除了继承ApplicationListener接口外,还可以使用注解@EventListener来监听一个事件,同时该注解还支持SpEL表达式,来触发监听的条件,比如只接受编码为001的事件,从而实现一些个性化操作。下文示例中会简单举例下。
2、使用@EventListener方式
/**
* 监听配置类
* @author oKong
*/
@Configuration
@Slf4j
public class EventListenerConfig {
@EventListener
public void handleEvent(Object event) {
//监听所有事件 可以看看 系统各类时间 发布了哪些事件
//可根据 instanceof 监听想要监听的事件
// if(event instanceof DemoEvent) {
// }
log.info("事件:{}", event);
}
@EventListener
public void handleCustomEvent(DemoEvent demoEvent) {
//监听 CustomEvent事件
log.info("监听到CustomEvent事件,消息为:{}, 发布时间:{}", demoEvent.getMsg(), demoEvent.getTimestamp());
}
/**
* 监听 code为oKong的事件
*/
@EventListener(condition="#demoEvent.msg == 'xmr'")
public void handleCustomEventByCondition(DemoEvent demoEvent) {
//监听 CustomEvent事件
log.info("监听到msg为'xmr'的DemoEvent事件,消息为:{}, 发布时间:{}", demoEvent.getMsg(), demoEvent.getTimestamp());
}
}
事件发布类
@Component
public class DemoPublisher {
@Autowired
ApplicationContext applicationContext;//注入 AppllcationContext用来发布事件
/**
* @param msg
* 使用 AppllicationContext的 publishEvent方法来发布
*/
public void publish(String msg){
applicationContext.publishEvent(new DemoEvent(this, msg));
}
}
Spring中,事件源不强迫继承ApplicationEvent接口的,也就是可以直接发布任意一个对象类。但内部其实是使用PayloadApplicationEvent类进行包装了一层。这点和guava的eventBus类似。而且,使用@EventListener的condition可以实现更加精细的事件监听,condition支持SpEL表达式,可根据事件源的参数来判断是否监听。
编写控制类,示例发布事件
@RestController
@RequestMapping("/event")
@Slf4j
public class EventController {
/**
* 注入事件发布类
*/
@Autowired
ApplicationEventPublisher eventPublisher;
/**
* 参数默认注解式@RequestParam
* @param message
* @return
*/
@GetMapping("/msg")
public String push(@RequestParam("message") String message) {
log.info("发布applicationEvent事件:{}", message);
eventPublisher.publishEvent(new DemoEvent(this, message));
return "事件发布成功!";
}
@GetMapping("/obj")
public String pushObject(String message) {
log.info("发布对象事件:{}", message);
eventPublisher.publishEvent(message);
return "对象事件发布成功!";
}
}
异步监听处理
默认情况下,监听事件都是同步执行的。在需要异步处理时,可以在方法上加上@Async进行异步化操作。此时,可以定义一个线程池,同时开启异步功能,加入@EnableAsync。
/**
* 监听msg为xmr的事件
*/
@Async
@EventListener(condition="#demoEvent.msg == 'xmr'")
public void handleCustomEventByCondition(DemoEvent demoEvent) {
//监听 CustomEvent事件
log.info("监听到msg为'xmr'的DemoEvent事件,消息为:{}, 发布时间:{}", demoEvent.getMsg(), demoEvent.getTimestamp());
}
关于事务绑定事件
当一些场景下,比如在用户注册成功后,即数据库事务提交了,之后再异步发送邮件等,不然会发生数据库插入失败,但事件却发布了,也就是邮件发送成功了的情况。此时,我们可以使用@TransactionalEventListener注解或者TransactionSynchronizationManager类来解决此类问题,也就是:事务成功提交后,再发布事件。当然也可以利用返回上层(事务提交后)再发布事件的方式了,只是不够优雅而已罢了
ApplicationListener和ContextRefreshedEvent
很多时候我们想要在某个类加载完毕时干某件事情,但是使用了spring管理对象,我们这个类引用了其他类(可能是更复杂的关联),所以当我们去使用这个类做事情时发现包空指针错误,这是因为我们这个类有可能已经初始化完成,但是引用的其他类不一定初始化完成,所以发生了空指针错误,解决方案如下:
1、写一个类继承spring的ApplicationListener监听,并监控ContextRefreshedEvent事件(容易初始化完成事件)
ApplicationListener和ContextRefreshedEvent一般都是成对出现的
在IOC的容器的启动过程,当所有的bean都已经处理完成之后,spring ioc容器会有一个发布事件的动作。从 AbstractApplicationContext 的源码中就可以看出
当ioc容器加载处理完相应的bean之后,也给我们提供了一个机会(先有InitializingBean,后有ApplicationListener<ContextRefreshedEvent>),可以去做一些自己想做的事。其实这也就是spring ioc容器给提供的一个扩展的地方。
一个最简单的方式就是,让我们的bean实现ApplicationListener接口,这样当发布事件时,[spring]的ioc容器就会以容器的实例对象作为事件源类,并从中找到事件的监听者,此时ApplicationListener接口实例中的onApplicationEvent(E event)方法就会被调用,我们的逻辑代码就会写在此处。这样我们的目的就达到了