码农在囧途
在时光这条隧道上,我们还是少走一点回头路,少去留恋一点过往,还是得保持前进,我们会被拒绝,会被嘲笑,会被蔑视,甚至被唾骂,不过这都不是你应该去关心的事,不要被其羁绊,相反,得将其作为助推器,作为生命的燃料,再也没有这样的事更能激起一个人斗志,虽然结果看来事如此的渺茫,不过,依然相信曙光!
前言
今天来分享一下Spring Event,那么在说Spring Event之前,我们得先清楚为什么要用Spring Event,使用它能够带来什么好处,它适合什么样的场景?
首先为什么要使用Spring Event?
我们都知道,代码耦合度不能太高,所以我们要尽量的将代码解耦,提到解耦,我们会想到使用消息队列来解耦,消息生产者发布消息,消息消费者消费消息,这样,两个端就解耦了,消费者只需要订阅生产者,然后生产者推动消息到消费者,不过使用消息队列的话成本比较大。
我们也可以使用观察者模式来进行解耦,观察者模式就是发布订阅模式,Spring Event就是观察者模式,只是我们如果直接去写观察者模式,那么需要考虑很多东西,所以使用Spring Event便是一个不错的选择。
综上,Spring Event能够降低我们代码的耦合度,使我们的系统具有更高的可扩展性,但是它也有局限性,如果我们的系统很庞大,复杂,分布式的情况下,那么Spring Event可能就不怎么适合了,那么我们肯定会选择消息队列。
Spring Event实现下单流程
下单后,我们需要保存订单,然后进行扣减库存,扣减账户余额,短信通知等操作,我们此处使用Spring Event来实现这些操作的解耦,不过只适合单体架构,如果项目是微服务,分布式,那么使用Spring Event自然就不行,因为扣减库存,扣减账户余额,短信通知这几个接口都会是单独的服务。
项目结构
代码语言:javascript复制└─com
└─steakliu
└─event
│ EventApplication.java
│
├─order
│ ├─controller
│ │ OrderController.java
│ │
│ ├─dto
│ │ MessageDTO.java
│ │ OrderDTO.java
│ │ StockDTO.java
│ │ UserDTO.java
│ │
│ ├─event
│ │ MessageEvent.java
│ │ StockEvent.java
│ │ UserEvent.java
│ │
│ ├─listener
│ │ MessageListener.java
│ │ StockListener.java
│ │ UserListener.java
│ │
│ └─service
│ OrderService.java
定义Event(事件)
这里的事件里面就包含这个事件需要传递的信息,因为我们下面使用的是注解的方式,所以此处就比较简单,就是普通的实体定义,如果我们不使用注解,那么此处需要继承ApplicationEvent
,不然事件无法进行传递,我们以最简洁的方式来进行。
库存事件
代码语言:javascript复制/**
* 功能说明:库存事件
*
* <p>
* Original @Author: steakliu-刘牌, 2022-06-25 19:25
* <p>
* Copyright (C)2020-2022 小四的技术之旅 All rights reserved.
*/
@Data
@AllArgsConstructor
public class StockEvent {
//库存DTO
private StockDTO stockDTO;
}
账户事件
代码语言:javascript复制/**
* 功能说明:账户事件
*
* <p>
* Original @Author: steakliu-刘牌, 2022-06-25 19:35
* <p>
* Copyright (C)2020-2022 小四的技术之旅 All rights reserved.
*/
@Data
@AllArgsConstructor
public class UserEvent {
private UserDTO userDTO;
}
消息事件
代码语言:javascript复制/**
* 功能说明:消息事件
*
* <p>
* Original @Author: steakliu-刘牌, 2022-06-25 19:51
* <p>
* Copyright (C)2020-2022 小四的技术之旅 All rights reserved.
*/
@Data
@AllArgsConstructor
public class MessageEvent {
private MessageDTO messageDTO;
}
定义监听器
我们定义监听器后,并监听对应事件,当对应的事件被发布后,就会触发监听器,监听器就可以进行业务操作,我们下面使用注解@EventListener
的形式来定义监听器,也可以实现ApplicationListener
接口,不过推荐使用@EventListener
,下面我们也使用了@Async
注解,因为这些操作应该都是异步的,使用@Async
,需要在SpringBoot的启动类上加上@EnableAsync
注解。
库存监听器
代码语言:javascript复制/**
* 功能说明:库存监听器
*
* <p>
* Original @Author: steakliu-刘牌, 2022-06-25 19:27
* <p>
* Copyright (C)2020-2022 小四的技术之旅 All rights reserved.
*/
@Component
@Slf4j
public class StockListener {
@Async
@EventListener(StockEvent.class)
public void decreaseStock(StockEvent stockEvent) throws InterruptedException {
Thread.sleep(2000);
log.info("扣减id为【{}】的商品库存,扣减数量为【{}】",stockEvent.getStockDTO().getCommodityId(),stockEvent.getStockDTO().getNum());
}
}
账户监听器
代码语言:javascript复制/**
* 功能说明:账户监听器
*
* <p>
* Original @Author: steakliu-刘牌, 2022-06-25 19:35
* <p>
* Copyright (C)2020-2022 小四的技术之旅 All rights reserved.
*/
@Component
@Slf4j
public class UserListener {
@Async
@EventListener(UserEvent.class)
public void decreaseBalance(UserEvent userEvent) throws InterruptedException {
log.info("扣减账户【{}】余额,扣减金额为:【{}】",userEvent.getUserDTO().getUserId(),userEvent.getUserDTO().getMoney());
}
}
消息监听器
代码语言:javascript复制/**
* 功能说明:消息监听器
*
* <p>
* Original @Author: steakliu-刘牌, 2022-06-25 19:52
* <p>
* Copyright (C)2020-2022 小四的技术之旅 All rights reserved.
*/
@Component
@Slf4j
public class MessageListener {
@Async
@EventListener(MessageEvent.class)
public void sendMsg(MessageEvent messageEvent){
log.info("发送短信至【{}】",messageEvent.getMessageDTO().getTel());
}
}
发布事件
当我们进行下单的时候,使用ApplicationEventPublisher
发布事件,对应的事件将会被发布,那么被监听对应事件的监听器将会被触发。
/**
* 功能说明:订单Service
*
* <p>
* Original @Author: steakliu-刘牌, 2022-06-25 19:52
* <p>
* Copyright (C)2020-2022 小四的技术之旅 All rights reserved.
*/
@Component
@Slf4j
@AllArgsConstructor
public class OrderService {
private ApplicationEventPublisher applicationEventPublisher;
public void placeOrder(OrderDTO orderDTO){
log.info("=======开始下单操作=======");
//保存订单
this.saveOrder(orderDTO);
//扣减库存
StockDTO stockDTO = new StockDTO().setCommodityId(orderDTO.getCommodityId()).setNum(orderDTO.getNum());
applicationEventPublisher.publishEvent(new StockEvent(stockDTO));
//扣减余额
UserDTO userDTO = new UserDTO().setUserId(orderDTO.getUserId()).setMoney(orderDTO.getPrice().multiply(new BigDecimal(orderDTO.getNum())));
applicationEventPublisher.publishEvent(new UserEvent(userDTO));
//短信通知
MessageDTO messageDTO = new MessageDTO().setTel(orderDTO.getTel());
applicationEventPublisher.publishEvent(new MessageEvent(messageDTO));
}
private void saveOrder(OrderDTO orderDTO){
log.info("保存订单:【{}】",orderDTO);
}
}
下单Controller
代码语言:javascript复制/**
* 功能说明:下单
*
* <p>
* Original @Author: steakliu-刘牌, 2022-06-25 20:00
* <p>
* Copyright (C)2020-2022 小四的技术之旅 All rights reserved.
*/
@RestController
@RequestMapping("/order")
@AllArgsConstructor
public class OrderController {
final OrderService orderService;
@PostMapping("/placeOrder")
public String placeOrder(@RequestBody OrderDTO orderDTO){
orderService.placeOrder(orderDTO);
return "下单成功";
}
}
输出
从输出可以看出三个操作是异步的,在库存监听器哪里休眠了2s,发送短信和扣减余额接口并没有被阻塞,因为使用了@Async
注解。
总结
从上面的解说和实战我们看出使用Spring Event还是比较简单的,对于解耦我们的代码是有很大的作用的,所以我们在工作中如果如果遇到代码耦合度过于高的时候,可以考虑使用Spring Event。
今天的分享就到这里,感谢你的观看,我们下期见。