前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >试试使用Spring Event组合@Async注解,轻松实现代码的解耦和异步

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

作者头像
Java进阶之路
发布2022-11-18 15:27:01
1K0
发布2022-11-18 15:27:01
举报

一 前言

在我们写代码的时候,通常需要考虑到代码的耦合性,因为低耦合的代码有利于我们后续的维护和迭代,而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()方法就可以发布对应的事件了。

代码语言:javascript
复制
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来降低代码的耦合度吧。最后,如果有任何疑问,欢迎在下方评论区留言。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-11-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java进阶之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档