基于Spring源码实现策略模式与观察者模式

5,281 阅读5分钟

本文摘要:使用Spring事件推送机制与aware接口实现策略模式观察者模式,并使用线程池进行任务异步化。

一、业务场景描述

假设我们要开发银行二类户开户功能,开户方式一需要执行如下步骤:

  1. 上传基础资料
  2. OCR检测
  3. 活体识别
  4. 绑定银行卡 开户方式二则只需要:
  5. 上传基础资料
  6. OCR检测 提交用款申请时,再补充:
  7. 活体识别
  8. 绑定银行卡 同时两种方式每个步骤之后都需要异步上报到监控系统

二、策略模式实现

我们先来实现路由两种开户方式的策略模式

  • 先定义开户接口
public interface OpenAccountHandler {

    void execute();
    
}
  • 实现两种开户类 完整开户流程:
@Component("complete")
public class OpenAcctComplete implements OpenAccountHandler{

    @Override
    public void execute() {
        // todo 后续观察者模式实现
    }
}

简略开户流程:

@Component("partly")
public class OpenAcctPartly implements OpenAccountHandler {

    @Override
    public void execute() {
        // todo 后续观察者模式实现
    }
}
  • 实现自定义路由 这里我们先定义一个开户类型枚举:
@Getter
public enum OpenAcctMethodEnum {

    COMPLETE("complete", "完整开户模式,需要执行所有流程"),
    PARTLY("partly", "部分开户模式,用款申请时补充其他流程");

    private String type;
    private String desc;

    OpenAcctMethodEnum(String type, String desc){
        this.type = type;
        this.desc = desc;
    }

    /**
     *
     * @param type
     * @return
     */
    public static OpenAcctMethodEnum getEnumByType(String type){
        return Arrays.stream(OpenAcctMethodEnum.values())
                .filter(eachEnum -> eachEnum.getType().equals(type))
                .findFirst().get();
    }

}

接下来实现路由的核心逻辑:

@Service
public class OpenAcctService implements ApplicationContextAware {

    /**
     * 也可以直接注入map,key为bean的名称,如果未对key指定名称,则是类名把首字母改为小写
     * value则为bean
     * 另外spring也支持注入list
     */
    /*@Autowired
    private Map<String, OpenAccountHandler> openAccountHandlerMap;*/

    /**
     * 也可以直接注入容器,再获取bean
     */
    /*@Resource
    private ApplicationContext applicationContext;*/

    private Map<OpenAcctMethodEnum, OpenAccountHandler> openAccountHandlerMap;

    /**
     *
     * @param type 选择开户方式
     */
    public void openAccount(String type){
        openAccountHandlerMap.get(OpenAcctMethodEnum.getEnumByType(type)).execute();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
           final Map<String, OpenAccountHandler> handlerMap = applicationContext.getBeansOfType(OpenAccountHandler.class);
        openAccountHandlerMap = new HashMap<>(handlerMap.size());
        handlerMap.forEach((type, handler) ->
            openAccountHandlerMap.put(OpenAcctMethodEnum.getEnumByType(type), handler));
        }
    }

这里我们使用ApplicationContextAwaresetApplicationContext(...)方法进行路由初始化。我们也可以通过如下方式实现(代码中注释部分):

  1. 直接注入ApplicationContext,再解析bean。
  2. Spring注入List,可以把开户实现类直接注入到使用类中。
  3. Spring注入Map,类似于listkey是bean的名称,如果未自定义,则是类名把首字母改为小写

三、观察者模式实现

外层策略实现完成后,接下来进入开户主逻辑的处理。

  • 首先我们定义各个事件与相应的处理方式:
//上传基础资料事件
@Getter
public class UploadBasicInfoEvent extends ApplicationEvent {

    // 事件发布需要传入的消息,也是上报到监控系统的消息
    private String message;

    public UploadBasicInfoEvent(Object source) {
        super(source);
    }

    public UploadBasicInfoEvent(Object source, String message) {
        super(source);
        this.message = message;
    }

}
// 上传基础资料监听器
@Component
public class UploadBasicInfoListen implements ApplicationListener<UploadBasicInfoEvent> {

        @Override
        public void onApplicationEvent(UploadBasicInfoEvent event) {
                // 打印执行线程,模拟上报到监控系统
                System.out.println(Thread.currentThread() + " " + event.getMessage());
        }

}

