根据上一篇文章所述,得知@PostConstruct注解修饰的方法会在构造方法执行之后调用,这篇文章就来阐述下,这个特性的一个使用场景。
场景
假设订单系统对外封装了一套完整的RPC服务(比如一套“贷款”流程),这套流程主要有从“意向创建”到“还款”7个流程(每个流程其实对应一套接口),同时,“贷款流程”内部又有并行的3条业务线,比如“Backward”、“Forward”、“ThirdWard”,为方便理解, 见下图:
由于我设计的“贷款流程”有7个步骤,同时3条业务线,按理来说我就需要写21个RPC接口了,显然很不满足OOD的思想,这时@PostConstruct就可以派上用场,只要7个接口就完全搞定,用“提交订单”这一步骤举例,请见下文。
详情
假设“提交订单”这一个步骤中,对应的3条业务向都可能会有“校验”、“提交”、“发送资质审核”、“发送操作记录”这4个子流程,通过抽取接口和抽象类,整理了类图如下:
从顶向下看:
(1)通过7步流程,抽象除了ISubmitService接口,其中只暴露submit()方法。
(2)对3条业务线进行抽象,AbsSubmitService类抽象出了共有属性hasMap,
数据类型为Map<Integer,AbsSubmitService>(这个属性就是关键),以及对应的子类抽象出的方法,和自己特有的私有方法initChildren().
(3)个具体实现类中分别对应了:
1、checkBusinessDate()
2、execSubmit()
3、sendQualAudit()
4、sendHistoryRecord()
这4个方法,只是各自的实现有差异。
那么到底@PostConstruct是怎么派上用场的,了解了上述业务结构以及接口、类图设计之后,参考我设计的伪代码:
代码展示
1.接口
public interface ISubmitService {
/**
* 订单提交
* @param form
* @return
* @throws BusinessException
*/
Integer submit(ApplyForm form) throws BusinessException;
}
2.抽象类(important)
public class AbsSubmitService implements ISubmitService{
private static final Logger logger = LoggerFactory.getLogger(AbsSubmitService.class);
/*@Autowired
protected ApplyDao applyDao;*/
public static Map<Integer, AbsSubmitService> hashMap = new ConcurrentHashMap<>();//存放实体bean
@PostConstruct
protected void initChildren(){
hashMap.put(getType(),this);
}
@Override
@Transactional(rollbackFor = Throwable.class)
public Integer submit(final ApplyForm form) throws BusinessException {
//step1:校验
checkBusinessData(form);
//step2:提交
execSubmit(form);
//step3:发送资质审核
sendQualAudit(form);
//step4:发送操作记录
sendHistoryRecordContent(form.getId());
return 1;
}
/**
* 获取类型
* @return
*/
abstract Integer getType();
/**
* step1:校验业务
* @param form
* @throws BusinessException
*/
abstract void checkBusinessData(MMCLoanApplyForm form) throws BusinessException;
/**
* step2:提交
* @param form
* @return
*/
abstract Integer execSubmit(MMCLoanApplyForm form)throws BusinessException;
/**
* step3:发送资质审核
* @param form
* @throws BusinessException
*/
abstract void sendQualAudit(MMCLoanApplyForm form) throws BusinessException;
/**
* step4:发送操作记录
* @param form
* @throws BusinessException
*/
protected void sendHistoryRecordContent(Long applyId, Boolean isNew) {
//todo
}
}
3.Backward实现类
public class BackwardSubmitServer extends AbsSubmitService{
private static final Logger logger = LoggerFactory.getLogger(AbsSubmitService.class);
@Override
Integer getType() {
logger.error("伪代码:反向业务--获取类型");
return BusinessFlowEnum.BackWard.getIndex();
}
@Override
Integer execSubmit(ApplyForm form) throws BusinessException {
logger.error("伪代码:反向业务--提交");
//todo 反向提交
}
@Override
void sendQualAudit(ApplyForm form) throws BusinessException {
logger.error("伪代码:反向业务--发送资质审核");
//todo 反向发送资质审核
}
@Override
void checkBusinessData(MMCLoanApplyForm form) throws BusinessException {
logger.error("伪代码:反向业务--校验业务");
//todo 反向校验
}
}
4.Forward实现类
public class ForwardSubmitServer extends AbsSubmitService{
private static final Logger logger = LoggerFactory.getLogger(AbsSubmitService.class);
@Override
Integer getType() {
logger.error("伪代码:正向业务--获取类型");
return BusinessFlowEnum.Forward.getIndex();
}
@Override
Integer execSubmit(ApplyForm form) throws BusinessException {
logger.error("伪代码:正向业务--提交");
//todo 正向提交
}
@Override
void sendQualAudit(ApplyForm form) throws BusinessException {
logger.error("伪代码:正向业务--发送资质审核");
//todo 正向发送资质审核
}
@Override
void checkBusinessData(MMCLoanApplyForm form) throws BusinessException {
logger.error("伪代码:正向业务--校验业务");
//todo 正向校验
}
}
5.Thirdward实现类
public class ThirdSubmitServer extends AbsSubmitService{
private static final Logger logger = LoggerFactory.getLogger(AbsSubmitService.class);
@Override
Integer getType() {
logger.error("伪代码:正向业务--获取类型");
return BusinessFlowEnum.THIRD.getIndex();
}
@Override
Integer execSubmit(ApplyForm form) throws BusinessException {
logger.error("伪代码:正向业务--提交");
//todo 正向提交
}
@Override
void sendQualAudit(ApplyForm form) throws BusinessException {
logger.error("伪代码:正向业务--发送资质审核");
//todo 正向发送资质审核
}
@Override
void checkBusinessData(MMCLoanApplyForm form) throws BusinessException {
logger.error("伪代码:正向业务--校验业务");
//todo 正向校验
}
}
6.业务标识枚举
/**
* 标识业务线的枚举
* @author zhenhua.zhang
*/
public enum BusinessFlowEnum {
Forward(1,"正向业务"),
BackWard(2,"反向业务"),
THIRD(3,"三方业务");
private int index;
private String name;
BusinessFlowEnum(int index, String name) {
this.index = index;
this.name = name;
}
@Override
public int getIndex() {
return index;
}
@Override
public String getName() {
return name;
}
public static BusinessFlowEnum get(int index) {
for (BusinessFlowEnum e : BusinessFlowEnum.values()) {
if (e.getIndex() == index) {
return e;
}
}
return null;
}
public static String getNameByIndex(int index) {
BusinessFlowEnum node = get(index);
return node == null ? null : node.getName();
}
}
如上所述,基本结构出来了,注意抽象类AbsSubmitService中的这句:
public static Map<Integer, AbsSubmitService> hashMap = new ConcurrentHashMap<>();//存放实体bean
如果读过spring源码就会发现,“AbstractBeanFactory”中有类似的设计:
private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
这不就是IOC吗?对,这就是容器的思想,AbsSubmitService中的hasMap变量就是一个容器,用于装3条业务线的具体实现,再看被@PostConstruct修饰的“initChildren”方法:
@PostConstruct
protected void initChildren(){
hashMap.put(getType(),this);
}
用来向hasMap变量中通过getType()放入具体的子类实现,此处设计来源,参考spring源码:
@Override
public Object getBean(String name) throws Exception {
BeanDefinition beanDefinition = beanDefinitionMap.get(name); //这里哦
if (beanDefinition == null) {
throw new IllegalArgumentException("No bean named " + name + " is defined");
}
Object bean = beanDefinition.getBean();
return bean;
}
通过getType()在3个子类中的实现,来把3个子类往AbsSubmitService中加载:
@Override
Integer getType() {
logger.error("伪代码:正向业务--获取类型");
return BusinessFlowEnum.Forward.getIndex(); //比如正向业务的实现类获取是通过这句
}
So,到此大致原理就都解释清楚了,那么@PostConstruct主角出场,当AbsSubmitService的构造方法执行完成之后,开始加载被@PostConstruct修饰的initChildren(),由于getType()是抽象方法,执行具体实现中的getType()方法,把3个子类加载到hashMap容器当中,此时就可以开始调用啦,下面参考我写的Test伪代码:
public class Test {
public static void main(String[] args) {
//1.实例化form,并赋值
//todo-伪代码
//2.调用sub方法
Integer result = submit(form,productTypeId);
}
@Override
@Transactional(rollbackFor = Throwable.class)
public Integer submit(final ApplyForm form, final Integer productTypeId) throws BusinessException {
try {
//加入分布式锁约束
CreateOrderRedisLock.getInstance().executeWithLock(form.getSaleOrderNo(), new RedisLockCallback() {
@Override
public void execute() throws BusinessException {
AbsSubmitService.hashMap.get(productTypeId).submit(form);
}
});
} catch (BusiLockedException e) {
logger.error("提交功能异常",e.getMessage());
throw new BusinessException("请稍后再试!");
}
return 1;
}
}
加入了分布式锁之后,在样板代码中调用SubmitService中的submit()方法:
AbsSubmitService.hashMap.get(productTypeId).submit(form);
根据接口传入的typeId(结合我写的枚举类),就会在3条业务线中找到对应的方法,提交订单了。
Summary
通过“订单提交”这一个伪代码例子,详细阐述了@PostConstruct的具体场景应用,做到了在3条业务线中最终只提供1个RPC接口的例子,其余6个接口也可以这样搞,为了方便,可以在这个基础上再次抽象一层,如下伪代码:
public interface IBusinessFlow {
/**
* step1 创建意向
*/
void createWishOrder();
/**
* step2 审核风控
*/
void audit();
/**
* step3 下单
*/
void order();
/**
* step4 完善资料
*/
void complete();
/**
* step5 提交订单
*/
void submit();
/**
* step6 放款
*/
void loan();
/**
* step7 还款
*/
void repay();
}
最终在IDE中,目录的结构也因此划分而非常清晰。
五一快乐!
That's all.