之所以是要做状态机,是因为最近工作上的业务成分实在侵入性太多,代码可以搞定,但是不易维护,更不够优雅。比如
一个搜索框,对应四个选项卡类型搜索,牵扯到4个或更多的服务调用,拿到数据还需去重聚合,提取想要的数据后,还需要本地数据库匹配组装,等等。
比如这是控制器,我需要分发出去,部分外部服务还不支持分页
/*
顾客APP查询接口
*/
@MethodInfo(code = "CustomerControllerQuery")
@GetMapping("/query")
String customersQuery(
@PageableDefault Pageable pageable,
Principal principal,
String keyword,
@RequestParam(defaultValue = "0") int tabIndex
){
String userName = principal.getName();
switch (tabIndex){
case 0:
return handlePhoneNumber(keyword,userName,pageable);
case 1:
return handleGoods(keyword,userName);
case 2:
return handlePreStoreOrder(keyword,userName);
case 3:
return handleDisposition(keyword,userName,pageable);
}
return JSON.toJSONString(ResultDTO.builder()
.message("操作失败")
.isSuccess(false)
.build());
}
比如从第二个选项卡筛选顾客关联的一些信息
String handlePreStoreOrder(String keyword,String userName){
val prestoreOrderReq = IOrderServiceClient
.PrestoreOrderReq
.builder()
.employeeCode(userName)
.orderName(keyword)
.build();
ResultDTO>
resultDTO = iOrderServiceClient.findOrderList(
iOrderServiceClient.beanToMap(prestoreOrderReq)
);
if (resultDTO.getIsSuccess()){
val data=resultDTO.getData().stream()
.filter(c -> c.getCustomerId() != null).collect(Collectors.toList());
Set customerSet=
new TreeSet<>((o1, o2) -> o1.getCustomerId().compareTo(o2.getCustomerId()));
customerSet.addAll(data);
if (customerSet != null && customerSet.size() > 0) {
val customerTuples=customerSet.parallelStream()
.map(
c -> {
List tasks=taskRepository.findAllByUserNameAndCustomerId(
1,
userName, c.getCustomerId()
);
return CustomerTuple.builder()
.id(c.getCustomerId())
.customerName(c.getCustomerName())
.customerMobile(c.getCustomerPhone())
.existTask((tasks == null || tasks.size() == 0) ? false : true)
.tasks(
tasks
)
.build();
}
).collect(Collectors.toList());
return JSON.toJSONString(ResultDTO.builder()
.data(buildPageInfo(customerTuples))
.build(), new SimplePropertyPreFilter() {
{
getExcludes().add("taskDetails");
}
}, WriteMapNullValue);
}
}
return JSON.toJSONString(ResultDTO.builder().isSuccess(resultDTO.getIsSuccess()).data(buildPageInfo(resultDTO.getData())).build());
}
业务无错,只是架构规划,导致实现起来非常不流畅
所以这俩天产生了关于状态机的想法,并且初步实现了
需要提供具体的mapFunc容器,为了优雅的使用,我也做了一个提取注解的mapFunc,使用者也可以使用如下的实例,就可以产生mapFunc容器
@Slf4j
@Builder
public class DemoFuncMap implements IMapFunction {
@MapFunctionListener(name = "测试01",group = "t",code = "test01")
public StateNode test01(StateNode
这是state对应的Func
当然如果你想更灵活或者丰富使用可以传递一个
@Slf4j
//@Component
@Data
@Accessors(chain = true)
public abstract class StateComponent
>,
Response extends ResultDTO,Ex extends Exception> {
Container自组类型就是mapFunc的映射容器
下面我来几本介绍下StateComponent这个组件吧
一.StateComponent结构
a.他是一个抽象类(为了实现生命周期,参考andorid的Activity的实现)
简单介绍下生命周期
/*
组件创建
提供初始化对象
*/
public abstract T onCreate();
/*
提供属性加载
提供容器加载
设置结束节点
*/
public abstract Pair onStart();
/*
优先使用注解class的mapFunction
*/
public Container onLoadContainer(IMapFunction iMapFunction,Container injectContainer){
Container container = injectContainer;
if (this.iMapFunctionListenerProcess!=null){
container = (Container) this.iMapFunctionListenerProcess.handler(iMapFunction);
}
return container;
}
b.别名类型 比较不太友好的是java不支持灵活的自组别名类型(swift kotlin就很灵活实现了)
>,
Response extends ResultDTO,Ex extends Exception>
第一个表示继承状态节点的类型,第二个Resource表示资源的类型,第三个是存储mapFunc的容器,第四个是响应,第五个是异常类型
c.状态的组装及循环执行
这个也属于生命周期内部方法,使用可选择性覆盖,也可默认执行
考虑到状态的更替,所以使用状态体的循环知道发生异常或执行到终止状态
/*
组件执行
*/
public T onProcess(Container functionContainer){
T responseNode=null;
//结束flag
boolean flag = false;
while (!flag){
val loopState = iTranslateState.translate(this);
flag = loopState.end();
//到达最后一个状态节点
//或者发生异常了的情况,终止
if (flag){
//结束所有节点循环
log.info("状态循环机制结束");
//执行最后的终点节点状态
log.info("执行最后的终点节点状态");
responseNode = iTranslateState.translate(this).getStateCode();
}
}
return responseNode;
}
节点间链表式的引用,并且处理传递捕获到的异常,节点续传
@Override
public StateComponent
translate(
StateComponent stateComponent
)
{
//判断当前是否最后一个节点
//Boolean endFlag = stateComponent.getStateCode().end(stateComponent.getEndStateCode());
/*
最后一个节点可以设置响应,灵活配置
*/
//if (endFlag) stateNode.setResponse(ResultDTO.builder().message("最后一个节点可以设置响应").build());
val current = stateComponent.getStateCode();
val next = iComboMapRouterProcessFunc.routerFunc(
current,
stateComponent.getContainer(),
current
);
if (next.getEx()!=null){
stateComponent.setStateCode(stateComponent.getEndStateCode());
//组装异常到最后节点
stateComponent.getEndStateCode().setEx(next.getEx());
return stateComponent;
}
current.setAfterState(next);
//否则返回新节点,并设置上一节点,前后关联
next.setBeforeState(current);
stateComponent.setStateCode(
(T) next
);
return stateComponent;
}
d.注解式的mapFunc定义
这里我在状态3做了一个测试异常,以便于做状态处理及传递的异常处理,
异常处理,分了俩处捕获
a.mapFunc容器里接口类型对接反射的函数引用的异常捕获并设置到节点内部去
//函数转调method,带同代理注解方法
IMapRouterProcessStateNodeFunc
iMapRouterProcessStateNodeFunc =
currentNode -> {
StateNode newStateNode = null;
try {
//newStateNode = (StateNode) method.invoke(clazz,currentNode);
newStateNode = (StateNode) method.invoke(existMapFunction,currentNode);
} catch (IllegalAccessException e) {
e.printStackTrace();
currentNode.setEx(e);
} catch (InvocationTargetException e) {
e.printStackTrace();
currentNode.setEx(e);
}finally {
if (newStateNode==null){
newStateNode = currentNode;
}
}
return newStateNode;
};
b.除了a所术,StateNode节点本身的定义也做了异常的处理机制
他实现了IStateException这个异常代理,所以在状态机的执行就可以很方便的补货异常信息,至于a的情况,是因为反射本身的异常比较特殊,所以单独处理,这样便于区分异常情况(业务导致还是代码本身结构异常)
public class StateNode implements IExactFilter,IStateEndFace,IStateNodeFlow,IStateException
以下就是一个简单的状态的循环的机遇注解的例子demo
@Slf4j
@Builder
public class DemoFuncMap implements IMapFunction {
@MapFunctionListener(name = "测试01",group = "t",code = "test01")
public StateNode test01(StateNode
结尾:
希望有时间写到状态机匹配(二)