我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第4篇文章,点击查看活动详情
前言
实际业务开发程中,实现复杂的业务逻辑,如果将核心业务逻辑和非核心业务耦合一起,代码的耦合度高,维护起性和扩展性很差,所以需要进行解耦,通常大家会联想到MQ,MQ虽然可以解决业务解耦问题,但是也会提升架构的复杂度,那么有没有简单的解决方案呢?今天给大家讲解下,如何使用Spring Boot Event来实现业务解耦。
具体实现
定义Event事件
事件是发送的消息体,可以根据实际的业务进行封装不同的数据信息。注意传递参数需要重写父类的构造方法。例如创建发送邮件事件。
public class EmailEvent extends ApplicationEvent
{
private static final long serialVersionUID = -2098387871131250466L;
private TUser tUser;
public EmailEvent(TUser tUser)
{
super(tUser);
this.tUser = tUser;
}
public TUser gettUser()
{
return tUser;
}
}
注意:传递参数需要重写父类的构造函数。
定义监听器
监听器相当于MQ中的消费者,当有相关的事件推送过来,监听器能够监听执行相关的业务处理。
接口实现监听器
@Component
public class EmailListener implements ApplicationListener<EmailEvent>
{
private static final Logger logger = LoggerFactory.getLogger(EmailListener.class);
@Async
@Override
public void onApplicationEvent(EmailEvent event)
{
TUser tUser = event.gettUser();
logger.info("tuser:{}",JSON.toJSONString(tUser));
//省略发送邮件的逻辑实现
}
}
说明:监听器需要添加@Component注解将其注入到Spring容器中。
事件发布
发布事件非常简单,只需要调用ApplicationEventPublisher.publishEvent来发布事件.
@RestController
@RequestMapping("/event")
public class EventController
{
private Logger logger = LoggerFactory.getLogger(EventController.class);
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
@RequestMapping("/addUser")
public void addUser()
{
logger.info("send email start");
TUser tuser =new TUser();
tuser.setOid(1009l);
tuser.setName("测试事件");
//省略新增用户逻辑
//发送邮件
EmailEvent emailEvent =new EmailEvent(tuser);
applicationEventPublisher.publishEvent(emailEvent);
logger.info(" send email end");
}
}
测试结果
c.s.f.c.ThreadPoolExecutorMdcWrapper - Initializing ExecutorService
c.skywares.fw.event.EventController - send email end
c.s.f.c.exception.ResponseAdvice - before body write param:null
com.skywares.fw.event.EmailListener - tuser:{"age":0,"name":"测试事件","oid":1009}
从测试结果来看,事件已经执行成功。
注解方式实现
除了通过实现接口来实现监听器,我们可以采用@EventListener注解的方式来实现,具体实现如下。
发布事件
@Component
public class EmailListenerPublisher
{
private static final Logger logger = LoggerFactory.getLogger(EmailListenerPublisher.class);
@Async
@EventListener(EmailEvent.class)
public void publisher(EmailEvent event)
{
TUser tUser = event.gettUser();
logger.info("Event Listener{}",JSON.toJSONString(tUser));
}
}
说明:采用@EventListener注解方式来实现,需要指定事件的类型。
相关测试
@Autowired
private EmailListenerPublisher emailListenerPublisher;
@RequestMapping("/publisher")
public void publisher()
{
logger.info("send email start");
TUser tuser =new TUser();
tuser.setOid(1008l);
tuser.setName("注解方式测试事件");
//发送邮件
EmailEvent emailEvent =new EmailEvent(tuser);
emailListenerPublisher.publisher(emailEvent);
logger.info(" send email end");
}
说明:执行结果与接口实现法式一致。
异步执行事件
默认情况事件是同步执行,需要采用异步执行方式,需要在监听器方法上添加@Async注解即可。
监听器上添加@Async注解
@Component
public class EmailListener implements ApplicationListener<EmailEvent>
{
private static final Logger logger = LoggerFactory.getLogger(EmailListener.class);
@Async
@Override
public void onApplicationEvent(EmailEvent event)
{
TUser tUser = event.gettUser();
logger.info("tuser:{}",JSON.toJSONString(tUser));
}
}
注意: @Async默认使用的线程池最大线程数是Integer.MAX,并且阻塞队列的大小也是Integer.MAX, 采用默认方式容易产生OOM异常,所以不建议采用默认线程池,需要根据业务的情况自定义线程池。
自定义异步线程池
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer
{
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public static final int PROCESSORS = Runtime.getRuntime().availableProcessors();
@Override
public Executor getAsyncExecutor()
{
//自定义线程池
ThreadPoolExecutorMdcWrapper taskExecutor =new ThreadPoolExecutorMdcWrapper();
//核心线程数
taskExecutor.setCorePoolSize(PROCESSORS*2);
//最大线程数
taskExecutor.setMaxPoolSize(PROCESSORS*4);
//队列大小
taskExecutor.setQueueCapacity(500);
taskExecutor.setKeepAliveSeconds(60);
//线程名称
taskExecutor.setThreadNamePrefix("async-thread-");
taskExecutor.initialize();
return taskExecutor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler()
{
return new CustAsyncExceptionHandler();
}
class CustAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method,
Object... params)
{
logger.error("Unexpected exception occurred invoking async method: " + method, ex);
}
}
}
说明:ThreadPoolExecutorMdcWrapper为自定义线程的实现。
事件机制优缺点
优点
降低代码的耦合度
通过不同的事件,将业务逻辑解耦,只需要发布相关事件,不需要关注具体的实现逻辑,业务逻辑清晰,便于后续的维护和扩展。
增强代码的复用度
相同的非核心业务功能,只需要定义相关事件和实现,其他的模块都能进行复用,增强了代码的复用度。
缺点
SpringBoot的事件只能在单系统中使用,无法跨系统调用。
总结
本文对于Spring Boot Event的事件进行相关讲解,针对不同的业务选择合适的方案,如有疑问请随时反馈。