背景
在开发工作中,会遇到一种场景,做完某一件事件当前,须要播送一些音讯或者告诉,通知其余的模块进行一些事件处理,一般来说,能够一个一个发送申请去告诉,然而有一种更好的形式,那就是事件监听,事件监听也是设计模式中 公布-订阅模式、观察者模式的一种实现。
观察者模式:简略的来讲就是你在做事件的时候身边有人在盯着你,当你做的某一件事件是旁边察看的人感兴趣的事件的时候,他会依据这个事件做一些其余的事,然而盯着你看的人必须要到你这里来注销,否则你无奈告诉到他(或者说他没有资格来盯着你做事件)。
对于 Spring 容器的一些事件,能够监听并且触发相应的办法。通常的办法有 2 种,ApplicationListener 接口和@EventListener 注解。
简介
要想顺利的创立监听器,并起作用,这个过程中须要这样几个角色:
1、事件(event)能够封装和传递监听器中要解决的参数,如对象或字符串,并作为监听器中监听的指标。
2、监听器(listener)具体依据事件产生的业务解决模块,这里能够接管处理事件中封装的对象或字符串。
3、事件发布者(publisher)事件产生的触发者。
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 框架自带的如下几个。
简略应用
应用办法很简略,就是实现一个 ApplicationListener 接口,并且将退出到容器中就行。
@Componentpublic class MyApplicationListener implements ApplicationListener<ApplicationEvent> { @Overridepublic void onApplicationEvent(ApplicationEvent event) { System.out.println("事件触发:"+event.getClass().getName());}
而后启动本人的springboot我的项目:
@SpringBootApplicationpublic class ApplicationListenerDemoApplication { public static void main(String[] args) { SpringApplication.run(ApplicationListenerDemoApplication.class, args); }}
能够看到控制台输入:
事件触发:org.springframework.context.event.ContextRefreshedEvent2021-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.ServletWebServerInitializedEvent2021-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默认的一些事件。
自定义事件以及监听
定义事件
首先,咱们须要定义一个工夫(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中,增加本人的业务解决:
@Componentpublic class MyNoAnnotationListener implements ApplicationListener<MyTestEvent> { @Override public void onApplicationEvent(MyTestEvent event) { System.out.println("非注解监听器:" + event.getMsg()); }}
事件公布
有了事件,有了事件监听者,那么什么时候触发这个事件呢?每次想让监听器收到事件告诉的时候,就能够调用一下事件公布的操作。首先在类里主动注入了ApplicationEventPublisher
,这个也就是咱们的ApplicationCOntext
,它实现了这个接口。
@Componentpublic class MyTestEventPubLisher { @Autowired private ApplicationEventPublisher applicationEventPublisher; /** * 事件公布办法 */ public void pushListener(String msg) { applicationEventPublisher.publishEvent(new MyTestEvent(this, msg)); }}
测试
用一个HTTP申请来模仿:
@RestControllerpublic 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 及其子类,能够设置多项。
@Configurationpublic class Config { @EventListener(classes = {ApplicationEvent.class}) public void listen(ApplicationEvent event) { System.out.println("事件触发:" + event.getClass().getName()); }}
启动我的项目
能够看到控制台和之前的输入是一样的:
事件触发:org.springframework.context.event.ContextRefreshedEvent2021-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.ServletWebServerInitializedEvent2021-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来做办法级别的注解。
和下面相似,事件以及事件公布不须要扭转,只有这样定义监听器即可。
@Componentpublic class MyAnnotationListener { @EventListener public void listener1(MyTestEvent event) { System.out.println("注解监听器1:" + event.getMsg()); }}
此时,就能够有一个公布,两个监听器监听到公布的音讯了,一个是注解形式,一个是非注解形式
后果:
事件触发:com.njit.personal.unannotation.MyTestEvent注解监听器1:我来了!非注解监听器:我来了!
咱们能够发现,注解模式的监听器的执行走在了非注解的后面。
原理
其实下面增加@EventListener
注解的办法被包装成了ApplicationListener
对象,下面的相似于上面这种写法,这个应该比拟好了解。
@Componentpublic 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
,
只是最初生成监听器时所用的工厂不一样而已。
参考
spring中监听器的应用
@EventListener注解原理