将一个流程做成低耦合可扩展性能:
1.注册一个用户person
2.注册之后给用户发送短信注册成功
3.注册之后给用户发送邮件最近活动信息
事件机制:
事件驱动模型也就是我们常说的观察者,或者发布-订阅模型;理解它的几个关键点
Spring 中事件机制中各角色:
- 事件 ApplicationEvent 是所有事件对象的父类,也就是说当某个业务发生改变 Spring 可以发出一个事件出来(当然这边可能是具体的某一个事件,Spring 中常用的事件请看第二节介绍)。
- 事件监听 ApplicationListener,也就是观察者,继承自 JDK 的 EventListener,该类中只有一个方法 onApplicationEvent。当监听的事件发生后该方法会被执行。
- 事件源 ApplicationContext,ApplicationContext 是 Spring 中的核心容器,在事件监听中 ApplicationContext 可以作为事件的发布者,也就是事件源。
- 事件管理 ApplicationEventMulticaster,用于事件监听器的注册和事件的广播。监听器的注册就是通过它来实现的,它的作用是把 Applicationcontext 发布的 Event 广播给它的监听器列表。
事件:注册一个用户
监听器:发送短信、发送邮件
增加了一个Listener来解耦UserService和其他服务,即注册成功后,只需要通知相关的监听器,不需要关系它们如何处理。增删功能非常容易。这就是一个典型的事件处理模型/观察者,解耦目标对象和它的依赖对象,目标只需要通知它的依赖对象,具体怎么处理,依赖对象自己决定。比如是异步还是同步,延迟还是非延迟等。
具体示例:
1.定义事件
public class UserRegisterEvent extends ApplicationEvent { private String userName; //source事件源 public UserRegisterEvent(Object source, String userName) { super(source); this.userName = userName; } public String getUserName() { return userName; } }
2.定义监听器(两种方式)
方式1)实现接口方式:
/** * 事件监听器:监听广播,发送邮件 */ @Component public class UserRegisterEventListener implements ApplicationListener<UserRegisterEvent> { @Override public void onApplicationEvent(UserRegisterEvent userRegisterEvent) { System.out.println(String.format("接口模式给用户[%S]发送邮件成功",userRegisterEvent.getUserName())); } }
方式2)注解方式:
/** * 事件监听器:监听广播,发送邮件 */ @Component public class UserRegisterEventListener2 { @EventListener(classes = {UserRegisterEvent.class}) public void sendMail(UserRegisterEvent userRegisterEvent){ System.out.println("注解1给用户"+userRegisterEvent.getUserName()+"发送邮件成功"); } @EventListener(UserRegisterEvent.class) public void sendCompon(UserRegisterEvent userRegisterEvent){ System.out.println(String.format("注解2给用户[%s]发送邮件成功",userRegisterEvent.getUserName())); } }
第三、第四种摘取往上,
第三种方式:有序监听器实现SmartApplicationListener接口:
package com.br.listener; import com.br.bean.User; import com.br.event.UserRegisterEvent; import com.br.service.UserService; import org.springframework.context.ApplicationEvent; import org.springframework.context.event.SmartApplicationListener; import org.springframework.stereotype.Component; /** * @author 10400 * @create 2018-02-27 15:03 * @deprecated 有序监听 */ @Component public class SmartRegisterListener implements SmartApplicationListener { @Override public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) { //只有UserRegisterEvent监听类型才会执行下面逻辑 return aClass == UserRegisterEvent.class; } @Override public boolean supportsSourceType(Class<?> aClass) { //只有在UserService内发布的UserRegisterEvent事件时才会执行下面逻辑 return aClass == UserService.class; } @Override public void onApplicationEvent(ApplicationEvent applicationEvent) { //转换事件类型 UserRegisterEvent userRegisterEvent = (UserRegisterEvent) applicationEvent; //获取注册用户对象信息 User user = userRegisterEvent.getUser(); //.../完成注册业务逻辑 System.out.println("SmartRegisterListener" + user.getName()); } /** * return 的数值越小证明优先级越高,执行顺序越靠前。 * @return */ @Override public int getOrder() { return 10; } }
SmartApplicationListener接口继承了全局监听ApplicationListener,
并且泛型对象使用的ApplicationEvent来作为全局监听,
可以理解为使用SmartApplicationListener作为监听父接口的实现,监听所有事件发布。
getOrder:return的数值越小证明优先级越高,执行顺序越靠前
第四种方式:使用@Async实现异步监听
@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秒,修改后的代码如下所示:
我们只需要在异步方法上添加@Async注解就可以实现方法的异步调用,为了证明这一点,我们在发送邮件onApplicationEvent方法内添加线程阻塞3秒,修改后的代码如下所示:
/** * supportsEventType & supportsSourceType 两个方法返回true时调用该方法执行业务逻辑 * @param applicationEvent 具体监听实例,这里是UserRegisterEvent */ @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()+",注册成功,发送邮件通知。"); }
网摘链接:https://www.jianshu.com/p/ef2cee8c5dd1
3.事件服务(任取一个事件管理实现接口即可ApplicationContextAware、ApplicationEventPublisherAware)
@Service public class UserRegisterEventService implements ApplicationContextAware, ApplicationEventPublisherAware { private static ApplicationContext applicationContext; private static ApplicationEventPublisher applicationEventPublisher; //注册 public void registerUser(String userName) { UserRegisterEvent userRegisterEvent = new UserRegisterEvent(UserRegisterEvent.class, userName); System.out.println(String.format("用户[%s]注册成功", userName)); //发布注册成功事件 applicationContext.publishEvent(userRegisterEvent); applicationEventPublisher.publishEvent(userRegisterEvent); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @EventListener(classes = {UserRegisterEvent.class}) public void sendMail(UserRegisterEvent userRegisterEvent){ System.out.println("注解3给用户"+userRegisterEvent.getUserName()+"发送邮件成功"); } @EventListener(UserRegisterEvent.class) public void sendCompon(UserRegisterEvent userRegisterEvent){ System.out.println(String.format("注解4给用户[%s]发送邮件成功",userRegisterEvent.getUserName())); } @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } }
测试用例(结合上一篇测试用例规范注入Service服务):
/** * 事件对象:用户注册事件 */ @RunWith(SpringRunner.class) //@SpringBootTest(classes = UserRegisterEventConfig.class) @ContextConfiguration(classes = UserRegisterEventConfig.class) public class UserRegisterTest { @Resource private UserRegisterEventService userRegisterEventService; @Test public void test() { // ApplicationContext context = new AnnotationConfigApplicationContext(UserRegisterEventService.class); // UserRegisterEventService userRegisterEventService = context.getBean(UserRegisterEventService.class); System.out.println(userRegisterEventService); userRegisterEventService.registerUser("userName222"); } }