SpringBoot中@EventListener注解的使用
对于 Spring 容器的一些事件,能够监听而且触发相应的方法。一般的方法有 2 种,ApplicationListener 接口和@EventListener 注解。spring
简介
要想顺利的建立监听器,并起做用,这个过程当中须要这样几个角色:
一、事件(event)能够封装和传递监听器中要处理的参数,如对象或字符串,并做为监听器中监听的目标。
二、监听器(listener)具体根据事件发生的业务处理模块,这里能够接收处理事件中封装的对象或字符串。
三、事件发布者(publisher)事件发生的触发者。shell
ApplicationListener 接口
ApplicationListener 接口的定义以下:设计模式
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { /** * Handle an application event. * @param event the event to respond to */ void onApplicationEvent(E event); }
它是一个泛型接口,泛型的类型必须是 ApplicationEvent 及其子类,只要实现了这个接口,那么当容器有相应的事件触发时,就能触发 onApplicationEvent 方法。ApplicationEvent 类的子类有不少,Spring 框架自带的以下几个。tomcat
简单使用
使用方法很简单,就是实现一个 ApplicationListener 接口,而且将加入到容器中就行。springboot
@Component public class MyApplicationListener implements ApplicationListener<ApplicationEvent> { @Override public void onApplicationEvent(ApplicationEvent event) { System.out.println("事件触发:"+event.getClass().getName()); }
而后启动本身的springboot项目:app
@SpringBootApplication public class ApplicationListenerDemoApplication { public static void main(String[] args) { SpringApplication.run(ApplicationListenerDemoApplication.class, args); } }
能够看到控制台输出:框架
事件触发:org.springframework.context.event.ContextRefreshedEvent
2021-01-24 22:09:20.113 INFO 9228 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
事件触发:org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent
2021-01-24 22:09:20.116 INFO 9228 --- [ main] c.n.ApplicationListenerDemoApplication : Started ApplicationListenerDemoApplication in 1.221 seconds (JVM running for 1.903)
事件触发:org.springframework.boot.context.event.ApplicationStartedEvent
事件触发:org.springframework.boot.context.event.ApplicationReadyEvent
这样就触发了spring默认的一些事件。ide
自定义事件以及监听
定义事件
首先,咱们须要定义一个时间(MyTestEvent),须要继承Spring的ApplicationEvent
public class MyTestEvent extends ApplicationEvent{ /** * */ private static final long serialVersionUID = 1L; private String msg ; public MyTestEvent(Object source,String msg) { super(source); this.msg = msg; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
定义监听器
须要定义一下监听器,本身定义的监听器须要实现ApplicationListener,同时泛型参数要加上本身要监听的事件Class名,在重写的方法onApplicationEvent中,添加本身的业务处理:
@Component public class MyNoAnnotationListener implements ApplicationListener<MyTestEvent> { @Override public void onApplicationEvent(MyTestEvent event) { System.out.println("非注解监听器:" + event.getMsg()); } }
事件发布
有了事件,有了事件监听者,那么何时触发这个事件呢?每次想让监听器收到事件通知的时候,就能够调用一下事件发布的操做。首先在类里自动注入了ApplicationEventPublisher
,这个也就是咱们的ApplicationCOntext
,它实现了这个接口。
@Component public class MyTestEventPubLisher { @Autowired private ApplicationEventPublisher applicationEventPublisher; /** * 事件发布方法 */ public void pushListener(String msg) { applicationEventPublisher.publishEvent(new MyTestEvent(this, msg)); } }
测试
用一个HTTP请求来模拟:
@RestController public class TestEventListenerController { @Autowired private MyTestEventPubLisher publisher; @RequestMapping(value = "/test/testPublishEvent1" ) public void testPublishEvent(){ publisher.pushListener("我来了!"); } }
启动项目,能够看到控制台输出,测试完成:
事件触发:com.njit.personal.unannotation.MyTestEvent 非注解监听器:我来了!
@EventListener 注解
简单使用
除了经过实现接口,还可使用@EventListener 注解,实现对任意的方法都能监听事件。
在任意方法上标注@EventListener 注解,指定 classes,即须要处理的事件类型,通常就是 ApplicationEven 及其子类,能够设置多项。
@Configuration public class Config { @EventListener(classes = {ApplicationEvent.class}) public void listen(ApplicationEvent event) { System.out.println("事件触发:" + event.getClass().getName()); } }
启动项目
能够看到控制台和以前的输出是同样的:
事件触发:org.springframework.context.event.ContextRefreshedEvent
2021-01-24 22:39:13.647 INFO 16072 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
事件触发:org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent
2021-01-24 22:39:13.650 INFO 16072 --- [ main] c.n.ApplicationListenerDemoApplication : Started ApplicationListenerDemoApplication in 1.316 seconds (JVM running for 2.504)
事件触发:org.springframework.boot.context.event.ApplicationStartedEvent
事件触发:org.springframework.boot.context.event.ApplicationReadyEvent
自定义事件以及监听
使用注解的好处是不用每次都去实现ApplicationListener,能够在一个class中定义多个方法,用@EventListener来作方法级别的注解。
和上面相似,事件以及事件发布不须要改变,只要这样定义监听器便可。
@Component public class MyAnnotationListener { @EventListener public void listener1(MyTestEvent event) { System.out.println("注解监听器1:" + event.getMsg()); } }
此时,就能够有一个发布,两个监听器监听到发布的消息了,一个是注解方式,一个是非注解方式
结果:
事件触发:com.njit.personal.unannotation.MyTestEvent 注解监听器1:我来了! 非注解监听器:我来了!
咱们能够发现,注解形式的监听器的执行走在了非注解的前面。
原理
其实上面添加@EventListener
注解的方法被包装成了ApplicationListener
对象,上面的相似于下面这种写法,这个应该比较好理解。
@Component
public class MyAnnotationListener implements ApplicationListener<MyTestEvent> {
@Override
public void onApplicationEvent(MyTestEvent event) {
System.out.println("注解监听器1:" + event.getMsg());
}
}
那么Spring是何时作这件事的呢?
查看SpringBoot
的源码,找到下面的代码,由于我是Tomcat环境,这里建立的ApplicationContext
是org.springframework.bootweb.servlet.context.AnnotationConfigServletWebServerApplicationContext
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
他的构造方法以下:
public AnnotationConfigServletWebServerApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
进到AnnotatedBeanDefinitionReader
里面
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
Assert.notNull(environment, "Environment must not be null");
this.registry = registry;
this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
再进到AnnotationConfigUtils
的方法里面,省略了一部分代码,能够看到他注册了一个EventListenerMethodProcessor
类到工厂了。这是一个BeanFactory
的后置处理器。
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
......
.....
......
if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
......
......
return beanDefs;
}
查看这个BeanFactory
的后置处理器EventListenerMethodProcessor
,下面方法,他会遍历全部bean,找到其中带有@EventListener
的方法,将它包装成ApplicationListenerMethodAdapter
,注册到工厂里,这样就成功注册到Spring的监听系统里了。
@Override public void afterSingletonsInstantiated() { ConfigurableListableBeanFactory beanFactory = this.beanFactory; Assert.state(this.beanFactory != null, "No ConfigurableListableBeanFactory set"); String[] beanNames = beanFactory.getBeanNamesForType(Object.class); for (String beanName : beanNames) { if (!ScopedProxyUtils.isScopedTarget(beanName)) { Class<?> type = null; try { type = AutoProxyUtils.determineTargetClass(beanFactory, beanName); } catch (Throwable ex) { // An unresolvable bean type, probably from a lazy bean - let's ignore it. if (logger.isDebugEnabled()) { logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex); } } if (type != null) { if (ScopedObject.class.isAssignableFrom(type)) { try { Class<?> targetClass = AutoProxyUtils.determineTargetClass( beanFactory, ScopedProxyUtils.getTargetBeanName(beanName)); if (targetClass != null) { type = targetClass; } } catch (Throwable ex) { // An invalid scoped proxy arrangement - let's ignore it. if (logger.isDebugEnabled()) { logger.debug("Could not resolve target bean for scoped proxy '" + beanName + "'", ex); } } } try { processBean(beanName, type); } catch (Throwable ex) { throw new BeanInitializationException("Failed to process @EventListener " + "annotation on bean with name '" + beanName + "'", ex); } } } } } private void processBean(final String beanName, final Class<?> targetType) { if (!this.nonAnnotatedClasses.contains(targetType) && !targetType.getName().startsWith("java") && !isSpringContainerClass(targetType)) { Map<Method, EventListener> annotatedMethods = null; try { annotatedMethods = MethodIntrospector.selectMethods(targetType, (MethodIntrospector.MetadataLookup<EventListener>) method -> AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class)); } catch (Throwable ex) { // An unresolvable type in a method signature, probably from a lazy bean - let's ignore it. if (logger.isDebugEnabled()) { logger.debug("Could not resolve methods for bean with name '" + beanName + "'", ex); } } if (CollectionUtils.isEmpty(annotatedMethods)) { this.nonAnnotatedClasses.add(targetType); if (logger.isTraceEnabled()) { logger.trace("No @EventListener annotations found on bean class: " + targetType.getName()); } } else { // Non-empty set of methods ConfigurableApplicationContext context = this.applicationContext; Assert.state(context != null, "No ApplicationContext set"); List<EventListenerFactory> factories = this.eventListenerFactories; Assert.state(factories != null, "EventListenerFactory List not initialized"); for (Method method : annotatedMethods.keySet()) { for (EventListenerFactory factory : factories) { if (factory.supportsMethod(method)) { Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName)); ApplicationListener<?> applicationListener = factory.createApplicationListener(beanName, targetType, methodToUse); if (applicationListener instanceof ApplicationListenerMethodAdapter) { ((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator); } context.addApplicationListener(applicationListener); break; } } } if (logger.isDebugEnabled()) { logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" + beanName + "': " + annotatedMethods); } } } }
由方法生成Listener
的逻辑由EventListenerFactory
完成的,这又分为两种,一种是普通的@EventLintener
另外一种是@TransactionalEventListener
,是由两个工厂处理的。
总结
上面介绍了@EventListener
的原理,其实上面方法里还有一个@TransactionalEventListener
注解,其实原理是如出一辙的,只是这个监听者能够选择在事务完成后才会被执行,事务执行失败就不会被执行。
这两个注解的逻辑是如出一辙的,而且@TransactionalEventListener
自己就被标记有@EventListener
,
只是最后生成监听器时所用的工厂不同而已。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· DeepSeek在M芯片Mac上本地化部署