likes
comments
collection

试试使用Spring Event组合@Async注解,轻松实现代码的解耦和异步

作者站长头像
站长
· 阅读数 11

一 前言

在我们写代码的时候,通常需要考虑到代码的耦合性,因为低耦合的代码有利于我们后续的维护和迭代,而Spring Event可以说是一个降低代码耦合度的神器,配合@Async注解更是能够轻松实现异步。今天我们就一起来了解一下Spring Event。

二:如何使用Spring Event

我们以一个简单的业务场景为例:

用户注册账号之后,我们需要赠送用户500积分

1.定义Event事件类和DTO传输数据对象

首先我们需要定义一个增加积分的事件,而这个类需要继承ApplicationEvent类。

public class AddCreditEvent extends ApplicationEvent {

    private static final long serialVersionUID = 303142463705896963L;

    public AddCreditEvent(Object source) {
        super(source);
    }
}
@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()方法中就可以执行我们的业务逻辑了。

@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注解

@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属性。

@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的类。

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    ...
   // Register early post-processor for detecting inner beans as ApplicationListeners.
   beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
   ....
}
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容器中。

	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广播器中。

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()方法。

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)方法(这里默认线程池为空,所以默认是同步执行的)。

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()方法。

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方法。

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来降低代码的耦合度吧。最后,如果有任何疑问,欢迎在下方评论区留言。原创不易,如果本文对你有所帮助,那么点个赞再走吧。