Implementing strategy mode and observer mode based on Spring source code

Summary of this article: Use the Spring event push mechanism and the aware interface to implement 策略模式and 观察者模式, and use it 线程池to perform task asynchrony.

1. Description of business scenarios

Assuming that we want to develop the function of opening a second-class bank account, the first method of opening an account requires the following steps:

  1. Upload basic data
  2. OCR detection
  3. Living body recognition
  4. Bind bank card

Account opening method 2 only requires:

  1. Upload basic data
  2. OCR detection

When submitting the payment application, add:

  1. Living body recognition
  2. Bind bank card

Both ways are required after each step 异步上报到监控系统.

Second, the strategy mode implementation

Let's first implement two ways to open an account 策略模式:

  • First define the account opening interface
public interface OpenAccountHandler {

    void execute();
    
}
复制代码
  • Implement two account opening classes

Complete account opening process:

@Component("complete")
public class OpenAcctComplete implements OpenAccountHandler{

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

Brief account opening process:

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

    @Override
    public void execute() {
        // todo 后续观察者模式实现
    }
}
复制代码
  • Implement custom routing

Here we first define an account opening type enumeration:

@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();
    }

}
复制代码

Next, implement the core logic of routing:

@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));
        }
    }
复制代码

Here we use ApplicationContextAwarethe setApplicationContext(...)method for route initialization. We can also do it in the following way (the commented part of the code):

  1. Direct injection ApplicationContext, and then parse the bean.
  2. Spring injection List, you can directly inject the account opening implementation class into the usage class.
  3. Spring injection Map, similarly list, keyis the name of the bean or, if not customized, the class name with the first letter changed to lowercase .

3. Implementation of Observer Pattern

After the implementation of the outer layer strategy is completed, the next step is to enter the processing of the main account opening logic.

  • First, we define each event and the corresponding processing method:
//上传基础资料事件
@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());
        }

}
复制代码

Similar to other events, we define each event and the corresponding listener.

  • Post an event

Because events need to be executed asynchronously, so let's see if spring provides a mechanism to execute events asynchronously, read the source code, see here:

1649932684(1).pngThe thread pool of the broadcaster is obtained here. Let's take a look at the construction of this broadcaster:

1649932908(1).pngAs you can see, we customize the broadcaster to implement the asynchronous execution of the listener logic. So let's customize the broadcaster first:

/**
 * @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,具体使用详情请查阅参考资料。

五、参考资料

Guess you like

Origin juejin.im/post/7086427136331874311