技术背景
我们在传统项目中往往各个业务逻辑之间耦合性较强,因为我们在service层都是直接引用的关联service或者jpa来作为协作处理逻辑,然而这种方式在后期更新、维护性时难度较大。如果我们采用事件通知、事件监听形式来处理逻辑时耦合性则是可以降到最小。
- 事件发布方使用applicationContext#publishEvent发布事件
# 定义事件
@Getter
public class UserRegisterEvent extends ApplicationEvent
{
//注册用户对象
private UserBean user;
/**
* 重写构造函数
* @param source 发生事件的对象
* @param user 注册用户对象
*/
public UserRegisterEvent(Object source,UserBean user) {
super(source);
this.user = user;
}
}
# 发布事件
@Service
public class UserService
{
@Autowired
ApplicationContext applicationContext;
/**
* 用户注册方法
* @param user
*/
public void register(UserBean user)
{
//../省略其他逻辑
//发布UserRegisterEvent事件
applicationContext.publishEvent(new UserRegisterEvent(this,user));
}
}
- 事件消费方
- 方式一:使用@Component+@EventListener注册监听者
# 第一个监听者
@Component
public class AnnotationRegisterListener {
/**
* 注册监听实现方法
* @param userRegisterEvent 用户注册事件
*/
@EventListener
public void register(UserRegisterEvent userRegisterEvent)
{
//获取注册用户对象
UserBean user = userRegisterEvent.getUser();
//../省略逻辑
//输出注册用户信息
System.out.println("@EventListener注册信息,用户名:"+user.getName()+",密码:"+user.getPassword());
}
}
# 第二个监听者
@Component
public class RegisterUserEmailListener
{
/**
* 发送邮件监听实现
* @param userRegisterEvent 用户注册事件
*/
@EventListener
public void sendMail(UserRegisterEvent userRegisterEvent)
{
System.out.println("用户注册成功,发送邮件。");
}
}
- 方式二:使用@Component+接口ApplicationListener<>实现
@Component
public class RegisterListener implements ApplicationListener<UserRegisterEvent>
{
/**
* 实现监听
* @param userRegisterEvent
*/
@Override
public void onApplicationEvent(UserRegisterEvent userRegisterEvent) {
//获取注册用户对象
UserBean user = userRegisterEvent.getUser();
//../省略逻辑
//输出注册用户信息
System.out.println("注册信息,用户名:"+user.getName()+",密码:"+user.getPassword());
}
}
通过测试发现,使用@EventListener和接口ApplicationListener<>注册的多个事件监听者,被通知的顺序是随机的,无法指定顺序。所以Spring又提供了第三种方式使用接口SmartApplicationListener来定义事件监听者的顺序。
- 方式三:使用@Component+接口SmartApplicationListener,可以覆盖supportsEventType,supportsSourceType,getOrder来精确指定事件类型、事件发起者、事件监听者被执行的顺序
# 第一个事件监听者
@Component
public class UserRegisterListener implements SmartApplicationListener
{
/**
* 该方法返回true&supportsSourceType同样返回true时,才会调用该监听内的onApplicationEvent方法
* @param aClass 接收到的监听事件类型
* @return
*/
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
//只有UserRegisterEvent监听类型才会执行下面逻辑
return aClass == UserRegisterEvent.class;
}
/**
* 该方法返回true&supportsEventType同样返回true时,才会调用该监听内的onApplicationEvent方法
* @param aClass
* @return
*/
@Override
public boolean supportsSourceType(Class<?> aClass) {
//只有在UserService内发布的UserRegisterEvent事件时才会执行下面逻辑
return aClass == UserService.class;
}
/**
* 同步情况下监听执行的顺序
* @return
*/
@Override
public int getOrder() {
return 0;
}
/**
* supportsEventType & supportsSourceType 两个方法返回true时调用该方法执行业务逻辑
* @param applicationEvent 具体监听实例,这里是UserRegisterEvent
*/
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
//转换事件类型
UserRegisterEvent userRegisterEvent = (UserRegisterEvent) applicationEvent;
//获取注册用户对象信息
UserBean user = userRegisterEvent.getUser();
//.../完成注册业务逻辑
System.out.println("注册信息,用户名:"+user.getName()+",密码:"+user.getPassword());
}
}
# 第二个事件监听者
@Component
public class UserRegisterSendMailListener implements SmartApplicationListener
{
/**
* 该方法返回true&supportsSourceType同样返回true时,才会调用该监听内的onApplicationEvent方法
* @param aClass 接收到的监听事件类型
* @return
*/
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
//只有UserRegisterEvent监听类型才会执行下面逻辑
return aClass == UserRegisterEvent.class;
}
/**
* 该方法返回true&supportsEventType同样返回true时,才会调用该监听内的onApplicationEvent方法
* @param aClass
* @return
*/
@Override
public boolean supportsSourceType(Class<?> aClass) {
//只有在UserService内发布的UserRegisterEvent事件时才会执行下面逻辑
return aClass == UserService.class;
}
/**
* 同步情况下监听执行的顺序
* @return
*/
@Override
public int getOrder() {
return 1;
}
/**
* supportsEventType & supportsSourceType 两个方法返回true时调用该方法执行业务逻辑
* @param applicationEvent 具体监听实例,这里是UserRegisterEvent
*/
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
//转换事件类型
UserRegisterEvent userRegisterEvent = (UserRegisterEvent) applicationEvent;
//获取注册用户对象信息
UserBean user = userRegisterEvent.getUser();
System.out.println("用户:"+user.getName()+",注册成功,发送邮件通知。");
}
}
测试输出如下
2017-07-21 13:40:43.104 INFO 10128 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-07-21 13:40:43.104 INFO 10128 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2017-07-21 13:40:43.119 INFO 10128 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 15 ms
注册信息,用户名:admin,密码:123456
用户:admin,注册成功,发送邮件通知。
- 方式四:在resources/application.properties中注册事件监听者 DelegatingApplicationListener 由spring boot初始化时启动,然后监听ApplicationEnvironmentPreparedEvent事件,收到事件后,就找当前项目的properties文件,并加载里面context.listener.classes对应的监听器
context.listener.classes=\
com.shahuwang.bootsample.listener.BlackListListener,\
com.shahuwang.bootsample.listener.BlackListListener2
SpringBoot相关的事件
- 定义事件监听者:以ApplicationStartedEvent为例
public class ApplicationStartedEventListener implements SmartApplicationListener {
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
// 判断事件的类型,只监听ApplicationStartedEvent事件类型
return eventType == ApplicationStartedEvent.class;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
// 将ApplicationEvent转换为ApplicationStartedEvent实例
ApplicationStartedEvent startedEvent = (ApplicationStartedEvent) event;
ConfigurableEnvironment environment = startedEvent.getApplicationContext().getEnvironment();
// 获取系统环境变量
Map<String, Object> props = environment.getSystemEnvironment();
Iterator<String> iterator = props.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
Object value = props.get(key);
System.out.println("Key : " + key + " , Value : " + value);
}
System.out.println("启动成功了.");
}
}
- 注册事件监听者
- 方式一:在resources/META-INF/spring.factories文件中注册事件监听者
org.springframework.context.ApplicationListener=\
org.minbox.chapter.developing.first.application.ApplicationStartedEventListener
- 方式二:使用SpringApplidation#addListeners来注册
@SpringBootApplication
public class DevelopingFirstApplication {
public static void main(String[] args) {
// 注释掉原启动方式
//SpringApplication.run(DevelopingFirstApplication.class, args);
// 手动实例化SpringApplication方式
SpringApplication application = new SpringApplication(DevelopingFirstApplication.class);
// 添加注册监听器
application.addListeners(new ApplicationStartedEventListener());
// 启动应用程序
application.run(args);
}
}
异步执行事件监听者
@Aysnc其实是Spring内的一个组件,可以完成对类内单个或者多个方法实现异步调用,这样可以大大的节省等待耗时。内部实现机制是线程池任务ThreadPoolTaskExecutor,通过线程池来对配置@Async的方法或者类做出执行动作。
- 线程任务池配置
我们创建一个ListenerAsyncConfiguration,并且使用@EnableAsync注解开启支持异步处理,具体代码如下所示:
@Configuration
@EnableAsync
public class ListenerAsyncConfiguration implements AsyncConfigurer
{
/**
* 获取异步线程池执行对象
* @return
*/
@Override
public Executor getAsyncExecutor() {
//使用Spring内置线程池任务对象
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//设置线程池参数
taskExecutor.setCorePoolSize(5);
taskExecutor.setMaxPoolSize(10);
taskExecutor.setQueueCapacity(25);
taskExecutor.initialize();
return taskExecutor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}
我们自定义的监听异步配置类实现了AsyncConfigurer接口并且实现内getAsyncExecutor方法以提供线程任务池对象的获取。
我们只需要在异步方法上添加@Async注解就可以实现方法的异步调用,为了证明这一点,我们在发送邮件onApplicationEvent方法内添加线程阻塞3秒,修改后的代码如下所示:
@Override
@Async
public void onApplicationEvent(ApplicationEvent applicationEvent) {
try {
Thread.sleep(3000);//静静的沉睡3秒钟
}catch (Exception e)
{
e.printStackTrace();
}
//转换事件类型
UserRegisterEvent userRegisterEvent = (UserRegisterEvent) applicationEvent;
//获取注册用户对象信息
UserBean user = userRegisterEvent.getUser();
System.out.println("用户:"+user.getName()+",注册成功,发送邮件通知。");
}
总结
我们在传统项目中往往各个业务逻辑之间耦合性较强,因为我们在service都是直接引用的关联service或者jpa来作为协作处理逻辑,然而这种方式在后期更新、维护性难度都是大大提高了。然而我们采用事件通知、事件监听形式来处理逻辑时耦合性则是可以降到最小。