Implementando el modo de estrategia y el modo de observador basado en el código fuente de Spring

Resumen de este artículo: use el mecanismo de inserción de eventos de Spring y la interfaz consciente para implementar 策略模式y 观察者模式, y utilícelo 线程池para realizar la asincronía de tareas.

1. Descripción de los escenarios de negocio

Suponiendo que queremos desarrollar la función de abrir una cuenta bancaria de segunda clase, el primer método para abrir una cuenta requiere los siguientes pasos:

  1. Subir datos básicos
  2. Detección OCR
  3. Reconocimiento del cuerpo vivo
  4. Vincular tarjeta bancaria

El método de apertura de cuenta 2 solo requiere:

  1. Subir datos básicos
  2. Detección OCR

Al enviar la solicitud de pago, agregue:

  1. Reconocimiento del cuerpo vivo
  2. Vincular tarjeta bancaria

Se requieren ambas formas después de cada paso 异步上报到监控系统.

En segundo lugar, la implementación del modo de estrategia.

Primero implementemos dos formas de abrir una cuenta 策略模式:

  • Primero defina la interfaz de apertura de cuenta
public interface OpenAccountHandler {

    void execute();
    
}
复制代码
  • Implementar dos clases de apertura de cuenta

Proceso completo de apertura de cuenta:

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

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

Breve proceso de apertura de cuenta:

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

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

Aquí primero definimos una enumeración de tipo de apertura de cuenta:

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

}
复制代码

A continuación, implemente la lógica central del enrutamiento:

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

Aquí usamos ApplicationContextAwareel setApplicationContext(...)método para la inicialización de rutas. También podemos hacerlo de la siguiente manera (la parte comentada del código):

  1. Inyección directa ApplicationContexty luego analizar el bean.
  2. Spring injection List, puede inyectar directamente la clase de implementación de apertura de cuenta en la clase de uso.
  3. Spring injection Map, de manera similar list, keyes el nombre del bean o, si no se personaliza, el nombre de la clase con la primera letra cambiada a minúsculas .

3. Implementación del patrón de observador

Una vez que se completa la implementación de la estrategia de la capa externa, el siguiente paso es ingresar al procesamiento de la lógica de apertura de la cuenta principal.

  • Primero, definimos cada evento y el método de procesamiento correspondiente:
//上传基础资料事件
@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());
        }

}
复制代码

Al igual que con otros eventos, definimos cada evento y el oyente correspondiente.

  • Publicar un evento

Debido a que los eventos deben ejecutarse de forma asíncrona, veamos si Spring proporciona un mecanismo para ejecutar eventos de forma asíncrona, lea el código fuente, vea aquí:

1649932684(1).pngEl grupo de subprocesos de la emisora ​​se obtiene aquí. Echemos un vistazo a la construcción de esta emisora:

1649932908(1).pngComo puede ver, personalizamos el emisor para implementar la ejecución asincrónica de la lógica del oyente. Entonces, primero personalicemos la emisora:

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

五、参考资料

Supongo que te gusta

Origin juejin.im/post/7086427136331874311
Recomendado
Clasificación