liteFlow源码解析
liteFlow简介
liteFlow是一个轻量级微流程框架.liteFlow能够帮助你的项目实现业务组件化
liteFlow能最大程度上解耦,支持即时调整策略的一个中间件
流程架构图
项目源码解析
以官网liteflow-example为例
1.自动装配
解析liteflow的starter中配置文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.yomahub.liteflow.springboot.LiteflowAutoConfiguration,\
com.yomahub.liteflow.springboot.LiteflowExecutorInit
- LiteflowAutoConfiguration
@Configuration
@EnableConfigurationProperties(LiteflowProperty.class)
@ConditionalOnProperty(prefix = "liteflow", name = "rule-source")
public class LiteflowAutoConfiguration {
@Bean
public ComponentScaner componentScaner(){
return new ComponentScaner();
}
@Bean
public FlowExecutor flowExecutor(LiteflowProperty property){
if(StringUtils.isNotBlank(property.getRuleSource())){
List<String> ruleList = Lists.newArrayList(property.getRuleSource().split(","));
FlowExecutor flowExecutor = new FlowExecutor();
flowExecutor.setRulePath(ruleList);
return flowExecutor;
}else{
return null;
}
}
}
这个类设置了liteflow的配置参数, 关键在于判断liteflow配置文件的condition以及配置了ruleSource
扩展一下ComponentScaner
public class ComponentScaner implements BeanPostProcessor, PriorityOrdered {
private static final Logger LOG = LoggerFactory.getLogger(ComponentScaner.class);
public static Map<String, NodeComponent> nodeComponentMap = new HashMap<String, NodeComponent>();
static {
LOGOPrinter.print();
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
@SuppressWarnings("rawtypes")
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Class clazz = bean.getClass();
if(NodeComponent.class.isAssignableFrom(clazz)){
LOG.info("component[{}] has been found",beanName);
NodeComponent nodeComponent = (NodeComponent)bean;
nodeComponent.setNodeId(beanName);
nodeComponentMap.put(beanName, nodeComponent);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
关键点在于实现了BeanPostProcessor, 在nodeComponentMap中放入了所有的NodeComponent实现类(业务实现组件)
- LiteflowExecutorInit
public class LiteflowExecutorInit implements InitializingBean {
@Resource
private FlowExecutor flowExecutor;
@Override
public void afterPropertiesSet() throws Exception {
flowExecutor.init();
}
}
init()具体代码, liteflow支持三种格式配置文件(xml, Java配置类, zk), 本文以xml配置文件为例
public void init() {
XmlFlowParser parser = null;
for(String path : rulePath){
try {
if(isLocalConfig(path)) {
parser = new LocalXmlFlowParser();
}else if(isZKConfig(path)){
if(StringUtils.isNotBlank(zkNode)) {
parser = new ZookeeperXmlFlowParser(zkNode);
}else {
parser = new ZookeeperXmlFlowParser();
}
}else if(isClassConfig(path)) {
Class c = Class.forName(path);
parser = (XmlFlowParser)c.newInstance();
}
parser.parseMain(path);
} catch (Exception e) {
String errorMsg = MessageFormat.format("init flow executor cause error,cannot parse rule file{0}", path);
LOG.error(errorMsg,e);
throw new FlowExecutorNotInitException(errorMsg);
}
}
}
这个类初始化了流程执行器的所需的数据到FlowBus, parserMain的解析方法具体不再展开
2.组件介绍
执行器
执行器FlowExecutor用来执行一个流程,用法为
public <T extends Slot> T execute(String chainId,Object param)
public <T extends Slot> T execute(String chainId,Object param,Class<? extends Slot> slotClazz)
public <T extends Slot> T execute(String chainId,Object param,Class<? extends Slot> slotClazz,Integer slotIndex,boolean isInnerChain)
- chainId: 流程流id
- param: 流程入参
- slotClazz: Slot的子类(业务中建议自己实现, 默认为DefaultSlot)
- isInnerChain: 是否节点内执行
数据槽
在执行器执行流程时会分配唯一的一个数据槽给这个请求。不同请求的数据槽是完全隔离的。
默认的数据槽主要是一个ConcurrentHashMap,里面存放着liteFlow的元数据.还有一个双向队列ArrayDeque, 里面存放着步骤数据.所以建议根据业务自行实现.
组件节点
继承NodeComponent
实现process()方法来实现业务
建议实现isAccess()来判断是否进入该组件
其他几个可以覆盖的方法有:
方法isContinueOnError:表示出错是否继续往下执行下一个组件
方法isEnd:表示是否立即结束整个流程
在组件节点里,随时可以通过方法getSlot获取当前的数据槽,从而可以获取任何数据。
路由节点
<flow>
<chain name="chain1">
<then value="node1, node2, node3" />
<then value="node4(node5 | node6)" />
<when value="node7, node8" />
<then value="node9, chain2" />
</chain>
<chain name="chain2">
<then value="node9" />
</chain>
</flow>
1.then节点代表顺序执行, when节点代表异步(同时执行),
2.node4节点是条件组件继承NodeCondComponent实现processCond()方法, 返回需要执行的节点字符串("node5"或"node6")
3. 2.3.0后支持子流程如在chain1中调用chain2, 也可以在代码内隐式调用
3.执行器的运作
FlowExecutor的excute方法
public <T extends Slot> T execute(String chainId,Object param,Class<? extends Slot> slotClazz,Integer slotIndex,boolean isInnerChain) throws Exception{
Slot slot = null;
try{
// 判断FlowBus中chainMap是否为空
if(FlowBus.needInit()) {
// 初始化方法, 在自动装配中提到了
init();
}
// 根据chainId获取对应的chain
Chain chain = FlowBus.getChain(chainId);
if(chain == null){
String errorMsg = MessageFormat.format("couldn't find chain with the id[{0}]", chainId);
throw new ChainNotFoundException(errorMsg);
}
// 如果不是子流程且数据槽还未获取(该方法有可能被invoke隐式代用)
if(!isInnerChain && slotIndex == null) {
// 从DataBus中遍历slots取一个值为null的下标, 赋值一个slot实例后返回
slotIndex = DataBus.offerSlot(slotClazz);
LOG.info("slot[{}] offered",slotIndex);
}
if(slotIndex == -1){
throw new NoAvailableSlotException("there is no available slot");
}
slot = DataBus.getSlot(slotIndex);
if(slot == null) {
throw new NoAvailableSlotException("the slot is not exist");
}
// 往当前slot增加一个当前时间的唯一ID
if(StringUtils.isBlank(slot.getRequestId())) {
slot.generateRequestId();
LOG.info("requestId[{}] has generated",slot.getRequestId());
}
// 如果不是子流程则加当前入参和chaiId设置到slot
if(!isInnerChain) {
slot.setRequestData(param);
slot.setChainName(chainId);
}else {
// 主流程已有入参, 当前入参加到子流程的key
slot.setChainReqData(chainId, param);
}
// 详解见Chain的execute()方法
chain.execute(slotIndex);
return (T)slot;
}catch(Exception e){
String errorMsg = MessageFormat.format("[{0}]executor cause error", slot.getRequestId());
LOG.error(errorMsg,e);
throw e;
}finally{
if(!isInnerChain) {
slot.printStep();
DataBus.releaseSlot(slotIndex);
}
}
}
Chain的execute()方法
public void execute(Integer slotIndex) throws Exception{
if(CollectionUtils.isEmpty(conditionList)){
throw new FlowSystemException("no conditionList in this chain[" + chainName + "]");
}
Slot slot = DataBus.getSlot(slotIndex);
// 遍历chain中的conditionList(xml中then或者when节点)
for (Condition condition : conditionList){
// 如果condition为then则顺序执行value中的node节点
if(condition instanceof ThenCondition){
for(Executable executableItem : condition.getNodeList()){
try{
// 详解见Executable的execute方法
executableItem.execute(slotIndex);
}catch (ChainEndException e){
break;
}
}
// 如果condition为when则创建多个线程执行value中的node节点
}else if(condition instanceof WhenCondition){
// 使用计数器
final CountDownLatch latch = new CountDownLatch(condition.getNodeList().size());
for(Executable executableItem : condition.getNodeList()){
// 当前版本有两个WhenConditionThread, 一个在FlowExecutor中内部类, 一个单独抽离出来.(猜测内部类应该是抽离后忘记删了)
new WhenConditionThread(executableItem,slotIndex,slot.getRequestId(),latch).start();
}
// 等待计数器完成或者超过15秒
latch.await(15, TimeUnit.SECONDS);
}
}
}
Executable的execute方法
public void execute(Integer slotIndex) throws Exception {
if(instance == null){
throw new FlowSystemException("there is no instance for node id " + id);
}
instance.setSlotIndex(slotIndex);
Slot slot = DataBus.getSlot(slotIndex);
try{
// 可以重写isAccess()决定是否执行
if(instance.isAccess()){
// 详解见NodeComponent的excute方法
instance.execute();
// NodeComponent中有一个InheritableThreadLocal<Boolean> isEndTL, 来判断是否停止整个流程.
// InheritableThreadLocal可继承,所以可以影响所有继承了NodeComponent的组件. 但是ThreadLocal是每个线程独有的, 所以对多线程的when并行流无效
if(instance.isEnd()){
LOG.info("[{}]:component[{}] lead the chain to end",slot.getRequestId(),instance.getClass().getSimpleName());
// 抛出异常停止当前流程
throw new ChainEndException("component lead the chain to end");
}
}else{
LOG.info("[{}]:[X]skip component[{}] execution",slot.getRequestId(),instance.getClass().getSimpleName());
}
}catch (Exception e){
// 重写isContinueOnError()方法决定发生错误后是否继续执行(会使isEndTL无效)
if(instance.isContinueOnError()){
String errorMsg = MessageFormat.format("[{0}]:component[{1}] cause error,but flow is still go on", slot.getRequestId(),id);
LOG.error(errorMsg,e);
}else{
String errorMsg = MessageFormat.format("[{0}]:component[{1}] cause error",slot.getRequestId(),id);
LOG.error(errorMsg,e);
throw e;
}
}finally {
// 移除NodeComponent中的InheritableThreadLocal<Integer> slotIndexTL(用于slotIndex的传递)
instance.removeSlotIndex();
// 移除isEndTL
instance.removeIsEnd();
}
}
NodeComponent的excute方法
public void execute() throws Exception{
Slot slot = this.getSlot();
LOG.info("[{}]:[O]start component[{}] execution",slot.getRequestId(),this.getClass().getSimpleName());
slot.addStep(new CmpStep(nodeId, CmpStepType.SINGLE));
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 执行重写的业务方法(如果是NodeCondComponent组件会将要运行的nodeId绑定到solt中)
process();
stopWatch.stop();
long timeSpent = stopWatch.getTime();
// slot.addStep(new CmpStep(nodeId, CmpStepType.END));
//性能统计
CompStatistics statistics = new CompStatistics();
statistics.setComponentClazzName(this.getClass().getSimpleName());
statistics.setTimeSpent(timeSpent);
MonitorBus.load().addStatistics(statistics);
// condition组件
if(this instanceof NodeCondComponent){
// 获取到绑定的nodeId, 并执行
String condNodeId = slot.getCondResult(this.getClass().getName());
if(StringUtils.isNotBlank(condNodeId)){
Node thisNode = FlowBus.getNode(nodeId);
Executable condExecutor = thisNode.getCondNode(condNodeId);
if(condExecutor != null){
// 执行对应的NodeCompenent
condExecutor.execute(slotIndexTL.get());
}
}
}
LOG.debug("[{}]:componnet[{}] finished in {} milliseconds",slot.getRequestId(),this.getClass().getSimpleName(),timeSpent);
}