spring是目前使用最為廣泛的Java框架之一。雖然spring最為核心是IOC和AOP,其中代碼實現中很多設計模式得以應用,代碼看起來簡潔流暢,在日常的軟件設計中很值得借鑒。以下是對一些設計模式的理解以及源碼解析,希望能給大家對設計模式的理解有所幫助。
更多設計模式更新中.....
(基於spring-4.3.23.RELEASE分析)
如對觀察者模式還不是很有概念,可以點擊這里。spring中ApplicationEvent事件通知就是觀察者模式的變種,巧妙的利用事件驅動機制實現容器刷新。開發者可充分利用該機制,實現框架的深度定制。下面分為幾點展開:
ApplicationEventMulticaster是一個接口,定義了事件管理的基本行為,目前spring只有一個實現類和一個抽象類。這里先貼出定義理解各個角色的作用,后邊一點點串聯整個功能。
public interface ApplicationEventMulticaster { /** * 增加一個具體事件監聽器 */ void addApplicationListener(ApplicationListener<?> listener); /** * 增加一個事件監聽器 以beanName形式 */ void addApplicationListenerBean(String listenerBeanName); /** * 刪除一個具體事件監聽器 */ void removeApplicationListener(ApplicationListener<?> listener); /** * 刪除一個事件監聽器 以beanName形式 */ void removeApplicationListenerBean(String listenerBeanName); /** * 刪除所有事件監聽器 */ void removeAllListeners(); /** * 發布事件 */ void multicastEvent(ApplicationEvent event); /** * 發布事件 */ void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType); }
ApplicationEvent是事件通知的媒介,也是消息傳遞的載體。這個類只是繼承JDK原生的EventObject,約定為source的事件傳遞數據 只要理解其作用即可 比較簡單所以貼出代碼
/** * Class to be extended by all application events. Abstract as it * doesn't make sense for generic events to be published directly. * * @author Rod Johnson * @author Juergen Hoeller */ public abstract class ApplicationEvent extends EventObject { /** use serialVersionUID from Spring 1.2 for interoperability. */ private static final long serialVersionUID = 7099057708183571937L; /** System time when the event happened. */ private final long timestamp; /** * Create a new ApplicationEvent. * @param source the object on which the event initially occurred (never {@code null}) */ public ApplicationEvent(Object source) { super(source); this.timestamp = System.currentTimeMillis(); } /** * Return the system time in milliseconds when the event happened. */ public final long getTimestamp() { return this.timestamp; } }
/** * <p> * The root class from which all event state objects shall be derived. * <p> * All Events are constructed with a reference to the object, the "source", * that is logically deemed to be the object upon which the Event in question * initially occurred upon. * * @since JDK1.1 */ public class EventObject implements java.io.Serializable { private static final long serialVersionUID = 5516075349620653480L; /** * The object on which the Event initially occurred. */ protected transient Object source; /** * Constructs a prototypical Event. * * @param source The object on which the Event initially occurred. * @exception IllegalArgumentException if source is null. */ public EventObject(Object source) { if (source == null) throw new IllegalArgumentException("null source"); this.source = source; } /** * The object on which the Event initially occurred. * * @return The object on which the Event initially occurred. */ public Object getSource() { return source; } /** * Returns a String representation of this EventObject. * * @return A a String representation of this EventObject. */ public String toString() { return getClass().getName() + "[source=" + source + "]"; } }
ApplicationEventPublisher是對外暴露發布事件接口的定義 ApplicationContext容器實現該接口定義:
/** * Interface that encapsulates event publication functionality. * Serves as super-interface for {@link ApplicationContext}. * * @author Juergen Hoeller * @author Stephane Nicoll * @since 1.1.1 * @see ApplicationContext * @see ApplicationEventPublisherAware * @see org.springframework.context.ApplicationEvent * @see org.springframework.context.event.EventPublicationInterceptor */ public interface ApplicationEventPublisher { /** * Notify all <strong>matching</strong> listeners registered with this * application of an application event. Events may be framework events * (such as RequestHandledEvent) or application-specific events. * @param event the event to publish * @see org.springframework.web.context.support.RequestHandledEvent */ void publishEvent(ApplicationEvent event); /** * Notify all <strong>matching</strong> listeners registered with this * application of an event. * <p>If the specified {@code event} is not an {@link ApplicationEvent}, * it is wrapped in a {@link PayloadApplicationEvent}. * @param event the event to publish * @since 4.2 * @see PayloadApplicationEvent */ void publishEvent(Object event); }
SimpleApplicationEventMulticaster是AbstractApplicationEventMulticaster具體的實現,可以配置異步調用以解決效率問題以及事件調用異常的處理方式。主要的邏輯不復雜,很多實現的細節都隱藏在抽象類中,后續會有分析,很容易看得懂 接下來把目光聚集在AbstractApplicationEventMulticaster類。
AbstractApplicationEventMulticaster抽象類是分析的重點,該類實現了大部分的核心功能,包括監聽者增加、刪除、根據發布的事件類型匹配監聽者,下面將結合容器刷新來串聯整個過程。
(1)、AbstractApplicationContext中refresh()是整個spring容器刷新的入口, 如果你看過spring源碼肯定非常熟悉這段代碼:
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // 獲取容器,具體實現類重載加載所有bean定義 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // 初始化事件廣播器 即上文的SimpleApplicationEventMulticaster 也可以重載該方法,注冊自定義的廣播器,實現定制化 initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // 開始注冊監聽器 registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // 完成最后容器工作 發布第一個容器刷新完成事件 finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }
(2) 、跟蹤initApplicationEventMulticaster()方法可以看出來,如果手動注入name為“applicationEventMulticaster”的bean 則spring會使用定制化的事件廣播器,否則會默認使用SimpleApplicationEventMulticaster。
/** * Name of the ApplicationEventMulticaster bean in the factory. * If none is supplied, a default SimpleApplicationEventMulticaster is used. * @see org.springframework.context.event.ApplicationEventMulticaster * @see org.springframework.context.event.SimpleApplicationEventMulticaster */ public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster"; /** * Initialize the ApplicationEventMulticaster. * Uses SimpleApplicationEventMulticaster if none defined in the context. * @see org.springframework.context.event.SimpleApplicationEventMulticaster */ protected void initApplicationEventMulticaster() { ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) { this.applicationEventMulticaster = beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class); if (logger.isTraceEnabled()) { logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]"); } } else { this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory); beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster); if (logger.isTraceEnabled()) { logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " + "[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]"); } } }
(3)、 此時事件廣播器已經注冊,再跟蹤registerListeners()方法。 該方法主要完成所有監聽者的注冊、 觸發最早起的事件通知,也是spring容器刷新階段較早期觸發的事件。
/** * 注冊所有的監聽器 */ protected void registerListeners() { // Register statically specified listeners first. // 注冊容器創建時期靜態賦值的監聽器 for (ApplicationListener<?> listener : getApplicationListeners()) { getApplicationEventMulticaster().addApplicationListener(listener); } // Do not initialize FactoryBeans here: We need to leave all regular beans // uninitialized to let post-processors apply to them! // 獲取所有監聽器的bean name 並注冊所有beanName 以待后續查找使用 String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false); for (String listenerBeanName : listenerBeanNames) { getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName); } // Publish early application events now that we finally have a multicaster... // 觸發最早起的事件通知 並且只會觸發一次 Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents; this.earlyApplicationEvents = null; if (earlyEventsToProcess != null) { for (ApplicationEvent earlyEvent : earlyEventsToProcess) { getApplicationEventMulticaster().multicastEvent(earlyEvent); } } }
(4)、跟蹤AbstractApplicationEventMulticaster#addApplicationListener 和 AbstractApplicationEventMulticaster#addApplicationListenerBean。 兩個方法都比較簡單,都是往監聽器持有者defaultRetriever注冊。
public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware { // 默認的監聽器持有者 private final ListenerRetriever defaultRetriever = new ListenerRetriever(false); // 使用緩存方式加快解析效率 final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<ListenerCacheKey, ListenerRetriever>(64); private ClassLoader beanClassLoader; private BeanFactory beanFactory; private Object retrievalMutex = this.defaultRetriever; //......... @Override public void addApplicationListener(ApplicationListener<?> listener) { synchronized (this.retrievalMutex) { // Explicitly remove target for a proxy, if registered already, // in order to avoid double invocations of the same listener. // 如果該類的代理類 需要將被代理類對象移除 防止產生兩次通知的現象 Object singletonTarget = AopProxyUtils.getSingletonTarget(listener); if (singletonTarget instanceof ApplicationListener) { this.defaultRetriever.applicationListeners.remove(singletonTarget); } this.defaultRetriever.applicationListeners.add(listener); this.retrieverCache.clear(); } } @Override public void addApplicationListenerBean(String listenerBeanName) { synchronized (this.retrievalMutex) { this.defaultRetriever.applicationListenerBeans.add(listenerBeanName); this.retrieverCache.clear(); } } //......... }
(5)、再分析最關鍵的SimpleApplicationEventMulticaster#multicastEvent(final ApplicationEvent event, ResolvableType eventType) 根據發布的事件類別找到匹配的ApplicationListener集合,並觸發通知。 串聯了整個事件通知流程:
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster { private Executor taskExecutor; private ErrorHandler errorHandler; // .......... @Override public void multicastEvent(ApplicationEvent event) { multicastEvent(event, resolveDefaultEventType(event)); } @Override public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) { // 使用spring提供的ResolvableType類型解析類 匹配發布事件類型獲得對應的監聽器 ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) { // 可配置的異步處理 和 異常處理機制 Executor executor = getTaskExecutor(); if (executor != null) { executor.execute(new Runnable() { @Override public void run() { invokeListener(listener, event); } }); } else { invokeListener(listener, event); } } } private ResolvableType resolveDefaultEventType(ApplicationEvent event) { return ResolvableType.forInstance(event); } protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) { ErrorHandler errorHandler = getErrorHandler(); if (errorHandler != null) { try { doInvokeListener(listener, event); } catch (Throwable err) { errorHandler.handleError(err); } } else { doInvokeListener(listener, event); } } @SuppressWarnings({"unchecked", "rawtypes"}) private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) { try { listener.onApplicationEvent(event); } catch (ClassCastException ex) { String msg = ex.getMessage(); if (msg == null || matchesClassCastMessage(msg, event.getClass())) { // Possibly a lambda-defined listener which we could not resolve the generic event type for // -> let's suppress the exception and just log a debug message. Log logger = LogFactory.getLog(getClass()); if (logger.isDebugEnabled()) { logger.debug("Non-matching event type for listener: " + listener, ex); } } else { throw ex; } } } private boolean matchesClassCastMessage(String classCastMessage, Class<?> eventClass) { // On Java 8, the message starts with the class name: "java.lang.String cannot be cast..." if (classCastMessage.startsWith(eventClass.getName())) { return true; } // On Java 11, the message starts with "class ..." a.k.a. Class.toString() if (classCastMessage.startsWith(eventClass.toString())) { return true; } // On Java 9, the message used to contain the module name: "java.base/java.lang.String cannot be cast..." int moduleSeparatorIndex = classCastMessage.indexOf('/'); if (moduleSeparatorIndex != -1 && classCastMessage.startsWith(eventClass.getName(), moduleSeparatorIndex + 1)) { return true; } // Assuming an unrelated class cast failure... return false; } }
跟進AbstractApplicationEventMulticaster#getApplicationListeners(ApplicationEvent event, ResolvableType eventType)獲取所有符合條件的監聽器,涉及到spring的ResolvableType類型可以 點擊這里 進行詳細了解。
public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware { private final ListenerRetriever defaultRetriever = new ListenerRetriever(false); final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<ListenerCacheKey, ListenerRetriever>(64); private ClassLoader beanClassLoader; private BeanFactory beanFactory; private Object retrievalMutex = this.defaultRetriever; // 。。。。。。。。。 /** * 根據事件類型找到匹配的監聽器集合 充分緩存機制解決效率問題 * * Return a Collection of ApplicationListeners matching the given * event type. Non-matching listeners get excluded early. * @param event the event to be propagated. Allows for excluding * non-matching listeners early, based on cached matching information. * @param eventType the event type * @return a Collection of ApplicationListeners * @see org.springframework.context.ApplicationListener */ protected Collection<ApplicationListener<?>> getApplicationListeners( ApplicationEvent event, ResolvableType eventType) { Object source = event.getSource(); Class<?> sourceType = (source != null ? source.getClass() : null); ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType); // Quick check for existing entry on ConcurrentHashMap... ListenerRetriever retriever = this.retrieverCache.get(cacheKey); if (retriever != null) { return retriever.getApplicationListeners(); } if (this.beanClassLoader == null || (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) && (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) { // Fully synchronized building and caching of a ListenerRetriever // 防止線程並發同步加載 synchronized (this.retrievalMutex) { retriever = this.retrieverCache.get(cacheKey); // 再次檢查 防止並發導致多次計算 if (retriever != null) { return retriever.getApplicationListeners(); } retriever = new ListenerRetriever(true); Collection<ApplicationListener<?>> listeners = retrieveApplicationListeners(eventType, sourceType, retriever); this.retrieverCache.put(cacheKey, retriever); return listeners; } } else { // No ListenerRetriever caching -> no synchronization necessary return retrieveApplicationListeners(eventType, sourceType, null); } } /** * 實際的事件類解析邏輯 結合上文提到的監聽器持有者defaultRetriever 使用spring提供的ResolvableType類進行類型匹配 * 最后找到符合條件的監聽器 * Actually retrieve the application listeners for the given event and source type. * @param eventType the event type * @param sourceType the event source type * @param retriever the ListenerRetriever, if supposed to populate one (for caching purposes) * @return the pre-filtered list of application listeners for the given event and source type */ private Collection<ApplicationListener<?>> retrieveApplicationListeners( ResolvableType eventType, Class<?> sourceType, ListenerRetriever retriever) { List<ApplicationListener<?>> allListeners = new ArrayList<ApplicationListener<?>>(); Set<ApplicationListener<?>> listeners; Set<String> listenerBeans; synchronized (this.retrievalMutex) { listeners = new LinkedHashSet<ApplicationListener<?>>(this.defaultRetriever.applicationListeners); listenerBeans = new LinkedHashSet<String>(this.defaultRetriever.applicationListenerBeans); } for (ApplicationListener<?> listener : listeners) { if (supportsEvent(listener, eventType, sourceType)) { if (retriever != null) { retriever.applicationListeners.add(listener); } allListeners.add(listener); } } if (!listenerBeans.isEmpty()) { BeanFactory beanFactory = getBeanFactory(); for (String listenerBeanName : listenerBeans) { try { Class<?> listenerType = beanFactory.getType(listenerBeanName); if (listenerType == null || supportsEvent(listenerType, eventType)) { ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class); if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) { if (retriever != null) { retriever.applicationListenerBeans.add(listenerBeanName); } allListeners.add(listener); } } } catch (NoSuchBeanDefinitionException ex) { // Singleton listener instance (without backing bean definition) disappeared - // probably in the middle of the destruction phase } } } AnnotationAwareOrderComparator.sort(allListeners); return allListeners; } protected boolean supportsEvent(Class<?> listenerType, ResolvableType eventType) { if (GenericApplicationListener.class.isAssignableFrom(listenerType) || SmartApplicationListener.class.isAssignableFrom(listenerType)) { return true; } ResolvableType declaredEventType = GenericApplicationListenerAdapter.resolveDeclaredEventType(listenerType); return (declaredEventType == null || declaredEventType.isAssignableFrom(eventType)); } protected boolean supportsEvent(ApplicationListener<?> listener, ResolvableType eventType, Class<?> sourceType) { GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ? (GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener)); return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType)); } /** * Cache key for ListenerRetrievers, based on event type and source type. */ private static final class ListenerCacheKey implements Comparable<ListenerCacheKey> { private final ResolvableType eventType; private final Class<?> sourceType; public ListenerCacheKey(ResolvableType eventType, Class<?> sourceType) { this.eventType = eventType; this.sourceType = sourceType; } // ....... } /** * 監聽器持有者 實際從bean容器獲得監聽器的bean實例 * Helper class that encapsulates a specific set of target listeners, * allowing for efficient retrieval of pre-filtered listeners. * <p>An instance of this helper gets cached per event type and source type. */ private class ListenerRetriever { public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<ApplicationListener<?>>(); public final Set<String> applicationListenerBeans = new LinkedHashSet<String>(); private final boolean preFiltered; public ListenerRetriever(boolean preFiltered) { this.preFiltered = preFiltered; } public Collection<ApplicationListener<?>> getApplicationListeners() { List<ApplicationListener<?>> allListeners = new ArrayList<ApplicationListener<?>>( this.applicationListeners.size() + this.applicationListenerBeans.size()); allListeners.addAll(this.applicationListeners); if (!this.applicationListenerBeans.isEmpty()) { BeanFactory beanFactory = getBeanFactory(); for (String listenerBeanName : this.applicationListenerBeans) { try { ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class); if (this.preFiltered || !allListeners.contains(listener)) { allListeners.add(listener); } } catch (NoSuchBeanDefinitionException ex) { // Singleton listener instance (without backing bean definition) disappeared - // probably in the middle of the destruction phase } } } AnnotationAwareOrderComparator.sort(allListeners); return allListeners; } } }
(6)、最后finishRefresh()發布容器刷新成功事件
/** * Finish the refresh of this context, invoking the LifecycleProcessor's * onRefresh() method and publishing the * {@link org.springframework.context.event.ContextRefreshedEvent}. */ protected void finishRefresh() { // Initialize lifecycle processor for this context. initLifecycleProcessor(); // Propagate refresh to lifecycle processor first. getLifecycleProcessor().onRefresh(); // Publish the final event. // 發布容器刷新成功事件 publishEvent(new ContextRefreshedEvent(this)); // Participate in LiveBeansView MBean, if active. LiveBeansView.registerApplicationContext(this); }
(7)、跟蹤上述的publishEvent(Object event, ResolvableType eventType)方法, spring容器實現了ApplicationEventPublisher接口 並委托事件廣播器AbstractApplicationEventMulticaster完成事件到發布的過程。
// 發布一個 ApplicationEvent 事件 @Override public void publishEvent(ApplicationEvent event) { publishEvent(event, null); } // 發布一個ApplicationEvent事件 如果非ApplicationEvent類型事件,則默認被封裝成PayloadApplicationEvent事件 傳遞實體 @Override public void publishEvent(Object event) { publishEvent(event, null); } protected void publishEvent(Object event, ResolvableType eventType) { Assert.notNull(event, "Event must not be null"); if (logger.isTraceEnabled()) { logger.trace("Publishing event in " + getDisplayName() + ": " + event); } // Decorate event as an ApplicationEvent if necessary // 如果非ApplicationEvent類型事件,則默認被封裝成PayloadApplicationEvent事件 傳遞實體 ApplicationEvent applicationEvent; if (event instanceof ApplicationEvent) { applicationEvent = (ApplicationEvent) event; } else { applicationEvent = new PayloadApplicationEvent<Object>(this, event); if (eventType == null) { eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType(); } } // Multicast right now if possible - or lazily once the multicaster is initialized if (this.earlyApplicationEvents != null) { this.earlyApplicationEvents.add(applicationEvent); } else { getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); } // Publish event via parent context as well... // 如果存在父親容器則在容器發布該事件 if (this.parent != null) { if (this.parent instanceof AbstractApplicationContext) { ((AbstractApplicationContext) this.parent).publishEvent(event, eventType); } else { this.parent.publishEvent(event); } } }
源碼分析到這里基本串聯了整個流程,用一張圖說明容器刷新時候事件調用鏈圖,展示整個過程的數據流:

(c) 依據spring機制實現事件的發布/訂閱 定制化框架。
實際項目中會有一些定制化的需求,spring強大之處不僅僅是Bean的托管,還提供很多拓展機制,例如Bean生命周期各個的接口 點擊這里了解更多,還有本文的主題ApplicationEvent發布特定的事件機制定制化框架。常見的spring內嵌Tomcat等Servlet容器提供Http服務 則優雅down機器很重要,需要等待所有業務請求處理完畢再關閉容器。 下面是一個簡單的demo,也是拓展事件機制的基礎:
實際傳遞DTO
@Data @AllArgsConstructor @ToString public class EventDto { private String name ; }
發布的事件繼承自 ApplicationEvent
@Getter public class DemoApplicationEvent extends ApplicationEvent { private String demo ; public DemoApplicationEvent(String demo, EventDto eventDto) { super(eventDto); this.demo = demo ; } }
事件監聽者
@Component public class DemoApplicationListener implements ApplicationListener<DemoApplicationEvent> { /** * Handle an application event. * * @param event the event to respond to */ @Override public void onApplicationEvent(DemoApplicationEvent event) { System.out.println(event.getDemo() + " -- " + event.getSource()); } }
啟動springboot:
@SpringBootApplication public class MqStarterApplication { public static void main(String[] args) { ConfigurableApplicationContext ctx = SpringApplication.run(MqStarterApplication.class, args); ctx.publishEvent(new DemoApplicationEvent("hello", new EventDto("pig"))); } }
運行結果:
hello -- EventDto(name=pig)
