最近在看一本书<<Spring技术内幕>>,一开始当然看到的是基础容器BeanFactory和高级容器ApplicationContext,首先吸引我眼球的就是ApplicationContext的事件机制,以前的我竟然一点都没了解过。。。因此,自己去捣鼓了一翻,写了一个小demo。
场景:
假如现在有服务1、服务2、服务3.当服务1处理完业务后需要调用服务2和服务3处理后续的业务。正常的写法就是在服务1中调用服务2和服务3的方法,但是这样子服务1和另外两个服务的耦合性就很高了,而且如果服务2和服务3都是其他小伙伴开发的,那么对方的一小点改动就会造成很大的麻烦。这时候,我们可以利用Spring的ApplicationContext的事件机制来完成,当然了,这是在单体应用中比较好的解决方案。如果在当前非常流行的微服务架构中,ApplicationContext的事件机制就没用了,现在比较流行的解决方案都是利用消息中间件,这里就不多做详细的介绍了,下篇文章将为大家讲解。
背景:
用户注册后,需要发送短信和发送邮件来通知用户已经注册成功。有三个Service:UserService,MessageService,MailService.
原始写法:
直接在UserService调用MessageService的发送短信方法和MailService的发送邮件方法。
Controller:
/**
* @author Howinfun
* @desc
* @date 2019/5/13
*/
@RestController
@RequestMapping("/user")
@AllArgsConstructor
public class UserController {
private UserService userService;
@PostMapping(value = "/registerUser",consumes = MediaType.APPLICATION_JSON_UTF8_VALUE,produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String registerUser(@RequestBody User user){
userService.registerUser(user);
return "注册成功";
}
}
Services:
/**
* @author Howinfun
* @desc 用户Service
* @date 2019/5/13
*/
@Service
@Slf4j
@AllArgsConstructor
public class UserService {
private MessageService messageService;
private MailService mailService;
/**
* 用户注册
* @param user
*/
public void registerUser(User user){
System.out.println("用户:"+user.getName()+"注册成功");
applicationContext.publishEvent(new UserEvent(this,user.getName(),user.getPhone(),user.getMail()));
// 发送短信&邮件
messageService.sendMessage(user);
mailService.sendEMail(user);
}
}
/**
* @author Howinfun
* @desc 短信Service
* @date 2019/5/13
*/
@Service
@Slf4j
public class MessageService {
/**
* 发送短信
* @param user
*/
public void sendMessage(User user){
System.out.println("给用户"+user.getName()+"发送短信,手机号码为:"+user.getPhone());
}
}
/**
* @author Howinfun
* @desc 邮件Service
* @date 2019/5/13
*/
@Service
@Slf4j
public class MailService {
/**
* 发送邮件
* @param user
*/
public void sendEMail(User user){
System.out.println("给用户"+user.getName()+"发送邮件,EMail为:"+user.getMail());
}
}
控制台打印:
2019-05-14 11:52:41.562 INFO 8388 --- [nio-8080-exec-1] c.h.t.A.service.UserService : 用户:howinfun注册成功
2019-05-14 11:52:41.573 INFO 8388 --- [nio-8080-exec-1] c.h.t.A.service.MessageService : 给用户howinfun发送短信,手机号码为:12345678900
2019-05-14 11:52:41.573 INFO 8388 --- [nio-8080-exec-1] c.h.t.A.service.MailService : 给用户howinfun发送邮件,EMail为:baidu@qq.com
总结:我们可以看到上面的代码,服务之间是直接引入和调用了,耦合性非常的高,如果这时候MessageService和MailService的发送方法多了一个参数,那么UserService的注册方法都必须跟着改代码了。
这不是一件很蛋疼的事情吗?
升级写法:这时候,我们可以利用ApplicationContext的事件机制来完成业务的解耦,还互相不影响各服务。原理是这样子的:在UserService的注册方法中,当注册成功后,我们就利用ApplicationContext
去发布一个UserEvent(继承ApplicationEvent类)事件,然后实现ApplicationListener<UserEvent>接口的类就可以捕获到这个用户事件,然后就可以进行后续的处理了。
当然了,以后不管是MessageService还是MailService去修改接口,到Listener去改就ok了,完全不影响UserService里头的注册方法,想当的nice。下面上代码。
1、首先创建UserEvent:
/**
* @author Howinfun
* @desc
* @date 2019/5/13
*/
@Data
public class UserEvent extends ApplicationEvent {
private String name;
private String phone;
private String mail;
public UserEvent(Object source,String name,String phone,String mail) {
super(source);
this.name = name;
this.phone = phone;
this.mail = mail;
}
}
2、然后适当修改UserService的代码,去掉MessageService和MailService的方法调用,添加ApplicationContext的依赖:
/**
* @author Howinfun
* @desc 用户Service
* @date 2019/5/13
*/
@Service
@Slf4j
@AllArgsConstructor
public class UserService {
private ApplicationContext applicationContext;
/**
* 用户注册
* @param user
*/
public void registerUser(User user){
System.out.println("用户:"+user.getName()+"注册成功");
// 发布事件
applicationContext.publishEvent(new UserEvent(this,user.getName(),user.getPhone(),user.getMail()));
}
}
3、最后,新增MessageApplicationListener和MailApplicationListener:
/**
* @author Howinfun
* @desc
* @date 2019/5/13
*/
@Component
public class MessageApplicatinoListener implements ApplicationListener<UserEvent> {
@Autowired
private MessageService messageService;
@Override
public void onApplicationEvent(UserEvent userEvent) {
User user = new User();
BeanUtils.copyProperties(userEvent,user);
// 发送短信
messageService.sendMessage(user);
}
}
/**
* @author Howinfun
* @desc
* @date 2019/5/13
*/
@Component
public class MailApplicatinoListener implements ApplicationListener<UserEvent> {
@Autowired
private MailService mailService;
@Override
public void onApplicationEvent(UserEvent userEvent) {
User user = new User();
BeanUtils.copyProperties(userEvent,user);
// 发送邮件
mailService.sendEMail(user);
}
}
4、当然了,最后调用结果和上面的一样:
2019-05-14 11:52:41.562 INFO 8388 --- [nio-8080-exec-1] c.h.t.A.service.UserService : 用户:howinfun注册成功
2019-05-14 11:52:41.573 INFO 8388 --- [nio-8080-exec-1] c.h.t.A.service.MessageService : 给用户howinfun发送短信,手机号码为:12345678900
2019-05-14 11:52:41.573 INFO 8388 --- [nio-8080-exec-1] c.h.t.A.service.MailService : 给用户howinfun发送邮件,EMail为:baidu@qq.com
5、但是我们可以看到,上面的调用都是用一个线程的,就是说,只有当MessageService和MailService的方法调用结束才会返回信息给前端,但是其实正常的用户注册,我们只需要在注册成功后立刻返回信息给前端,所以我们可以用到异步调用。因为项目是基于Spring Boot的,所以只需要在两个Listener加上@Async注解即可,记得在Application类加上@EnableAsync注解。
下面再看一下控制台的打印:能明显看到发动短信和邮件都是另外的线程!
2019-05-14 11:52:41.562 INFO 8388 --- [nio-8080-exec-1] c.h.t.A.service.UserService : 用户:howinfun注册成功
2019-05-14 11:52:41.573 INFO 8388 --- [ task-2] c.h.t.A.service.MessageService : 给用户howinfun发送短信,手机号码为:12345678900
2019-05-14 11:52:41.573 INFO 8388 --- [ task-1] c.h.t.A.service.MailService : 给用户howinfun发送邮件,EMail为:baidu@qq.com
文章到此结束,如果还有疑问的小伙伴,可在下面评论~