代码耦合度那么高,试试Spring Event!

2022-07-26 08:00:46 浏览数 (1)

码农在囧途

在时光这条隧道上,我们还是少走一点回头路,少去留恋一点过往,还是得保持前进,我们会被拒绝,会被嘲笑,会被蔑视,甚至被唾骂,不过这都不是你应该去关心的事,不要被其羁绊,相反,得将其作为助推器,作为生命的燃料,再也没有这样的事更能激起一个人斗志,虽然结果看来事如此的渺茫,不过,依然相信曙光!

前言

今天来分享一下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发布事件,对应的事件将会被发布,那么被监听对应事件的监听器将会被触发。

代码语言:javascript复制
/**
 * 功能说明:订单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。

今天的分享就到这里,感谢你的观看,我们下期见。

0 人点赞