采用事件监听的好处
以用户注册的业务逻辑为例,用户在填写完信息表单后,提交信息到后台,后台对用户信息进行处理,然后给用户返回处理结果的信息。
如上图所示,用户在注册时,后台需要处理一些系列流程,实际业务逻辑可能更加复杂。这样写很直观,但是不利于后期新业务逻辑的添加。
如果采用事件监听的模式,上面的流程就可以变成如下:
用户在注册的过程中,发送一个信号给监听对象,而这个信号就是用户正在注册的事件,监听对象在收到信号时,就会在后台处理这些流程,如果采用异步事件处理的方式,用户的主干逻辑可以快速完成,而且如果后期需要在注册流程中加入新的逻辑也只需要在监听对象处理事件的过程中加入新的逻辑。
实际代码演示
首先是项目结构,如下
- controller 处理用户注册请求
- service 处理用户注册逻辑
- event 存放事件对象
- listener 在event的子目录下 监听并调用逻辑处理事件
- config 存放配置文件
原本还应该有entity和repository层,为简化逻辑,暂时不加
非异步事件处理
编写用户注册事件类,放在event包内,这个类可以根据需求添加一些属性,而这些属性就是代表发生这件事的基本信息。可以通俗的理解,要知道一件事,前提肯定要知道是什么事情。我在这里添加了属性username,表示用户名。
public class UserRegisterEvent extends ApplicationEvent { private String userName; public UserRegisterEvent(Object source, String userName) { super(source); this.userName = userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserName() { return userName; } }
编写listener事件监听,用来处理这些事情。当用户注册时,先保存用户注册的信息到数据库,然后给用户发送邮件(本次简化了逻辑,只在控制台打印了信息)。需要在事件处理器上面加上注解@EventListener,一旦事件UserRegisterEvent被发布,监听器就会监听这个事件,然后处理,处理的方式是:保存用户信息,给用户发送邮件。(为了模拟发送邮件这个较为复杂而且耗时的事情,我让线程暂停了5秒,然后继续执行)
@Component public class UserListener { @EventListener public void handleUserRegisterEvent(UserRegisterEvent userRegisterEvent) { // save user information System.out.println(Calendar.getInstance().getTime() + " " + "user information " + userRegisterEvent.getUserName()); // send email to user try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Calendar.getInstance().getTime() + " " + "send Email to " + userRegisterEvent.getUserName()); } }
编写controller层,模拟用户注册,当用户访问localhost:8080/register/{username}时,就算用户注册,注册成功后给用户返回一个hello
@RestController public class UserController { @Autowired private UserService userService; @GetMapping("/register/{username}") public ResponseEntity<String> register(@PathVariable(name = "username") String username){ return ResponseEntity.ok(userService.register(username)); } }
编写service层,在UserService返回用户注册成功的信息时,先要发布事件,需用ApplicationEventPulisher接口中的pulishEvent( )方法,这个方法的参数就是我们前面创建的UserRegisterEvent的一个对象(注意往对象中存放一些用户基本信息,便于处理器通过这些信息处理事件),当程序运行到这里时就相当于发出一个信号:某某用户正在注册!
@Service public class UserService { @Autowired ApplicationEventPublisher publisher; public String register(String username) { publisher.publishEvent(new UserRegisterEvent(this, username)); return "hello " + username + " " + Calendar.getInstance().getTime(); } }
启动应用程序,访问localhost:8080/register/tom,等待一段时间后就可以看到返回的注册成功信息hello tom,控制台也可以看到用户注册的时一些流程也执行了。
这样就完成了事件监听处理业务逻辑。
异步事件的处理
通过上面的例子已经完成了事件监听,但是也存在一些问题,例如假如发送邮件比较耗时,例如上面的例子,花了5秒钟,那么用户就需要等待五秒,才可以接受到返回的信息。看上面的例子,后台保存用户信息时间是18时38分47秒,邮件发送完毕是18时38分52秒,等到用户接到到信息时已经是18时38分52秒了,这样用户体验显然很差。采用异步事件的方式,新建一个线程,将处理事件放到新的线程中,这样就可以显著提高用户的体验。
要变成异步事件处理,在上面的例子上稍做修改即可:
在UserListener中,在需要做异步处理的方法上加上注解@Async
然后在项目启动类上加上注解@EnableAsync
只要加上这两个注解就可以了。
再次启动项目,访问localhost:8080/register/john,结果如下
可以看到用户很快就获得了响应。
补充:如果想自己控制线程,可以在config文件夹下加入配置类,代码如下
public class ListenerAsyncConfiguration implements AsyncConfigurer { @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; } }
总结:采用事件监听的方式可以很轻松实现业务逻辑的解耦,方便后期业务逻辑的扩展。