根据上一篇文章所述,得知@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.