一 前言
在我们写代码的时候,通常需要考虑到代码的耦合性,因为低耦合的代码有利于我们后续的维护和迭代,而Spring Event可以说是一个降低代码耦合度的神器,配合@Async注解更是能够轻松实现异步。今天我们就一起来了解一下Spring Event。
二:如何使用Spring Event
我们以一个简单的业务场景为例:
用户注册账号之后,我们需要赠送用户500积分
1.定义Event事件类和DTO传输数据对象
首先我们需要定义一个增加积分的事件,而这个类需要继承ApplicationEvent类。
代码语言:javascript复制public class AddCreditEvent extends ApplicationEvent {
private static final long serialVersionUID = 303142463705896963L;
public AddCreditEvent(Object source) {
super(source);
}
}
代码语言:javascript复制@Data
public class AddCreditDTO {
private Integer userId;
private Integer creditAmount;
}
2.发布一个增加积分的事件
我们在用户进行注册之后,需要赠送用户积分,因此,我们在用户注册之后需要发布一个增加积分的事件。我们在UserServiceImpl中实现ApplicationEventPublisherAware
接口,实现setApplicationEventPublisher()方法,获取到applicationEventPublisher对象,通过applicationEventPublisher对象的publishEvent()方法就可以发布对应的事件了。
public class UserServiceImpl implements UserService, ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
@Override
public void register(RegisterReqDTO reqDTO) {
//注册用户
registerUser(reqDTO);
//发布增加积分事件 新增积分
AddCreditDTO addCreditDTO = new AddCreditDTO();
addCreditDTO.setUserId(user.getId());
addCreditDTO.setCreditAmount(500);
applicationEventPublisher.publishEvent(new AddCreditEvent(addCreditDTO));
}
}
3.创建监听类监听发布的事件
发布完事件之后,我们需要有对应的监听类去监听我们发布的事件,在监听类中执行我们对应的业务逻辑。监听类需要实现ApplicationListener类,并且设置泛型为我们发布的事件类型,同时我们需要将监听类交给Spring管理(所以我们加上@Component注解)。这样在onApplicationEvent()方法中就可以执行我们的业务逻辑了。
代码语言:javascript复制@Component
public class AddCreditEventListener implements ApplicationListener<AddCreditEvent> {
@Override
public void onApplicationEvent(AddCreditEvent event) {
Object source = event.getSource();
AddCreditDTO addCreditDTO = (AddCreditDTO)source;
//新增积分业务代码
....
}
}
三:配合@Async注解实现异步
1.启动类上添加@EnableAsync注解
在启动类上添加@EnableAsync注解
代码语言:javascript复制@SpringBootApplication
@EnableAsync
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class,args);
}
}
2.在onApplicationEvent()方法上添加@Async注解
在onApplicationEvent方法上添加@Async注解就可以轻松实现异步了,但是并不推荐直接使用@Async注解,可以配置一个自定义线程池,根据业务以及系统资源配置好最大线程数,核心线程数,阻塞队列等线程池参数。
注:为什么不推荐直接使用@Async?因为在Springboot环境中,@Async默认使用的线程池最大线程数是Integer.MAX,并且阻塞队列的大小也是Integer.MAX,这显然是不合理的,所以我们最好自己定义线程池,然后指定@Async的value属性。
代码语言:javascript复制@Component
public class AddCreditEventListener implements ApplicationListener<AddCreditEvent> {
@Override
@Async
public void onApplicationEvent(AddCreditEvent event) {
Object source = event.getSource();
AddCreditDTO addCreditDTO = (AddCreditDTO)source;
//新增积分业务代码
....
}
}
四:利用事件机制完成业务逻辑有什么好处?
1.降低代码的耦合度
如果我需要新增积分,那么我就发布一个新增积分的事件,需要成为会员,那么我就发布一个成为会员的事件,通过不同的事件,将业务逻辑解耦,只需要发布事件,不需要关注具体的实现逻辑,代码的条理更清晰。方便以后的扩展与维护工作。
2.增强代码的复用性
例如注册用户可能需要新增积分,购买商品之后也需要新增积分,那么我们就都可以通过发布一个新增积分的事件来完成了。代码的复用性得到了很大的提高。
五:Spring Event的实现原理
1.首先在Spring容器启动时,在Application的refresh()方法中会调用prepareBeanFactory()方法,在prepareBeanFactory()方法中会注册一个ApplicationListenerDetector的类,ApplicationListenerDetector实现了BeanPostProcessor,在postProcessAfterInitialization()方法中保存所有的实现了ApplicationListener的类。
代码语言:javascript复制protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
...
// Register early post-processor for detecting inner beans as ApplicationListeners.
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
....
}
代码语言:javascript复制public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof ApplicationListener) {
// potentially not detected as a listener by getBeanNamesForType retrieval
Boolean flag = this.singletonNames.get(beanName);
if (Boolean.TRUE.equals(flag)) {
// singleton bean (top-level or inner): register on the fly
//保存所有实现了ApplicationListener的类
this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);
}
else if (Boolean.FALSE.equals(flag)) {
if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) {
// inner bean with other scope - can't reliably process events
logger.warn("Inner bean '" beanName "' implements ApplicationListener interface "
"but is not reachable for event multicasting by its containing ApplicationContext "
"because it does not have singleton scope. Only top-level listener beans are allowed "
"to be of non-singleton scope.");
}
this.singletonNames.remove(beanName);
}
}
return bean;
}
2.refresh()方法会调用initApplicationEventMulticaster()方法,initApplicationEventMulticaster()方法的作用是看Spring容器中是否存在ApplicationEventMulticaster广播器,如果不存在,那么就创建一个SimpleApplicationEventMulticaster的广播器,并且加入到Spring容器中。
代码语言:javascript复制 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 {
//如果不存在广播器 那么久创建一个SimpleApplicationEventMulticaster的广播器 并注册到Spring容器中
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.refresh()方法中调用registerListeners()方法,registerListeners()方法作用就是将ApplicationEvent绑定到ApplicationEventMulticaster广播器中。
代码语言:javascript复制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!
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 (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}
4.有了前面的准备,接下来我们可以看applicationEventPublisher.publishEvent()方法了。publishEvent()方法的核心作用就是调用ApplicationEventMulticaster广播器的multicastEvent()方法。
代码语言:javascript复制protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
// Decorate event as an ApplicationEvent if necessary
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<>(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);
}
}
}
multicastEvent()
multicastEvent方法会找到所有监听对应事件的监听类,调用invokeListener(listener, event)方法(这里默认线程池为空,所以默认是同步执行的)。
代码语言:javascript复制public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
invokeListener()
invokeListener()方法的作用就是调用doInvokeListener()方法,所以我们看doInvokeListener()方法。
代码语言:javascript复制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);
}
}
doInvokeListener()
doInvokeListener()方法就是调用对应监听器的onApplicationEvent方法。
代码语言:javascript复制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()) ||
(event instanceof PayloadApplicationEvent &&
matchesClassCastMessage(msg, ((PayloadApplicationEvent) event).getPayload().getClass()))) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for
// -> let's suppress the exception.
Log loggerToUse = this.lazyLogger;
if (loggerToUse == null) {
loggerToUse = LogFactory.getLog(getClass());
this.lazyLogger = loggerToUse;
}
if (loggerToUse.isTraceEnabled()) {
loggerToUse.trace("Non-matching event type for listener: " listener, ex);
}
}
else {
throw ex;
}
}
}
至此,Spring Event事件的实现也就完成了。
六:最后
本文主要介绍了Spring Event的使用以及它的实现原理,看完这篇文章相信你对Spring Event已经有了一定的了解,不妨在我们的业务开发中尝试使用Spring Event来降低代码的耦合度吧。最后,如果有任何疑问,欢迎在下方评论区留言。