影落离风

The shadow falls away from the wind

0%

业务重构时用事件驱动模式

前言

需求:当新用户注册时,需要给用户发放各种礼品、积分、短信、邀请人奖励等。

常见写法

  1. 直接将上述后续操作堆到注册方法里,搞的注册方法又臭又长;当有其他注册如app注册、小程序注册、第三方注册时,然后将同样的发放逻辑复制的到处都是,可读性、维护性极差

  2. 优化:可以将发放逻辑抽成一个方法,然后供调用,这个应该是最常用的;

    但随着业务的扩张,可能不止普通用户、还有承包商、供应商等。可能都已经是不同的表了,他们也有不同的发放逻辑。

    然后发放逻辑建好几个方法,将之前的逻辑复制过去改一改;最后这个发放逻辑就会变得很臃肿难以维护。

  3. 再优化:可以套用事件驱动模式肢解该逻辑,当然也可以不使用。一切都要从业务出发,业务不复杂就完全没必要使用。因为不管用到什么设计模式都会建好多个类,虽然在单个类里维护性很强,但找起来可能不方便、整体使用还是不如直接调用方便。

    就是说不能为了使用设计模式而使用设计模式

设计模式优势

  1. 一般来说使用设计模式可以提高代码的三性:
    1. 提高代码的可重用性
    2. 提高代码的可读性
    3. 提高代码的可靠性

事件驱动模式处理

  • 事件源:用户

  • 事件:用户注册

  • 事件监听器:监听到用户注册时调用奖励发放功能

步骤

1. 建UserRegisterEvent类继承ApplicationEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 用户注册事件
*
* @author MinWeikai
* @date 2021-11-01 17:42:33
*/
public class UserRegisterEvent extends ApplicationEvent {

private User user;

public UserRegisterEvent(Object source, User user) {
super(source);
this.user = user;
}

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}
}

2. 用户注册事件发布

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 用户注册事件发布
*
* @author MinWeikai
* @date 2021-11-01 17:45:06
*/
@Component
public class UserRegisterEventPublisher implements ApplicationEventPublisherAware {

private ApplicationEventPublisher applicationEventPublisher;

@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}

public void publish(User user) {
this.applicationEventPublisher.publishEvent(new UserRegisterEvent(this, user));
}
}

3. 用户注册事件监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 用户注册事件监听
*
* @author MinWeikai
* @date 2021-11-01 17:43:09
*/
@Slf4j
@Component
public class UserRegisterEventListener implements ApplicationListener<UserRegisterEvent> {

@SneakyThrows
@Override
public void onApplicationEvent(UserRegisterEvent userRegisterEvent) {
User user = userRegisterEvent.getUser();
log.info("用户:[{}] 进行了注册,开始发放奖励", user.getName());
Thread.sleep(1000);
log.info("执行发放奖品");
Thread.sleep(1000);
log.info("执行发放积分");
Thread.sleep(1000);
log.info("执行发送短信");
Thread.sleep(1000);
log.info("新用户奖励发放完成");
}
}

4. 测试执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @author MinWeikai
* @date 2021-11-01 17:45:43
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRegisterEventPublisherTest {
@Autowired
private UserRegisterEventPublisher userRegisterEventPublisher;

@Test
public void publish() throws InterruptedException {
User user = new User();
user.setName("君莫笑");
userRegisterEventPublisher.publish(user);
Thread.sleep(1000*10);
}
}

5. 当有其他监听者时,如邀请人奖励发放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 用户注册事件邀请人监听
*
* @author MinWeikai
* @date 2021-11-01 17:43:09
*/
@Slf4j
@Component
public class UserRegisterEventInviterListener implements ApplicationListener<UserRegisterEvent> {

@SneakyThrows
@Override
public void onApplicationEvent(UserRegisterEvent userRegisterEvent) {
User user = userRegisterEvent.getUser();
log.info("用户:[{}] 进行了注册,开始发放邀请人奖励", user.getName());
Thread.sleep(1000);
log.info("邀请人奖励发放完成");
}
}

监听同一事件对象,业务代码相对解耦

原理

  1. 点这个方法进去applicationEventPublisher.publishEvent

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
    Assert.notNull(event, "Event must not be null");

    // Decorate event as an ApplicationEvent if necessary
    ApplicationEvent applicationEvent;
    if (event instanceof ApplicationEvent) {
    applicationEvent = (ApplicationEvent) event;
    }
    else {
    applicationEvent = new PayloadApplicationEvent<>(this, event);
    if (eventType == null) {
    eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
    }
    }

    // Multicast right now if possible - or lazily once the multicaster is initialized
    if (this.earlyApplicationEvents != null) {
    this.earlyApplicationEvents.add(applicationEvent);
    }
    else {
    // !! 主要看这里面 !!
    getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
    }

    // Publish event via parent context as well...
    if (this.parent != null) {
    if (this.parent instanceof AbstractApplicationContext) {
    ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
    }
    else {
    this.parent.publishEvent(event);
    }
    }
    }
  2. 获取事件的监听器执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    // 如果初始化异步任务,就会异步执行每个监听器,否则就是同步执行
    Executor executor = getTaskExecutor();
    // 此处根据事件对象获取它的所有监听器
    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
    if (executor != null) {
    executor.execute(() -> invokeListener(listener, event));
    }
    else {
    invokeListener(listener, event);
    }
    }
    }
  3. 执行UserRegisterEventListener实现的接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @SuppressWarnings({"rawtypes", "unchecked"})
    private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    try {
    // 最后就是这里去执行UserRegisterEventListener实现的接口
    listener.onApplicationEvent(event);
    }
    catch (ClassCastException ex) {
    String msg = ex.getMessage();
    if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
    // Possibly a lambda-defined listener which we could not resolve the generic event type for
    // -> let's suppress the exception and just log a debug message.
    Log logger = LogFactory.getLog(getClass());
    if (logger.isTraceEnabled()) {
    logger.trace("Non-matching event type for listener: " + listener, ex);
    }
    }
    else {
    throw ex;
    }
    }
    }

异步

1. 怎么异步使用监听器

可以自己注入一个SimpleApplicationEventMulticaster类,给里面设置上线程即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 自己注入SimpleApplicationEventMulticaster,设置执行线程
*
* @author MinWeikai
* @date 2021/11/3 10:35
*/
@Configuration
public class MySimpleApplicationEventMulticaster {
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setAllowCoreThreadTimeOut(true);
taskExecutor.setCorePoolSize(5);
taskExecutor.setMaxPoolSize(20);
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
taskExecutor.setThreadFactory(new CustomizableThreadFactory("MySimpleApplicationEventMulticaster-pool-"));
return taskExecutor;
}

@Bean
public SimpleApplicationEventMulticaster applicationEventMulticaster(@Qualifier("taskExecutor") ThreadPoolTaskExecutor taskExecutor) {
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
multicaster.setTaskExecutor(taskExecutor);
return multicaster;
}

}

总结

优点

  1. 从以上步骤中可以看到事件对象、事件发布、事件监听器代码相对解耦
  2. 当有新的业务时如邀请人事件监听,只需要添加一个事件监听器即可,无需修改原有代码。减少对现有业务的影响,相对扩展性较好
  3. 可以更容易开发和维护不可预知的服务或异步服务

缺点

  1. 同一事件的监听器尽量放在同一目录,有可能会出现同一事件的不同监听器散落在不同的模块中,增加业务逻辑的复杂度

代码

上述代码路径