其他事件也类似,我们定义好每个事件及相应的监听器。

  • 发布事件 因为事件需要异步执行,所有我们看下spring是否提供异步执行事件的机制,阅读源码,看到这里:

1649932684(1).png 这里获取了广播器的线程池,我们看下这个广播器的构建:

1649932908(1).png 可以看到,我们自定义广播器,即可实现异步执行监听器逻辑。那么我们先来自定义广播器:

/**
 * @Author winsonWu
 * @Description: 自定义广播器
 **/
@Configuration
public class MulticasterConfig {

    /**
     * 创建spring内置线程池
     * 线程池文章参考:https://juejin.cn/post/7086351322944913438
     * @return
     */
    @Bean
    public ThreadPoolTaskExecutor listenerExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setAllowCoreThreadTimeOut(true);
        taskExecutor.setCorePoolSize(5);
        taskExecutor.setMaxPoolSize(5);
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
        taskExecutor.setThreadFactory(new CustomizableThreadFactory("event-listener-resolver"));
        taskExecutor.setAwaitTerminationSeconds(10);
        return taskExecutor;
    }
    
    /**
     * 创建广播器
     * @return
     */
    @Bean
    public SimpleApplicationEventMulticaster applicationEventMulticaster(ConfigurableListableBeanFactory beanFactory,
                                                                         @Qualifier("listenerExecutor") ThreadPoolTaskExecutor taskExecutor) {
        SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster(beanFactory);
        multicaster.setTaskExecutor(taskExecutor);
        return multicaster;
    }
}

广播器定义好后,我们就可以发布事件了,首先在前面的Handler中引入我们定义的ApplicationEventPublisher,这里我们提供简略开户流程的代码:

/**
 * @Author winsonWu
 * @Description: 开户简略流程
 * @date Date : 2022.04.14 17:08
 **/
@Component("partly")
public class OpenAcctPartly implements OpenAccountHandler {

    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void execute() {
        // 上传基础资料
        final String uploadBasicInfoMsg = uploadBasicInfo();
        // 结果上报到监控系统
        applicationEventPublisher.publishEvent(new UploadBasicInfoEvent(this, uploadBasicInfoMsg));
        // OCR检查
        final String OCRCheckMsg = OCRCheck();
        // 结果上报到监控系统
        applicationEventPublisher.publishEvent(new UploadBasicInfoEvent(this, OCRCheckMsg));
        //简略流程不涉及活体识别
        //aliveCheck();
        //简略流程不涉及绑卡
        //bindBankCard();
    }

    private String OCRCheck() {
        // 打印线程号,模拟执行OCR
        System.out.println(Thread.currentThread() + " doing OCR");
        return "OCRCheck";
    }

    private String uploadBasicInfo() {
         // 打印线程号,模拟执行上传基础资料
        System.out.println(Thread.currentThread() + " doing uploadBasicInfo");
        return "uploadBasicInfo";
    }
}

调用我们定义好的服务:

@SpringBootApplication
public class FoundationApplication implements InitializingBean {

    @Autowired
    private OpenAcctService openAcctService;

    public static void main(String[] args) {
        SpringApplication.run(FoundationApplication.class, args);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        openAcctService.openAccount(OpenAcctMethodEnum.PARTLY.getType());
    }
}

执行结果:

1649935415(1).png 符合预期。

四、拾遗

  • 如上,我们就通过spring事件推送机制线程池实现了第一部分中定义的业务功能。当然异步执行事件也可以通过spring内置的@Async注解并自定义线程池实现,我们这里不赘述。
  • Spring提供了各种类型的特性与扩展点,方便我们开发者自定义逻辑,如BeanPostProcessorBeanDefinitionPostProcessoraware等。
  • 代码中使用了awareInitializingBean等初始化方法,日常开发中,初始化方法是我们常用的一种预处理形式,其他初始化方法包括@postConstructor,init-method等,项目中经常出现各类初始化方法的混用,在项目复杂或极端情况下,极易因为初始化顺序的问题导致灾难,所以我们这里记录下初始化自动销毁方法的执行顺序:

image.png

  • 文中线程池我们用了spring自带的线程池ThreadPoolTaskExecutor,具体使用详情请查阅参考资料。

五、参考资料