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:
- Upload basic data
- OCR detection
- Living body recognition
- Bind bank card
Account opening method 2 only requires:
- Upload basic data
- OCR detection
When submitting the payment application, add:
- Living body recognition
- 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 ApplicationContextAware
the setApplicationContext(...)
method for route initialization. We can also do it in the following way (the commented part of the code):
- Direct injection
ApplicationContext
, and then parse the bean. - Spring injection
List
, you can directly inject the account opening implementation class into the usage class. - Spring injection
Map
, similarlylist
,key
is 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:
The thread pool of the broadcaster is obtained here. Let's take a look at the construction of this broadcaster:
As 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());
}
}
复制代码
执行结果:
符合预期。
四、拾遗
- 如上,我们就通过
spring事件推送机制
与线程池
实现了第一部分中定义的业务功能。当然异步执行事件也可以通过spring内置的@Async
注解并自定义线程池实现,我们这里不赘述。 - Spring提供了各种类型的特性与扩展点,方便我们开发者自定义逻辑,如
BeanPostProcessor
、BeanDefinitionPostProcessor
、aware
等。 - 代码中使用了
aware
、InitializingBean
等初始化方法,日常开发中,初始化方法是我们常用的一种预处理形式,其他初始化方法包括@postConstructor
,init-method
等,项目中经常出现各类初始化方法的混用,在项目复杂或极端情况下,极易因为初始化顺序的问题导致灾难,所以我们这里记录下初始化
和自动销毁
方法的执行顺序:
- 文中线程池我们用了spring自带的线程池
ThreadPoolTaskExecutor
,具体使用详情请查阅参考资料。