众所周知,在领域驱动设计中聚合根的设计有一个很重要的原则:
每个事务应该只更新一个聚合根。
然而,在我们的实际业务中,经常会出现涉及到多个聚合根同时更新的场景。这无疑对于系统设计提出了不小的挑战,设计一个粒度适中,又能符合业务要求的聚合根并不是一件容易的事情。领域事件为我们提供了一种非常简单的解决方案:在聚合根完成更新后产生一个新的事件并广播出去,由其他订阅该事件的订阅者完成其他聚合根的更新,这样就解除了聚合根之间的耦合。
如果是多个域之间的事件交互,可以考虑使用kafka等消息中间件实现,今天本文介绍的是spring中的事件编程,适合用在单个域中包含多个聚合根的场景。
在java中,本身就已经自带了对事件编程的支持(java.util.EventObject),早期的GUI编程中使用事件编程会比较多。而spring又对java的事件做了进一步扩充,本文会通过简单梳理事件驱动编程在spring boot中的实践,学习其思想和技巧,为我们更好的设计代码架构提供参考。
我们先看一下spring事件编程的简单实践。
我们用spring的事件机制,模拟实现一个简单的业务场景:
在用户完成下单动作以后,我们需要给到用户邮件通知,这里涉及邮件和订单两个模块,我们在下单动作完成后,发布事件,再由邮件模块发送邮件给用户。
入口函数 :
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class Main {
@Autowired
private OrderService orderService;
@Test
public void placeOrder(){
Order order = new Order();
order.setUserName("lisi");
order.setEmail("lisi@qq.com");
orderService.placeOrder(order);
log.info("下单完成");
}
}
订单实体:
@Getter
@Setter
@ToString
public class Order {
private String userName;
private String email;
}
订单service:
我们在完成订单保存后,并没有显式调用邮件发送,而是发布了一个“订单事件”,后面交给其他感兴趣的订阅模块去做相应处理,实现了很好的解耦。发布事件借助spring的ApplicationEventPublisher完成,后面会再细说它的具体实现。
@Slf4j
@Service
public class OrderService {
@Autowired
private ApplicationEventPublisher publisher;
public void placeOrder(Order order) {
this.saveOrder(order);
log.info("发布下单事件成功:[{}]", order);
publisher.publishEvent(order);
}
private void saveOrder(Order order) {
log.info("保存订单");
}
}
邮件监听器:
我们只需要在业务方法上加@EventListener注解即可实现监听。
注意:监听器需要是被spring管理的bean,@Service或者@Component等都可以
@Slf4j
@Service
public class EmailListener {
private void sendEmail(Order order){
//发邮件
log.info("发送邮件");
//throw new RuntimeException("模拟发送邮件异常");
}
@EventListener
public void placeOrderNotice(Order order){
log.info("邮件监听器收到通知[{}]", order);
this.sendEmail(order);
}
}
运行结果:
可以看到下单后,监听器拿到了订单信息,并成功发送了邮件。
需要注意的是,这里的监听器是同步执行的,如果发送邮件出现异常,那么会导致主线程后面逻辑也走不下去,如果包含事务,那么事务也会回滚。
例如,我们模拟在邮件发送中出现异常,再看结果:
private void sendEmail(Order order){
//发邮件
log.info("发送邮件");
throw new RuntimeException("模拟发送邮件异常");
}
可以看到,发送邮件抛出异常,导致日志[下单完成],也没有正常执行,此处若有事务也会回滚;
要想订阅者的逻辑不影响主线程,我们可以使用异步执行的方式,在springBoot入口类添加 @EnableAsync注解,并在监听器中添加注解 @Async 即可。
@Slf4j
@EnableAsync
@RunWith(SpringRunner.class)
@SpringBootTest
public class AsyncMain {
......
}
@Async
@EventListener
public void placeOrderNotice1(Order order){
log.info("异步邮件监听器[1]收到通知[{}]", order);
this.sendEmail1(order);
}
如不指定Executor,则会使用默认的applicationTaskExecutor,我们也可以自定义Executor:
@Bean
public ThreadPoolTaskExecutor myExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(50);
return executor;
}
如果有多个订阅者,还可以通过 @Order 注解指定收到通知的顺序,数字越小优先级越高,本例我们指定两个订阅者。
@Async
@EventListener
@org.springframework.core.annotation.Order(1)
public void placeOrderNotice1(Order order){
log.info("异步邮件监听器[1]收到通知[{}]", order);
this.sendEmail1(order);
}
private void sendEmail1(Order order){
log.info("发送邮件1");
throw new RuntimeException("异步发送邮件[1]异常");
}
@Async
@EventListener
@org.springframework.core.annotation.Order(2)
public void placeOrderNotice2(Order order){
log.info("异步邮件监听器[2]收到通知[{}]", order);
this.sendEmail2(order);
}
private void sendEmail2(Order order){
log.info("发送邮件2");
}
运行结果:
可以看到这里使用了自定义的myExecutor去异步化执行监听器中的逻辑,而且异步化之后,监听器[1]执行出现异常,没有影响主线程和监听器[2]。
在spring中的事件编程,其实就是我们常说的设计模式中的观察者模式的实现,也看到有人说观察者模式=发布订阅模式,其实这两者是有差别的。
在观察者模式中,观察者需要直接订阅目标事件;在目标发出内容改变的事件后,直接接收事件并作出响应,如下:
╭─────────────╮ Fire Event ╭──────────────╮
│ │─────────────>│ │
│ Subject │ │ Observer │
│ │<─────────────│ │
╰─────────────╯ Subscribe ╰──────────────╯
在发布订阅模式中,发布者和订阅者之间多了一个发布通道;一方面从发布者接收事件,另一方面向订阅者发布事件;订阅者需要从事件通道订阅事件,以此避免发布者和订阅者之间产生依赖关系,如下:
╭─────────────╮ ╭───────────────╮ Fire Event ╭──────────────╮
│ │ Publish Event │ │───────────────>│ │
│ Publisher │────────────────>│ Event Channel │ │ Subscriber │
│ │ │ │<───────────────│ │
╰─────────────╯ ╰───────────────╯ Subscribe ╰──────────────╯
发布-订阅可以认为是观察者模式的一种实现。在spring中的事件编程,都是观察者模式的实现,没有涉及消息中间件,如果业务足够复杂,可以考虑引入第三方消息中间件来实现领域事件。
spring这么多年经久不衰,它的源码可谓是经典中的经典,里面的抽象和设计都是值得我们学习和研究的,下面我们就一起看一下spring是如何实现事件驱动的。
Spring事件机制涉及的重要的类主要有以下四个:
然后我们开始看具体的源码。我们从最简单的发布事件开始跟踪,可以看到,publishEvent方法的逻辑最终是在AbstractApplicationContext.java中,而AbstractApplicationContext是ApplicationEventPublisher的实现类:
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
Object applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent)event;
} else {
applicationEvent = new PayloadApplicationEvent(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
}
}
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
} else {
//注意这里,委托给ApplicationEventMulticaster发布器去处理了
this.getApplicationEventMulticaster().multicastEvent((ApplicationEvent)applicationEvent, eventType);
}
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext)this.parent).publishEvent(event, eventType);
} else {
this.parent.publishEvent(event);
}
}
}
我们再看发布器的初始化代码initApplicationEventMulticaster:
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
if (beanFactory.containsLocalBean("applicationEventMulticaster")) {
this.applicationEventMulticaster = (ApplicationEventMulticaster)beanFactory.getBean("applicationEventMulticaster", ApplicationEventMulticaster.class);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
}
} else {
//注意这里,用实现类SimpleApplicationEventMulticaster去处理发布事件
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton("applicationEventMulticaster", this.applicationEventMulticaster);
if (this.logger.isTraceEnabled()) {
this.logger.trace("No 'applicationEventMulticaster' bean, using [" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
}
}
}
再看SimpleApplicationEventMulticaster中的发布事件方法multicastEvent():
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
Executor executor = this.getTaskExecutor();
Iterator var5 = this.getApplicationListeners(event, type).iterator();
while(var5.hasNext()) {
ApplicationListener<?> listener = (ApplicationListener)var5.next();
if (executor != null) {
//异步的执行方法
executor.execute(() -> {
this.invokeListener(listener, event);
});
} else {
//同步的执行方法
this.invokeListener(listener, event);
}
}
}
再看invokeListener:
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
ErrorHandler errorHandler = this.getErrorHandler();
if (errorHandler != null) {
try {
//看这个
this.doInvokeListener(listener, event);
} catch (Throwable var5) {
errorHandler.handleError(var5);
}
} else {
//看这个
this.doInvokeListener(listener, event);
}
}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
//实际上执行了监听器的onApplicationEvent方法
listener.onApplicationEvent(event);
} catch (ClassCastException var6) {
String msg = var6.getMessage();
if (msg != null && !this.matchesClassCastMessage(msg, event.getClass())) {
throw var6;
}
Log logger = LogFactory.getLog(this.getClass());
if (logger.isTraceEnabled()) {
logger.trace("Non-matching event type for listener: " + listener, var6);
}
}
}
总结一下上面的流程:ApplicationEventPublisher发布事件是委托给ApplicationEventMulticaster去执行的,ApplicationEventMulticaster的实现类SimpleApplicationEventMulticaster最终会调用ApplicationListener的onApplicationEvent方法。
那么问题来了,我们的监听器实际上只用了一个@EventListener注解,并没有onApplicationEvent方法,spring又是如何来识别的呢?
众所周知,spring容器启动时,会有一个refresh的动作(org.springframework.boot.SpringApplication/#run()),如下:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
//准备变量
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//注意这里:刷新容器
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
再看refreshContext()里面的内容:
private void refreshContext(ConfigurableApplicationContext context) {
//再看下面
this.refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
} catch (AccessControlException var3) {
}
}
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
//调用了applicationContext.refresh()
((AbstractApplicationContext)applicationContext).refresh();
}
里面实际调用了applicationContext的refresh,applicationContext就是我们前面调用publishEvent的类,也是ApplicationEventPublisher的实现类,我们来看看AbstractApplicationContext的refresh做了什么:
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
//初始化事件发布器
this.initApplicationEventMulticaster();
this.onRefresh();
//注册事件监听器
this.registerListeners();
//我们要关注的和注解有关的地方
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
我们来看finishBeanFactoryInitialization()方法:
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
if (beanFactory.containsBean("conversionService") && beanFactory.isTypeMatch("conversionService", ConversionService.class)) {
beanFactory.setConversionService((ConversionService)beanFactory.getBean("conversionService", ConversionService.class));
}
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver((strVal) -> {
return this.getEnvironment().resolvePlaceholders(strVal);
});
}
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
String[] var3 = weaverAwareNames;
int var4 = weaverAwareNames.length;
for(int var5 = 0; var5 < var4; ++var5) {
String weaverAwareName = var3[var5];
this.getBean(weaverAwareName);
}
beanFactory.setTempClassLoader((ClassLoader)null);
beanFactory.freezeConfiguration();
//注意这里
beanFactory.preInstantiateSingletons();
}
继续跟进preInstantiateSingletons():
public void preInstantiateSingletons() throws BeansException {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Pre-instantiating singletons in " + this);
}
List<String> beanNames = new ArrayList(this.beanDefinitionNames);
Iterator var2 = beanNames.iterator();
while(true) {
String beanName;
Object bean;
do {
while(true) {
RootBeanDefinition bd;
do {
do {
do {
if (!var2.hasNext()) {
var2 = beanNames.iterator();
while(var2.hasNext()) {
beanName = (String)var2.next();
Object singletonInstance = this.getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton)singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(() -> {
//注意这里
smartSingleton.afterSingletonsInstantiated();
return null;
}, this.getAccessControlContext());
} else {
//注意这里
smartSingleton.afterSingletonsInstantiated();
}
}
}
return;
}
beanName = (String)var2.next();
bd = this.getMergedLocalBeanDefinition(beanName);
} while(bd.isAbstract());
} while(!bd.isSingleton());
} while(bd.isLazyInit());
if (this.isFactoryBean(beanName)) {
bean = this.getBean("&" + beanName);
break;
}
this.getBean(beanName);
}
} while(!(bean instanceof FactoryBean));
FactoryBean<?> factory = (FactoryBean)bean;
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
SmartFactoryBean var10000 = (SmartFactoryBean)factory;
((SmartFactoryBean)factory).getClass();
isEagerInit = (Boolean)AccessController.doPrivileged(var10000::isEagerInit, this.getAccessControlContext());
} else {
isEagerInit = factory instanceof SmartFactoryBean && ((SmartFactoryBean)factory).isEagerInit();
}
if (isEagerInit) {
this.getBean(beanName);
}
}
}
再来看SmartInitializingSingleton.afterSingletonsInstantiated(),SmartInitializingSingleton这个接口有几个实现类,明显EventListenerMethodProcessor就是我们所关注的:
再看EventListenerMethodProcessor.afterSingletonsInstantiated():
public void afterSingletonsInstantiated() {
ConfigurableListableBeanFactory beanFactory = this.beanFactory;
Assert.state(this.beanFactory != null, "No ConfigurableListableBeanFactory set");
String[] beanNames = beanFactory.getBeanNamesForType(Object.class);
String[] var3 = beanNames;
int var4 = beanNames.length;
for(int var5 = 0; var5 < var4; ++var5) {
String beanName = var3[var5];
if (!ScopedProxyUtils.isScopedTarget(beanName)) {
Class type = null;
try {
type = AutoProxyUtils.determineTargetClass(beanFactory, beanName);
} catch (Throwable var10) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Could not resolve target class for bean with name '" + beanName + "'", var10);
}
}
if (type != null) {
if (ScopedObject.class.isAssignableFrom(type)) {
try {
Class<?> targetClass = AutoProxyUtils.determineTargetClass(beanFactory, ScopedProxyUtils.getTargetBeanName(beanName));
if (targetClass != null) {
type = targetClass;
}
} catch (Throwable var11) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Could not resolve target bean for scoped proxy '" + beanName + "'", var11);
}
}
}
try {
//我们的关注点
this.processBean(beanName, type);
} catch (Throwable var9) {
throw new BeanInitializationException("Failed to process @EventListener annotation on bean with name '" + beanName + "'", var9);
}
}
}
}
}
再看processBean():
private void processBean(String beanName, Class<?> targetType) {
//处理@EventListener注解的方法
if (!this.nonAnnotatedClasses.contains(targetType) && AnnotationUtils.isCandidateClass(targetType, EventListener.class) && !isSpringContainerClass(targetType)) {
Map annotatedMethods = null;
try {
annotatedMethods = MethodIntrospector.selectMethods(targetType, (methodx) -> {
return (EventListener)AnnotatedElementUtils.findMergedAnnotation(methodx, EventListener.class);
});
} catch (Throwable var12) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Could not resolve methods for bean with name '" + beanName + "'", var12);
}
}
if (CollectionUtils.isEmpty(annotatedMethods)) {
this.nonAnnotatedClasses.add(targetType);
if (this.logger.isTraceEnabled()) {
this.logger.trace("No @EventListener annotations found on bean class: " + targetType.getName());
}
} else {
ConfigurableApplicationContext context = this.applicationContext;
Assert.state(context != null, "No ApplicationContext set");
List<EventListenerFactory> factories = this.eventListenerFactories;
Assert.state(factories != null, "EventListenerFactory List not initialized");
Iterator var6 = annotatedMethods.keySet().iterator();
while(true) {
while(var6.hasNext()) {
Method method = (Method)var6.next();
Iterator var8 = factories.iterator();
while(var8.hasNext()) {
EventListenerFactory factory = (EventListenerFactory)var8.next();
if (factory.supportsMethod(method)) {
Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
//EventListenerFactory这里根据我们@EventListener注解的方法构建出了ApplicationListener
ApplicationListener<?> applicationListener = factory.createApplicationListener(beanName, targetType, methodToUse);
if (applicationListener instanceof ApplicationListenerMethodAdapter) {
((ApplicationListenerMethodAdapter)applicationListener).init(context, this.evaluator);
}
context.addApplicationListener(applicationListener);
break;
}
}
}
if (this.logger.isDebugEnabled()) {
this.logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" + beanName + "': " + annotatedMethods);
}
break;
}
}
}
}
我们再来看EventListenerFactory.createApplicationListener():
public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {
//这里其实是使用了适配器模式,构建了一个适配器返回
return new ApplicationListenerMethodAdapter(beanName, type, method);
}
可以看到ApplicationListenerMethodAdapter适配器继承自ApplicationListener
到这里,其实就已经很清晰了,在spring容器启动时applicationContext会调用refresh方法,然后一路跟踪到注解@EventListenser的处理类EventListenerMethodProcessor中的afterSingletonsInstantiated()方法,再到EventListenerMethodProcessor.processBean()中,找到了对针对注解@EventListenser的处理逻辑:
根据传入的beanName通过EventListenerFactory构建出一个ApplicationListenerMethodAdapter适配器,该适配器也是ApplicationListenser的实现,也就把我们用@EventListenser注解的方法适配成了ApplicationListenser的实现。
最后我们再看看,这个适配器是如何实现适配的。我们知道,发布事件时会调用onApplicationEvent方法,我们来看看ApplicationListenerMethodAdapter中的onApplicationEvent实现:
public void onApplicationEvent(ApplicationEvent event) {
//看下面的具体方法
this.processEvent(event);
}
public void processEvent(ApplicationEvent event) {
Object[] args = this.resolveArguments(event);
if (this.shouldHandle(event, args)) {
//这里通过反射来执行被@EventListenser注解的方法
Object result = this.doInvoke(args);
if (result != null) {
this.handleResult(result);
} else {
this.logger.trace("No result object given - no result to handle");
}
}
}
@Nullable
protected Object doInvoke(Object... args) {
Object bean = this.getTargetBean();
ReflectionUtils.makeAccessible(this.method);
try {
// this.method是构造adapter时传入的变量,就是被@EventListenser注解的方法
return this.method.invoke(bean, args);
} catch (IllegalArgumentException var6) {
this.assertTargetBean(this.method, bean, args);
throw new IllegalStateException(this.getInvocationErrorMessage(bean, var6.getMessage(), args), var6);
} catch (IllegalAccessException var7) {
throw new IllegalStateException(this.getInvocationErrorMessage(bean, var7.getMessage(), args), var7);
} catch (InvocationTargetException var8) {
Throwable targetException = var8.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException)targetException;
} else {
String msg = this.getInvocationErrorMessage(bean, "Failed to invoke event listener method", args);
throw new UndeclaredThrowableException(targetException, msg);
}
}
}
//ApplicationListenerMethodAdapter的构造方法
public ApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method) {
this.beanName = beanName;
//这个方法就是被@EventListenser注解的方法
this.method = BridgeMethodResolver.findBridgedMethod(method);
this.targetMethod = !Proxy.isProxyClass(targetClass) ? AopUtils.getMostSpecificMethod(method, targetClass) : this.method;
this.methodKey = new AnnotatedElementKey(this.targetMethod, targetClass);
EventListener ann = (EventListener)AnnotatedElementUtils.findMergedAnnotation(this.targetMethod, EventListener.class);
this.declaredEventTypes = resolveDeclaredEventTypes(method, ann);
this.condition = ann != null ? ann.condition() : null;
this.order = resolveOrder(this.targetMethod);
}
总结下,ApplicationListenerMethodAdapter适配器构造时会传入被@EventListenser注解的方法,然后在onApplicationEvent()中通过反射,调用传入的方法,就实现了注解方式的事件发布。
本文简介了spring的事件驱动编程实践,以及源码实现,希望能给大家的使用提供一些借鉴。事件驱动为我们提供了一种十分优秀的解耦的手段,可以将大量复杂的业务逻辑拆分到不同的订阅者中去处理,让我们的代码更符合SRP原则。而且,spring已经为我们在java的事件基础上提供了优秀的二次封装,推荐大家在合适的业务场景中去尝试一下。
—by eric02.li
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/vipshop_fin_dev/article/details/120115764
内容来源于网络,如有侵权,请联系作者删除!