-
APQC流程框架体系
2018-07-19 19:26:18一级流程:价值链,是公司最高级别流程。每一个方框代表一个流程链。业务流程链是指一组联系紧密并行/串行的流程。 二级流程:流程链,是对一级流程模块的流程组合。每个方框代表一组流程,又称“流程簇”。 三级... -
轻量级流程编排框架liteFlow
2021-01-23 19:43:52前言 在每个公司的系统中,总有一些拥有复杂业务逻辑的系统,这些系统承载着核心业务逻辑,几乎每个需求都和这些核心业务有关,...如要灵活改变业务流程的顺序,则要进行代码大改动进行抽象,重新写方法。实时热变更前言
在每个公司的系统中,总有一些拥有复杂业务逻辑的系统,这些系统承载着核心业务逻辑,几乎每个需求都和这些核心业务有关,这些核心业务业务逻辑冗长,涉及内部逻辑运算,缓存操作,持久化操作,外部资源调取,内部其他系统RPC调用等等。时间一长,项目几经易手,维护的成本得就会越来越高。各种硬代码判断,分支条件越来越多。代码的抽象,复用率也越来越低,各个模块之间的耦合度很高。一小段逻辑的变动,会影响到其他模块,需要进行完整回归测试来验证。如要灵活改变业务流程的顺序,则要进行代码大改动进行抽象,重新写方法。实时热变更业务流程?几乎很难实现。
开源解决方案
说到流程引擎,开源界有大名鼎鼎的老牌开源软件JBPM,也有近几年非常流行的Activiti和Flowable。他们都是基于BPM协议,可以做到基于角色任务的流传,逻辑的流转。并且很多基于BPM协议的编辑工具都能做可视化的编辑。
但今天我要介绍的,是一款轻量级的流程编排框架——Liteflow。
Liteflow主要致力于逻辑驱动的编排。可以满足于大部分的生产业务场景。和以上著名的开源流程引擎相比,虽然不如他们那么全面,但是胜在轻量,高性能和极少的学习成本。而且这些项目都是国外开源项目,集成起来相对比较重,文档本地化也做的不够好。Liteflow拥有完善的本地文档和使用范例。能帮助你的核心系统变得更加灵活,更加易扩展。是一个解耦你系统的利器。
Liteflow框架的作用
Liteflow就是为解耦复杂逻辑而生,如果你要对复杂业务逻辑进行新写或者重构,用liteflow最合适不过。它是一个轻量,快速的组件式流程引擎框架,组件编排,帮助解耦业务代码,让每一个业务片段都是一个组件。
使用Liteflow,你需要去把复杂的业务逻辑按代码片段拆分成一个个小组件,并定义一个规则流程配置。这样,所有的组件,就能按照你的规则配置去进行复杂的流转。同时Liteflow支持规则文件的热加载,即时完成修改生效。并提供多种持久化规则的方式的扩展。
Liteflow的设计原则
Liteflow是基于工作台模式进行设计的,何谓工作台模式?
n个工人按照一定顺序围着一张工作台,按顺序各自生产零件,生产的零件最终能组装成一个机器,每个工人只需要完成自己手中零件的生产,而无需知道其他工人生产的内容。每一个工人生产所需要的资源都从工作台上拿取,如果工作台上有生产所必须的资源,则就进行生产,若是没有,就等到有这个资源。每个工人所做好的零件,也都放在工作台上。
这个模式有几个好处:
- 每个工人无需和其他工人进行沟通。工人只需要关心自己的工作内容和工作台上的资源。这样就做到了每个工人之间的解耦和无差异性。
- 即便是工人之间调换位置,工人的工作内容和关心的资源没有任何变化。这样就保证了每个工人的稳定性。
- 如果是指派某个工人去其他的工作台,工人的工作内容和需要的资源依旧没有任何变化,这样就做到了工人的可复用性。
- 因为每个工人不需要和其他工人沟通,所以可以在生产任务进行时进行实时工位更改:替换,插入,撤掉一些工人,这样生产任务也能实时的被更改。这样就保证了整个生产任务的灵活性。
这个模式映射到Liteflow框架里,工人就是组件,工人坐的顺序就是流程配置,工作台就是上下文,资源就是参数,最终组装的这个机器就是这个业务。正因为有这些特性,所以Liteflow能做到统一解耦的组件和灵活的装配。
springboot里快速配置
Liteflow支持了springboot的自动装配,当然Liteflow也为非springboot和非spring的项目也提供了支持,这里仅以springboot项目为示例进行介绍:
依赖最新的依赖包:
<dependency> <groupId>com.yomahub</groupId> <artifactId>liteflow-spring-boot-starter</artifactId> <version>2.3.3</version> </dependency>
配置上规则路径:
liteflow.rule-source=config/flow.xml
定义组件
Liteflow希望用户把复杂逻辑拆分成一个个可复用的组件,所以你得定义你的组件,组件的定义很简单,你需要继承
NodeComponent
类,然后实现process
方法就行,以下为示例:@Component("test") public class TestComponent extends NodeComponent { @Override public void process() { Slot slot = this.getSlot();//slot为这个请求的上下文 //这里为你的业务处理逻辑 } }
这里会有童鞋问,我的业务方法需要入参和出参怎么办,如何传递呢?
Liteflow为每个线程都自动分配了唯一的一个slot,可以理解为上下文。想一想上面说的那个模型,每个组件不需要和其他组件进行信息互通,所需要的参数从slot里取就是了,同时,执行完业务逻辑之后,把结果也放入slot里。所以每个组件都是独立的无参构造,这样就消除了每个组件的差异性。
这里的slot能贯穿所有组件,每一个组件都可以访问到slot里所有的数据。当然每个请求之间的slot,Liteflow做了严格的隔离,不用担心数据会串的问题。
Liteflow提供的默认Slot是一个弱类型的对象,这里建议使用者自己定义一个值对象,只需要继承
AbsSlot
类,便可成为你自己的Slot。更加贴合业务。组件除了必须要实现的
process
方法,还有几个可选实现:isAccess:表示是否进入该节点,可以用于业务参数的预先判断
isContinueOnError:表示出错是否继续往下执行下一个组件,默认为false
isEnd:表示是否立即结束整个流程 ,默认为false,也可以在业务日志里根据业务判断来调用this.setIsEnd(true)来结束整个流程。
@Component("test") public class TestComponent extends NodeComponent { @Override public void process() { Slot slot = this.getSlot();//slot为这个请求的上下文 //这里为你的业务处理逻辑 } @Override public boolean isAccess() { Slot slot = this.getSlot(); //这里做你的参数检查,如果没获取到必须的业务参数,就不会进入该组件 boolean checkResult = true;//模拟检查结果为true return checkResult; } @Override public boolean isContinueOnError() { return super.isContinueOnError();//默认为false } @Override public boolean isEnd() { return super.isEnd();//默认为false } }
你只需定义你的业务组件,之后,在启动时,Liteflow会自动扫描到你定义的所有组件,并进行加载。
编辑规则文件
实现完了组件之后,你需要定义规则文件,之前规则文件的路径配置在了
config/flow.xml
中,所以我们要编辑这个文件。Liteflow的规则文件定义非常简单好理解。简单的配置,但是能覆盖大部分的应用场景。
先来看一个示例:
<chain name="chain1"> <then value="a,c"/> <when value="b,d"/> <then value="e,f,g"/> </chain>
在Liteflow中,定义了then和when两种线程执行方式,then代表串行,上面的示例中,c必须要等a执行完才能执行。when代表并行,上面的示例中,b,d同时执行。并且b,d都执行完了,下面的e,f,g才能挨个顺序执行。
再来看个稍微复杂点的:
<chain name="chain1"> <then value="a,c(b|d)"/> <then value="e,f,g"/> </chain>
Liteflow提供了条件组件,这种节点的职责就是路由,根据业务逻辑来路由到b节点还是d节点。
条件组件的定义示例如下,需要去继承
NodeCondComponent
这个类,最终返回的b就是最终要路由到的节点@Component("c") public class CComponent extends NodeCondComponent { @Override public String processCond() throws Exception { //你的业务逻辑 return "b"; } }
Liteflow允许你编辑嵌套的流程,例子如下:
<chain name="chain1"> <then value="a,c,strategy1,g"/> </chain> <chain name="strategy1"> <then value="m(m1|m2|strategy2)"/> </chain> <chain name="strategy2"> <then value="q,p(p1|p2)"/> </chain>
在这个例子中,这3条链路是串起来执行的,在xml里,可以写你的组件id,也可以写流程id。配合之前的例子,是不是能表达的流程就更加丰富了点呢。
以上3个例子涵盖了Liteflow最主要的功能,当然Liteflow还提供一些其他的特性,比如如何进行循环执行,如何打印步骤,并且Liteflow还提供了一个简易的监控模块,用于统计你的组件执行情况。这里就不一一介绍了。具体你可以点击Liteflow的Gitee主页进行查看:
示例工程
为了方便用户的使用,Liteflow在项目里提供了一个测试用例,你可以直接拿来跑:
同时作者还做了一个带简单业务的示例工程,来演示如何具体实践:
这个简单业务是一个电商场景的价格计算的案例,如何通过拆分组件来组合不同的影响价格的业务。并且这个示例工程还提供了一个简单的页面供大家进行调试:
最后
在流程编排开源上,国内一直没有特别著名的开源项目。Liteflow的体量虽然无法和业界著名的流程引擎相比,但是在某些场景,的确提供了轻量级的解决方案。并且Liteflow经过了公司生产大流量业务的考验,在稳定性和性能方面有一定保障。希望Liteflow这个开源框架能帮助到有这方面业务需要的同学们。
关于我
-
别再用硬编码写业务流程了,试试这款轻量级流程编排框架
2021-01-07 13:25:59前言 在每个公司的系统中,总有一些拥有复杂业务逻辑的系统,这些系统承载着核心业务逻辑,几乎每个需求都和这些核心业务有关,...如要灵活改变业务流程的顺序,则要进行代码大改动进行抽象,重新写方法。实时热变更业前言
在每个公司的系统中,总有一些拥有复杂业务逻辑的系统,这些系统承载着核心业务逻辑,几乎每个需求都和这些核心业务有关,这些核心业务业务逻辑冗长,涉及内部逻辑运算,缓存操作,持久化操作,外部资源调取,内部其他系统RPC调用等等。时间一长,项目几经易手,维护的成本得就会越来越高。各种硬代码判断,分支条件越来越多。代码的抽象,复用率也越来越低,各个模块之间的耦合度很高。一小段逻辑的变动,会影响到其他模块,需要进行完整回归测试来验证。如要灵活改变业务流程的顺序,则要进行代码大改动进行抽象,重新写方法。实时热变更业务流程?几乎很难实现。
开源解决方案
说到流程引擎,开源界有大名鼎鼎的老牌开源软件JBPM,也有近几年非常流行的Activiti和Flowable。他们都是基于BPM协议,可以做到基于角色任务的流传,逻辑的流转。并且很多基于BPM协议的编辑工具都能做可视化的编辑。
但今天我要介绍的,是一款轻量级的流程编排框架——Liteflow。
Liteflow主要致力于逻辑驱动的编排。可以满足于大部分的生产业务场景。和以上著名的开源流程引擎相比,虽然不如他们那么全面,但是胜在轻量,高性能和极少的学习成本。而且这些项目都是国外开源项目,集成起来相对比较重,文档本地化也做的不够好。Liteflow拥有完善的本地文档和使用范例。能帮助你的核心系统变得更加灵活,更加易扩展。是一个解耦你系统的利器。
Liteflow框架的作用
Liteflow就是为解耦复杂逻辑而生,如果你要对复杂业务逻辑进行新写或者重构,用liteflow最合适不过。它是一个轻量,快速的组件式流程引擎框架,组件编排,帮助解耦业务代码,让每一个业务片段都是一个组件。
使用Liteflow,你需要去把复杂的业务逻辑按代码片段拆分成一个个小组件,并定义一个规则流程配置。这样,所有的组件,就能按照你的规则配置去进行复杂的流转。同时Liteflow支持规则文件的热加载,即时完成修改生效。并提供多种持久化规则的方式的扩展。
Liteflow的设计原则
Liteflow是基于工作台模式进行设计的,何谓工作台模式?
n个工人按照一定顺序围着一张工作台,按顺序各自生产零件,生产的零件最终能组装成一个机器,每个工人只需要完成自己手中零件的生产,而无需知道其他工人生产的内容。每一个工人生产所需要的资源都从工作台上拿取,如果工作台上有生产所必须的资源,则就进行生产,若是没有,就等到有这个资源。每个工人所做好的零件,也都放在工作台上。
这个模式有几个好处:
- 每个工人无需和其他工人进行沟通。工人只需要关心自己的工作内容和工作台上的资源。这样就做到了每个工人之间的解耦和无差异性。
- 即便是工人之间调换位置,工人的工作内容和关心的资源没有任何变化。这样就保证了每个工人的稳定性。
- 如果是指派某个工人去其他的工作台,工人的工作内容和需要的资源依旧没有任何变化,这样就做到了工人的可复用性。
- 因为每个工人不需要和其他工人沟通,所以可以在生产任务进行时进行实时工位更改:替换,插入,撤掉一些工人,这样生产任务也能实时的被更改。这样就保证了整个生产任务的灵活性。
这个模式映射到Liteflow框架里,工人就是组件,工人坐的顺序就是流程配置,工作台就是上下文,资源就是参数,最终组装的这个机器就是这个业务。正因为有这些特性,所以Liteflow能做到统一解耦的组件和灵活的装配。
springboot里快速配置
Liteflow支持了springboot的自动装配,当然Liteflow也为非springboot和非spring的项目也提供了支持,这里仅以springboot项目为示例进行介绍:
依赖最新的依赖包:
<dependency> <groupId>com.yomahub</groupId> <artifactId>liteflow-spring-boot-starter</artifactId> <version>2.3.3</version> </dependency>
配置上规则路径:
liteflow.rule-source=config/flow.xml
定义组件
Liteflow希望用户把复杂逻辑拆分成一个个可复用的组件,所以你得定义你的组件,组件的定义很简单,你需要继承
NodeComponent
类,然后实现process
方法就行,以下为示例:@Component("test") public class TestComponent extends NodeComponent { @Override public void process() { Slot slot = this.getSlot();//slot为这个请求的上下文 //这里为你的业务处理逻辑 } }
这里会有童鞋问,我的业务方法需要入参和出参怎么办,如何传递呢?
Liteflow为每个线程都自动分配了唯一的一个slot,可以理解为上下文。想一想上面说的那个模型,每个组件不需要和其他组件进行信息互通,所需要的参数从slot里取就是了,同时,执行完业务逻辑之后,把结果也放入slot里。所以每个组件都是独立的无参构造,这样就消除了每个组件的差异性。
这里的slot能贯穿所有组件,每一个组件都可以访问到slot里所有的数据。当然每个请求之间的slot,Liteflow做了严格的隔离,不用担心数据会串的问题。
Liteflow提供的默认Slot是一个弱类型的对象,这里建议使用者自己定义一个值对象,只需要继承
AbsSlot
类,便可成为你自己的Slot。更加贴合业务。组件除了必须要实现的
process
方法,还有几个可选实现:isAccess:表示是否进入该节点,可以用于业务参数的预先判断
isContinueOnError:表示出错是否继续往下执行下一个组件,默认为false
isEnd:表示是否立即结束整个流程 ,默认为false,也可以在业务日志里根据业务判断来调用this.setIsEnd(true)来结束整个流程。
@Component("test") public class TestComponent extends NodeComponent { @Override public void process() { Slot slot = this.getSlot();//slot为这个请求的上下文 //这里为你的业务处理逻辑 } @Override public boolean isAccess() { Slot slot = this.getSlot(); //这里做你的参数检查,如果没获取到必须的业务参数,就不会进入该组件 boolean checkResult = true;//模拟检查结果为true return checkResult; } @Override public boolean isContinueOnError() { return super.isContinueOnError();//默认为false } @Override public boolean isEnd() { return super.isEnd();//默认为false } }
你只需定义你的业务组件,之后,在启动时,Liteflow会自动扫描到你定义的所有组件,并进行加载。
编辑规则文件
实现完了组件之后,你需要定义规则文件,之前规则文件的路径配置在了
config/flow.xml
中,所以我们要编辑这个文件。Liteflow的规则文件定义非常简单好理解。简单的配置,但是能覆盖大部分的应用场景。
先来看一个示例:
<chain name="chain1"> <then value="a,c"/> <when value="b,d"/> <then value="e,f,g"/> </chain>
在Liteflow中,定义了then和when两种线程执行方式,then代表串行,上面的示例中,c必须要等a执行完才能执行。when代表并行,上面的示例中,b,d同时执行。并且b,d都执行完了,下面的e,f,g才能挨个顺序执行。
再来看个稍微复杂点的:
<chain name="chain1"> <then value="a,c(b|d)"/> <then value="e,f,g"/> </chain>
Liteflow提供了条件组件,这种节点的职责就是路由,根据业务逻辑来路由到b节点还是d节点。
条件组件的定义示例如下,需要去继承
NodeCondComponent
这个类,最终返回的b就是最终要路由到的节点@Component("c") public class CComponent extends NodeCondComponent { @Override public String processCond() throws Exception { //你的业务逻辑 return "b"; } }
Liteflow允许你编辑嵌套的流程,例子如下:
<chain name="chain1"> <then value="a,c,strategy1,g"/> </chain> <chain name="strategy1"> <then value="m(m1|m2|strategy2)"/> </chain> <chain name="strategy2"> <then value="q,p(p1|p2)"/> </chain>
在这个例子中,这3条链路是串起来执行的,在xml里,可以写你的组件id,也可以写流程id。配合之前的例子,是不是能表达的流程就更加丰富了点呢。
以上3个例子涵盖了Liteflow最主要的功能,当然Liteflow还提供一些其他的特性,比如如何进行循环执行,如何打印步骤,并且Liteflow还提供了一个简易的监控模块,用于统计你的组件执行情况。这里就不一一介绍了。具体你可以点击Liteflow的Gitee主页进行查看:
示例工程
为了方便用户的使用,Liteflow在项目里提供了一个测试用例,你可以直接拿来跑:
同时作者还做了一个带简单业务的示例工程,来演示如何具体实践:
这个简单业务是一个电商场景的价格计算的案例,如何通过拆分组件来组合不同的影响价格的业务。并且这个示例工程还提供了一个简单的页面供大家进行调试:
最后
在流程编排开源上,国内一直没有特别著名的开源项目。Liteflow的体量虽然无法和业界著名的流程引擎相比,但是在某些场景,的确提供了轻量级的解决方案。并且Liteflow经过了公司生产大流量业务的考验,在稳定性和性能方面有一定保障。希望Liteflow这个开源框架能帮助到有这方面业务需要的同学们。
关于我
我是一个开源作者,也是一名内容创作者。「元人部落」是一个坚持做原创的技术科技分享号,会一直分享原创的技术文章,陪你一起成长。关注回复
liteflow
能加入群聊,这里有很多大佬能和你一起探讨技术,回答你的问题。 -
一篇文章了解mvc框架工作流程
2017-08-25 11:08:09动机argo是58同城开源出来的一个基于java的轻量级mvc框架。这个框架是其13年开源出来源代码,但接下来就没有维护了,但58内部好像还一直维护沿用wf(argo内部称呼)。 但阅读这款轻量级框架源码对于我们理解mvc框架...动机
argo是58同城开源出来的一个基于java的轻量级mvc框架。这个框架是其13年开源出来源代码,但接下来就没有维护了,但58内部好像还一直维护沿用wf(argo内部称呼)。
但阅读这款轻量级框架源码对于我们理解mvc框架运行原理还是有很大裨益的。其代码量不是很大,这也是我读的第一个开源框架源码。同时argo跟springmvc在思想上有很多相似之处,相信读过这个源码,对以后阅读springmvc有会很有帮助。0.知识要求
熟悉google的依赖注入框架guice。最好熟悉java servlet。对tomcat的servlet容器了解 ^_^
UML类图时序图整理
首先我整理了argo的uml类图下载地址,该资源用rational rose打开即可查看。把argo核心类都整理了一遍。先放一张Argo一次请求的时序图吧
1依赖注入中心
argo中大量的使用了依赖注入,源码通读下来,你会发现DI(Dependency Injection)的有点,但初始接触会有一种代码不连贯的感觉。
Argo的依赖注入配置中心是ArgoModule这个类,这里面包含了所有的注入规则,for (Class<? extends ArgoController> clazz : argo.getControllerClasses()) bind(clazz).in(Singleton.class);
上面代码片段中可以发现argo所有controller都是单例实现的。
2框架入口在哪?
这是我要说的第一个问题,servlet容器启动后,又是怎么进入我们这个框架,又是怎样运行我们写的业务逻辑代码的。
拿tomcat来说在其web.xml配置文件中有一个load-on-startup配置项,如果其值<0 表示tomcat在在启动时不会加载该资源(拿servlet举例,你可以发现web.xml的文件中包括servlet,jsp,defaultServlet这三个配置项且其值大于0),tomcat会根据其值的从小到大进行加载。
ArgoFilter就是真个argo处理请求的源头,其实现了Filter接口,当浏览器请求落到web容器上(本文中就是tomcat)。可以看到ArgoFilter#init()方法中实例化了 用于处理请求分发的ArgoDispatcher对象,并且初始化Argo.class
ArgoFilter.java public void init(FilterConfig filterConfig) throws ServletException { ServletContext servletContext = filterConfig.getServletContext(); try { dispatcher = ArgoDispatcherFactory.create(servletContext);//该方法里又初始化了Argo dispatcher.init(); } catch (Exception e) { servletContext.log("failed to argo initialize, system exit!!!", e); System.exit(1); } }
初始化完走ArgoFilter#doFilter方法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpReq = (HttpServletRequest) request; HttpServletResponse httpResp = (HttpServletResponse) response; dispatcher.service(httpReq, httpResp); }
这里便便是系统的真正的入口,可以看到dispatcher对其进行处理,ArgoDispatcher是一个接口
@ImplementedBy(DefaultArgoDispatcher.class) public interface ArgoDispatcher { void init(); void service(HttpServletRequest request, HttpServletResponse response); void destroy(); public HttpServletRequest currentRequest(); public HttpServletResponse currentResponse(); BeatContext currentBeatContext(); }
可以看 @ImplementedBy(DefaultArgoDispatcher.class)这个注解,这是Guice的注解,作用是指该接口的默认实现是DefaultArgoDispatcher,这个实现过程就交流guice实现了,所以在读这个代码了解guice这个依赖注入框架是非常必要的。
在DefaultArgoDispatcher#service方法中绑定了request,response,context等参数DefaultArgoDispatcher.java private BeatContext bindBeatContext(HttpServletRequest request, HttpServletResponse response) { Context context = new Context(request, response); localContext.set(context); BeatContext beat = argo.injector().getInstance(defaultBeatContextKey); // 增加默认参数到model beat.getModel().add("__beat", beat); context.setBeat(beat); return beat; }
这里有一个ThreadLocal localContext变量,他会为每一个线程创建一个Context的副本,等线程结束该副本便销毁,BeatContext也是通过guice注入的。
3分发路由
在请求进来后,根据请求url找到我们实际的controller并且并且运行又是一个关键点
DefaultArgoDispatcher.java private void route(BeatContext beat) { try { ActionResult result = router.route(beat); if (ActionResult.NULL == result) result = statusCodeActionResult.getSc404(); result.render(beat); } catch (Exception e) { statusCodeActionResult.render405(beat); e.printStackTrace(); logger.error(String.format("fail to route. url:%s", beat.getClient().getRelativeUrl()), e); //TODO: catch any exceptions. } finally { localContext.remove(); } }
调用代码中可以看到调用了router.route方法执行路由,根据BeatContext得到请求的url及其请求方式(get or post & eg.)。
接下来看一下DefaultRouter里面的代码
@Inject public DefaultRouter(Argo argo, @ArgoSystem Set<Class<? extends ArgoController>> controllerClasses, @StaticActionAnnotation Action staticAction) { this.argo = argo; argo.getLogger().info("initializing a %s(implements Router)", this.getClass()); this.actions = buildActions(argo, controllerClasses, staticAction); argo.getLogger().info("%s(implements Router) constructed.", this.getClass()); }
这是DefaultRouter的构造方法,构造方法中已经注入了controller所有子类的class(不熟悉DI同学看到这个可能有点蒙了,没看到哪里new DefaultRouter啊,如果你熟悉guice的用法,你就不会迷茫了。@Inject这个注解表示构造参数中的参数会自动通过guice给你注入,又有同学问那构造方法中的参数哪里来的,这个同样通过guice注入的啊,还记得开头在guice配置中心提到的所有的controller都是单例实例化的,是的,guice就是相当于给你帮你进行new操作,是不是很方便了)
在构造方法中通过buildActions获得action,这个action所代表的就是服务器上能被访问的资源,包括controller中我们开发的所有接口,所有静态文件。
//DefaultRouter.java List<Action> buildActions(Argo argo, Set<Class<? extends ArgoController>> controllerClasses, Action staticAction) { Set<ArgoController> controllers = getControllerInstances(argo, controllerClasses); return buildActions(controllers, staticAction); }
通过所有的controller获得action
//DefaultRouter.java List<Action> buildActions(Set<ArgoController> controllers, Action staticAction) { List<Action> actions = Lists.newArrayList(); actions.add(staticAction); for (ArgoController controller : controllers) { ControllerInfo controllerInfo = new ControllerInfo(controller); List<ActionInfo> subActions = controllerInfo.analyze(); for(ActionInfo newAction : subActions) merge(actions, MethodAction.create(newAction)); } return ImmutableList.copyOf(actions); }
上面代码就是获得controller中所有的方法。
关于argo自己的拦截器
这里特别摘出来说一下
//ActionInfo.java public ActionInfo(ControllerInfo controllerInfo, Method method, Argo argo) { this.controllerInfo = controllerInfo; this.method = method; this.argo = argo; Path path = AnnotationUtils.findAnnotation(method, Path.class); this.order = path.order(); this.pathPattern = simplyPathPattern(controllerInfo, path); this.paramTypes = ImmutableList.copyOf(method.getParameterTypes()); this.paramNames = ImmutableList.copyOf(ClassUtils.getMethodParamNames(controllerInfo.getClazz(), method)); // 计算匹配的优先级,精确匹配还是模版匹配 isPattern = pathMatcher.isPattern(pathPattern) || paramTypes.size() > 0; Pair<Boolean, Boolean> httpMethodPair = pickupHttpMethod(controllerInfo, method); this.isGet = httpMethodPair.getKey(); this.isPost = httpMethodPair.getValue(); annotations = collectAnnotations(controllerInfo, method); // 拦截器 List<InterceptorInfo> interceptorInfoList = findInterceptors(); preInterceptors = getPreInterceptorList(interceptorInfoList); postInterceptors = getPostInterceptorList(interceptorInfoList); }
ActionInfo的构造方法中对argo使用者编写的controller的所有的注解进行遍历,这里说一下argo的拦截器如何使用,可以看到argo实现了前置拦截器PreInterceptorAnnotation,后置拦截器PostInterceptorAnnotation两个注解及其相关接口,使用者将拦截器类声明相关接口
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @PreInterceptorAnnotation( value =MyI.class) public @interface MyInterceptorAnnotation { }
MyI.java是我自己实现的一个拦截器类,通过PreInterceptorAnnotation/PostInterceptorAnnotation注解关联。看源码好像argo自己的拦截器只能通过这个方式实现,通过ActionInfo类就可以发现其获取拦截器的方法,扫描controller上所有的注解,得到拦截器相关并转为action。
4 controller代码运行
终于要将到开发者在controller写的代码怎么运行的了。
在DefaultRouter类的route方法中
public ActionResult route(BeatContext beat) { RouteBag bag = RouteBag.create(beat); for(Action action : actions) { RouteResult routeResult = action.matchAndInvoke(bag); if (routeResult.isSuccess()) return routeResult.getResult(); } return ActionResult.NULL; }
可以看到这里有个for循环,通过我们前面扫描获取的action调用他们的matchAndInvoke方法
//MethodAction.java @Override public RouteResult matchAndInvoke(RouteBag bag) { if (!actionInfo.matchHttpMethod(bag)) return RouteResult.unMatch(); Map<String, String> uriTemplateVariables = Maps.newHashMap(); boolean match = actionInfo.match(bag, uriTemplateVariables); if (!match) return RouteResult.unMatch(); // PreIntercept for(PreInterceptor preInterceptor : actionInfo.getPreInterceptors()) { ActionResult actionResult = preInterceptor.preExecute(bag.getBeat()); if (ActionResult.NULL != actionResult) return RouteResult.invoked(actionResult); } ActionResult actionResult = actionInfo.invoke(uriTemplateVariables); // PostIntercept for(PostInterceptor postInterceptor : actionInfo.getPostInterceptors()) { actionResult = postInterceptor.postExecute(bag.getBeat(), actionResult); } return RouteResult.invoked(actionResult); }
可以看到先是运行顺序是前置拦截器-controller-后置拦截器,运行完返回路由处理结果RouteResult,如果路由成功(根据url找到对应的controller或者静态资源
//ActionInfo.java ActionResult invoke(Map<String, String> urlParams) { Object[] param = new Object[getParamTypes().size()]; for(int index = 0; index < getParamNames().size(); index++){ String paramName = getParamNames().get(index); Class<?> clazz = getParamTypes().get(index); String v = urlParams.get(paramName); if (v == null) throw ArgoException.newBuilder("Invoke exception:") .addContextVariable(paramName, "null") .build(); // fixMe: move to init if(!getConverter().canConvert(clazz)) throw ArgoException.newBuilder("Invoke cannot convert parameter.") .addContextVariable(paramName, "expect " + clazz.getName() + " but value is " + v) .build(); param[index] = getConverter().convert(clazz, v); } try { Object result = method().invoke(controller(), param); return ActionResult.class.cast(result); } catch (Exception e) { throw ArgoException.newBuilder("invoke exception.", e) .addContextVariables(urlParams) .build(); } }
ActionInfo#invoke方法中通过反射调用controller中对应的方法,执行相应的代码。并且返回ActionResult,接着将其放入RouterResult中。
在这个运行结果其实就是开发者写在controller里的代码运行的结果。
我们可以通过Argo的demo中可以看到//HomeController.java @Path("{phoneNumber:\\d+}") public ActionResult helloView(int phoneNumber) { BeatContext beatContext = beat(); beatContext .getModel() .add("title", "phone") .add("phoneNumber", phoneNumber); return view("hello"); }
上面是demo的代码片段,最后调用AbstractController#view()方法返回的是ActionResult,然后将其set到RouterResult中。
这里提一下我们经常将传递给前端(velocity)的数据放到beat中。这个beat是存在Argo.java中,上面代码通过beat()方法在argo中获取BeatContext,虽然Argo是单例的,但beat是会为每一线程创建一个副本的,所有每个请求会保存自己的值。
5. 交由Response返回
当这些分发路由controller运行完,根据其返回结果ActionResult进行相应的处理
//DefautlArgoDispatcher.java private void route(BeatContext beat) { try { ActionResult result = router.route(beat); if (ActionResult.NULL == result) result = statusCodeActionResult.getSc404(); result.render(beat); } catch (Exception e) { statusCodeActionResult.render405(beat); e.printStackTrace(); logger.error(String.format("fail to route. url:%s", beat.getClient().getRelativeUrl()), e); //TODO: catch any exceptions. } finally { localContext.remove(); } }
还记得这是开头调用的代码,当获得result之后先判断是否为空,空的话我们看到了我们熟悉的404。
不同的返回类型由不同的ActionResult来实现,总的来说ActionResult#render就是将我们的返回结果交给reponse,servlet来返回处理,呈献给用户。
总结
其实这篇文章也就讲了argo一个流程或者说是大概,很多细节我也没细说,不过我相信大流程搞明白之后,一些小细节上的东西自己在慢慢研究也是没问题的。
-
java 流程框架_Java框架MyBatis工作流程是怎样的?
2021-02-28 16:03:20展开全部一、Mybatis介绍MyBatis是一款一流的支持自定义SQL、存储过程和高级映射的持久化32313133353236313431303231363533e4b893e5b19e31333339653664框架。MyBatis几乎消除了所有的JDBC代码,也基本不需要手工去 ...展开全部
一、Mybatis介绍
MyBatis是一款一流的支持自定义SQL、存储过程和高级映射的持久化32313133353236313431303231363533e4b893e5b19e31333339653664框架。MyBatis几乎消除了所有的JDBC代码,也基本不需要手工去 设置参数和获取检索结果。MyBatis能够使用简单的XML格式或者注解进行来配置,能够映射基本数据元素、Map接口和POJOs(普通java对 象)到数据库中的记录。
二、MyBatis工作流程
(1)加载配置并初始化
触发条件:加载配置文件
配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。
(2)接收调用请求
触发条件:调用Mybatis提供的API
传入参数:为SQL的ID和传入参数对象
处理过程:将请求传递给下层的请求处理层进行处理。
(3)处理操作请求 触发条件:API接口层传递请求过来
传入参数:为SQL的ID和传入参数对象
处理过程:
(A)根据SQL的ID查找对应的MappedStatement对象。
(B)根据传入参数对象解析MappedStatement对象,得到最终要执行的SQL和执行传入参数。
(C)获取数据库连接,根据得到的最终SQL语句和执行传入参数到数据库执行,并得到执行结果。
(D)根据MappedStatement对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理结果。
(E)释放连接资源。
(4)返回处理结果将最终的处理结果返回。
orm工具的基本思想
无论是用过的hibernate,mybatis,你都可以法相他们有一个共同点:
从配置文件(通常是XML配置文件中)得到 sessionfactory.
由sessionfactory 产生 session
在session 中完成对数据的增删改查和事务提交等.
在用完之后关闭session 。
在java 对象和 数据库之间有做mapping 的配置文件,也通常是xml 文件。
功能架构
Java框架篇---Mybatis 入门
Mybatis的功能架构分为三层:
API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
多需要添加的驱动包:
Java框架篇---Mybatis 入门
下面进行快速入门:
目录如下:
Java框架篇---Mybatis 入门
实体类User
package com.oumyye.model;
public class User {
private String id;
private String name;
private int age;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
映射文件UserMapping.xml
resultType="com.oumyye.model.User">
select * from user where id=#{id}
资源文件mybatis.xml
测试类:
package test;
import java.io.InputStream;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import com.oumyye.model.User;
public class Tests {
@Test
public void test(){
String resource = "mybatis.xml";
//使用类加载器加载mybatis的配置文件(它也加载关联的映射文件)
InputStream is = Tests.class.getClassLoader().getResourceAsStream(resource);
//构建sqlSession的工厂
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession session = sessionFactory.openSession();
/**
* 映射sql的标识字符串,
* com.oumyye.mapping.UserMapping是userMapper.xml文件中mapper标签的namespace属性的值,
* getUser是select标签的id属性值,通过select标签的id属性值就可以找到要执行的SQL
*/
String statement = "com.oumyye.mapping.UserMapping.getUser";//映射sql的标识字符串
//执行查询返回一个唯一user对象的sql
User user = session.selectOne(statement, "1123");
System.out.println(user.toString());
}
}
结果:
-
轻量级前端MVVM框架avalon - 执行流程2
2019-09-27 08:39:22接上一章执行流程1 在这一大堆扫描绑定方法中应该会哪些实现? 首先我们看avalon能帮你做什么? 数据填充,比如表单的一些初始值,切换卡的各个面板的内容({{xxx}},{{xxx|html}}, ms-html) 类名处理,如隔行... -
自己设计一个的轻量级的RPC框架
2019-03-04 11:14:33自己设计一个的轻量级的RPC框架RPC简介RPC技术选择RPC流程图后期展望 #前言 本人是一个java小菜鸟 但是希望自己成为一个大牛。目前真正学习分布式和微服务的一些相关知识,由于公司没有相关的项目,所以学习起来... -
SSM框架项目搭建系列(一)—SSM框架的简介及搭建流程
2016-11-02 09:50:12首先说明一下,本系列《SSM项目搭建系列》是为了复习并且完整的熟悉一下SSM框架搭建项目的流程。本系列博客会参照Nimiky...Spring框架是一个轻量级的J2EE框架,同时Spring 是最受欢迎的企业级 Java 应用程序开发框架,核 -
Activiti进阶(一)--工作流(流程框架)
2021-01-27 20:59:05工作流 Activiti项目是一项新的基于Apache许可的开源BPM平台,从基础开始构建,旨在提供支持新的BPMN 2.0... Activiti将是一种轻量级,可嵌入的BPM引擎,而且还设计适用于可扩展的云架构。 Activiti将提供宽松的A.. -
轻量级前端MVVM框架avalon - 执行流程1
2019-09-27 08:39:19M,即model,一个普通的JS对象,可能是后台传过来的,也可能是直接从VM中拿到,即VM.$json。有关的这个$json的名字还在商讨 V,即View,HTML页面,通过绑定属性或插值表达式,呈现数据,处理隐藏,绑定... -
一个二级审批流程的具体实现(1)
2019-02-21 17:08:21本 demo 使用 activiti 框架实现了一个二级审批流程示例。包含前端和后端,后端用 springboot+activity 实现,前端用 vue+iview 实现。前后端是分离的。如果你只关注后端,那么你可以不实现前端,用 Postman 来测试... -
Python-SandBox是一个基于django框架开发的轻量级办公平台
2019-08-12 03:27:26SandBox是一个基于django框架开发的轻量级办公平台,主要模块有:权限控制、资产(库存)管理、设备管理、客户信息管理和工单流程管理,其目的在于建立一套规范化、统一化和清晰化的标准服务流程,能够清晰的处理、... -
beego 框架安装流程
2020-06-03 11:16:06beego安装流程beego 框架安装流程 beego 框架安装流程 1、首先下载 $ go get -u github.com/astaxie/beego $ go get -u github.com/beego/bee 2、设置环境变量 右键 我的电脑/此电脑 > 属性 > 高级系统设置 ... -
kubegene:KubeGene-一个完整的基因组测序工作流程管理框架-源码
2021-02-04 12:22:28KubeGene基于轻量级容器技术和官方标准算法。 您可以使用KubeGene进行灵活,可定制的基因组测序过程。 优点 通用工作流设计语法 KubeGene提供了一套完整的基因组测序工作流程语法,可与特定的分析工具分离。 学习... -
3、Solidity编程语言开发框架之高级篇 构建流程
2018-03-18 15:51:08自定义构建流程纵贯Truffle的发展历史看来,默认构造器并不适合每一个人。它有一些明显的缺点,且相比其它构建系统显得不太成熟。由此,Truffle提供了三种方式,来让你扩展默认的构建系统,但让你能体验到绝大部分的... -
功能权限,数据权限等模块进行更高一级的自动化封装,并规范了一套业务实现的代码结构与操作流程,...
2021-01-31 03:49:11OSharp框架 包名称 Nuget稳定版本 Nuget预览版本 下载数 的系统,所有实现了模块基类( )的类都知道一个独立的模块,一个模块可以独立添加服务(AddServices),并在初始化时应用服务(UsePack)进行模块初始化。 ... -
对webmagic的一个example进行Debug——框架执行流程
2017-12-06 21:45:05笔者前言由于业务需求,突然需要在Spring上支持爬虫业务,于是上头选择了这个较为轻量级的框架。 其实笔者在收到爬虫任务时,要求自己空手从零制作一个爬虫,用上了Jsoup作为解析包。早期单单使用Jsoup做小爬虫是能... -
Java轻量级MVC框架Struts2 2.5详解(1)struts2的配置与执行流程
2019-01-16 17:18:57一、struts2的执行流程 二、struts2的版本区别 1.以下环境 jre1.10 ,Tomcat8.5 ,struts2 2.3 会出错,如下:(解决办法升级struts2) ------------------------------------------------------------------... -
Django框架搭建流程
2018-11-26 20:56:12Python的WEB框架有Django、Tornado、Flask ...1、重量级web框架,功能齐全,提供一站式解决思路 2、自带ORM和模板引擎,可以使用jinja等第三方库模板引擎 3、自带的ORM和关系型数据库耦合度高,如果要使用非关系型... -
MochiPHP(轻量级PHP框架) v0.2
2019-10-25 21:12:09MochiPHP 是一个轻量级的PHP5框架,面向组件和页面设计开发,使用一个简单的数据库和对象持久层。MochiPHP 面向页面的设计流程:为每一个页面编写一个类和一个模板;而表单组件可隐藏很多复杂的HTML标签和用户交互... -
自己设计一个的轻量级的RPC框架--服务端netty
2019-03-04 14:31:21自己设计一个的轻量级的RPC框架--服务端netty创建netty服务梳理一下流程netty解码器和TCP的粘包和拆包介绍TCP 拆包/粘包TCP 拆包/粘包 解决办法netty解决拆包/粘包问题 解码器创建netty服务类创建调用服务工具类 ... -
ssJ框架搭建的流程
2019-04-02 22:50:20一个轻量级的ioc和aop的容器框架 ioc:反转控制,依赖注入,就是将创建管理bean的事情交个spring来做,我们只需要在使用时注入 aop:面向切面编程就是按条件拦截,加入相应的功能,例如权限验证就是这个原理 springmvc:... -
从零开始写javaweb框架笔记11-搭建轻量级JAVAWEB框架-搭建开发环境
2017-05-22 14:36:54搭建开发环境 从这里开始,后面学习的内容的代码会上传github。进行git开发,需要有一个github的... 创建一个smart-framework的maven项目,它是一个普通的maven项目,创建过程可以参考之前的创建mavne项目的流程。在 -
activity的启动流程及框架
2016-04-29 01:51:51看了activity的启动流程及相关的框架,记录下自己的理解。有些内容直接取自参考文章。 一、几个概念与框架: 1、ActivityManagerService AMS作为一种系统级服务管理所有Activity,当操作某个Activity时,例如: ...
-
MaxScale 实现 MySQL 读写分离与负载均衡
-
MySQL 高可用工具 heartbeat 实战部署详解
-
Windows系统管理
-
MMM 集群部署实现 MySQL 高可用和读写分离
-
Android架构简介
-
PowerBI重要外部工具详解
-
八大排序-选择排序(二)
-
mrpagz.github.io-源码
-
龙芯生态应用开发基础:C语言精要
-
插入排序实验室直流网络职业生涯040119-源码
-
MySQL 四类管理日志(详解及高阶配置)
-
树
-
MySQL 查询与高级查询(多表、嵌套和正则表达式)
-
基于对称金属包覆光波导的高灵敏度低成本温度传感器
-
2021年 系统架构设计师 系列课
-
自动化测试Python3+Selenium3+Unittest
-
VX6.8下软件使用方法.doc
-
被动锁模掺th光纤激光器的脉冲整形机制
-
联想电脑硬盘保护系统EDU8.0.1iso安装
-
用微服务spring cloud架构打造物联网云平台