在開發工做中,會遇到一種場景,作完某一件事情之後,須要廣播一些消息或者通知,告訴其餘的模塊進行一些事件處理,通常來講,能夠一個一個發送請求去通知,可是有一種更好的方式,那就是事件監聽,事件監聽也是設計模式中 發佈-訂閱模式、觀察者模式的一種實現。java
觀察者模式:簡單的來說就是你在作事情的時候身邊有人在盯着你,當你作的某一件事情是旁邊觀察的人感興趣的事情的時候,他會根據這個事情作一些其餘的事,可是盯着你看的人必需要到你這裏來登記,不然你沒法通知到他(或者說他沒有資格來盯着你作事情)。web
對於 Spring 容器的一些事件,能夠監聽而且觸發相應的方法。一般的方法有 2 種,ApplicationListener 接口和@EventListener 註解。spring
要想順利的建立監聽器,並起做用,這個過程當中須要這樣幾個角色:
一、事件(event)能夠封裝和傳遞監聽器中要處理的參數,如對象或字符串,並做爲監聽器中監聽的目標。
二、監聽器(listener)具體根據事件發生的業務處理模塊,這裏能夠接收處理事件中封裝的對象或字符串。
三、事件發佈者(publisher)事件發生的觸發者。shell
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 註解,指定 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
,
只是最後生成監聽器時所用的工廠不同而已。