精华内容
下载资源
问答
  • APQC流程框架体系

    2018-07-19 19:26:18
    一级流程:价值链,是公司最高级别流程。每一个方框代表一个流程链。业务流程链是指一组联系紧密并行/串行的流程。 二级流程:流程链,是对一级流程模块的流程组合。每个方框代表一组流程,又称“流程簇”。 三级...
  • 轻量级流程编排框架liteFlow

    千次阅读 2021-01-23 19:43:52
    前言 在每个公司的系统中,总有一些拥有复杂业务逻辑的系统,这些系统承载着核心业务逻辑,几乎每个需求都和这些核心业务有关,...如要灵活改变业务流程的顺序,则要进行代码大改动进行抽象,重新写方法。实时热变更

    前言

    在每个公司的系统中,总有一些拥有复杂业务逻辑的系统,这些系统承载着核心业务逻辑,几乎每个需求都和这些核心业务有关,这些核心业务业务逻辑冗长,涉及内部逻辑运算,缓存操作,持久化操作,外部资源调取,内部其他系统RPC调用等等。时间一长,项目几经易手,维护的成本得就会越来越高。各种硬代码判断,分支条件越来越多。代码的抽象,复用率也越来越低,各个模块之间的耦合度很高。一小段逻辑的变动,会影响到其他模块,需要进行完整回归测试来验证。如要灵活改变业务流程的顺序,则要进行代码大改动进行抽象,重新写方法。实时热变更业务流程?几乎很难实现。

    开源解决方案

    说到流程引擎,开源界有大名鼎鼎的老牌开源软件JBPM,也有近几年非常流行的Activiti和Flowable。他们都是基于BPM协议,可以做到基于角色任务的流传,逻辑的流转。并且很多基于BPM协议的编辑工具都能做可视化的编辑。

    但今天我要介绍的,是一款轻量级的流程编排框架——Liteflow。

    Liteflow主要致力于逻辑驱动的编排。可以满足于大部分的生产业务场景。和以上著名的开源流程引擎相比,虽然不如他们那么全面,但是胜在轻量,高性能和极少的学习成本。而且这些项目都是国外开源项目,集成起来相对比较重,文档本地化也做的不够好。Liteflow拥有完善的本地文档和使用范例。能帮助你的核心系统变得更加灵活,更加易扩展。是一个解耦你系统的利器。

    https://gitee.com/bryan31/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主页进行查看:

    https://gitee.com/bryan31/liteFlow

    示例工程

    为了方便用户的使用,Liteflow在项目里提供了一个测试用例,你可以直接拿来跑:

    同时作者还做了一个带简单业务的示例工程,来演示如何具体实践:

    https://gitee.com/bryan31/liteflow-example

    这个简单业务是一个电商场景的价格计算的案例,如何通过拆分组件来组合不同的影响价格的业务。并且这个示例工程还提供了一个简单的页面供大家进行调试:

    最后

    在流程编排开源上,国内一直没有特别著名的开源项目。Liteflow的体量虽然无法和业界著名的流程引擎相比,但是在某些场景,的确提供了轻量级的解决方案。并且Liteflow经过了公司生产大流量业务的考验,在稳定性和性能方面有一定保障。希望Liteflow这个开源框架能帮助到有这方面业务需要的同学们。

    关于我

    展开全文
  • 篇文章我们讲了《实战项目:设计实现流程编排框架(分析)》主要对流程编排框架产生的背景,并做了需求分析,这其中包含功能性需求和非功能性需求,算是在正式开始设计之前做个铺垫。前面提...

    上一篇文章我们讲了《实战项目:设计实现一个流程编排框架(分析)》主要对流程编排框架产生的背景,并做了需求分析,这其中包含功能性需求和非功能性需求,算是在正式开始设计之前做一个铺垫。

    前面提到,项目实战分为分析、设计、实现、测试几个部分讲解,其中分析环节跟面向对象分析很相似,都是做需求梳理。但是在时间项目中设计和实现就不是这么一回事了这里的“设计”指的是系统设计,主要是划分模块,对模块进行设计。

    我们主要分为模型定义、流程加载、流程解析、集成使用这四个模块,来说明编排框架的设计思路,今天讲解的重点是,如何通过合理的设计,实现一个易用,易扩展,灵活,延迟低,高容错等非功能需求的编排框架。

    模型定义

    框架需要定义出服务模型,包括请求参数、输出参数、临时变量,流程定义,节点定义,这几个元素。

    • 请求参数:流程需要定义一个输入参数的基类,在开发的过程中请求参数DTO需要继承这个DTO;

    • 输出参数:流程需要定义一个输出参数的基类,在开发的过程中响应参数DTO需要继承这个DTO;

    • 临时变量:用于在流程执行过程中做上下文管理,包括EL表达式解析变量和流程之间参数扭转;

    • 流程定义:需要定义流程标识ID用于标识流程,流程名称用于定义流程别名,请求参数用于定义流程统一输入DTO,输出参数用于定义流程统一响应DTO,临时变量用于上下文管理,流程描述用于描述整个流程的作用,流程节点用于所有节点以Map的形式保存;

    • 节点定义:需要定义节点ID用于标识节点,节点名称用于定义节点别名,请求参数用于定义流程统一输入DTO,输出参数用于定义流程统一响应DTO,节点描述用于描述整个流程的作用,节点类型用于不同的节点类型包括方法节点、bean节点、服务节点、条件节点等,下一个节点标识用过递归的形式执行节点,组件内容,具体指向执行的节点。

    这几个模型直接的关系是怎么样的?整个流程模型是一个包含关系,流程定义 = 请求参数+输出参数+临时变量+节点定义+自定义属性;节点定义 = 请求参数+输出参数+自定义属性;我们在日常开发的过程中需要定义请求参数和输出参数分别集成请求参数和输出参数的基类,通过配置文件加载流程定义,也就是说运行期阶段我们只会用到流程定义模型。定义如下:

    name: openAccount
    id: test
    desc: 条件执行
    input: com.service.flow.sample.common.model.TestInput
    output: com.service.flow.sample.common.model.TestOutput
    temp: com.service.flow.sample.common.model.TestTemp
    startNode: node1
    nodes:
      - node:
          id: node1
          name: methodInvoke
          component: com.service.flow.sample.common.component.TestComponent:test1
          desc: 单节点执行
          input: com.service.flow.sample.common.model.TestInput
          type: method
          next: node2
      - node:
          id: node2
          name: beanInvoke
          component: testComponent:test2
          desc: 单节点执行
          input: com.service.flow.sample.common.model.TestInput
          type: bean
          next: node3
      - node:
          id: node3
          name: conditionByAge
          component: age>20:node4,age<20:node5
          desc: 单节点执行
          type: condition
      - node:
          id: node4
          name: beanInvoke
          component: testComponent:test4
          desc: 单节点执行
          input: com.service.flow.sample.common.model.TestInput
          type: bean
      - node:
          id: node5
          name: beanInvoke
          component: testComponent:test5
          desc: 单节点执行
          input: com.service.flow.sample.common.model.TestInput
          type: bean

    流程加载

    首先我们要确定流程的几个来源有可能是定义的yml、properties、xml、json或者接口;那我我们设计就要支持流程模式可扩展,不管通过什么方式初始化模型,只要能够转换成流程定义,不管什么方式都是可以,完全用到可扩展的需求;

    当然我们也会提供默认的流程加载方式,我们这里以yml格式举例,首先我们需要定义一个yml流程文件,示例如下,新增一个test.flow.yml文件,为了考虑文件路径自定义,也需要把扫描包做成可配置的功能,模式是classpath:flow/*.flow.yml;

    平台首先需要定义定一个流程注册接口,所用加载流程模型用于实现流程注册,在通过平台的机制把流程模型加载到流程管理器,具体实现类我们需要对定义的yml流程就行解析,当然规范要严格按照yml的格式定义;在加载的过程中要有异常处理机制,比如定义的节点和参数不存在,重复定义流程;每一个模型的异常处理完全由平台统一管控。

    考虑到编写流程文件的便利性,需要提供一种机制需要把所有的DTO和基础组件在应用初始化的过程中加载到组件模型,在定义流程文件的时候不需要定义全路径,而且通过注册的bean映射到具体的实现组件,这样的编排文件就变的更加简单了。

    流程解析

    我们把节点分为方法节点、bean节点、服务节点、条件节点、循环节点、子流程节点,我们来讲一下主要的设计;

    第一步:拿到流程ID,在定义的流程上下文获取流程模型,如果获取不到对应的流程,则抛出对应的异常;

    第二步:处理主流程,处理主流程我们需要执行过程就行计时处理,通过解析请求参数和输出参数,上下文并实例化成对象;加载所有的流程节点,并开始查到第一个定义的流程节点;

    第三步:节点解析,根据定义的type类型,我们可以知道需要那种节点解析器去做处理;

    第四步:节点处理,方法节点很简单通过反射机制调用基础组件;bean节点比方法节点多了一次bean到类的映射,也是通过反射执行组件;条件节点通过EL表达式进行匹配,可配置多个条件,既满足if else场景,也需要满足when case场景;服务节点需要我们自定义实现sdk定义一个轻量级框架,不依赖任务容器,需要在starter模块实现等;

    第五步:流程节点执行完成之后会获取下一个节点,然后通过递归的形式调用,有多少节点就递归调用多少次,直到没有下一个节点标识;

    第六步:统计执行情况,包括耗时,响应参数;

    第七步:异常处理机制,全局可自定义异常处理机制,当流程发生异常时回滚操作;

    集成使用

    我们参考Spring框架,低侵入松耦合设计思想。编排框架也应该满足这个设计思想。因为框架是需要集成到应用中使用的,我们希望框架尽可能低侵入,与业务代码松耦合,替换、删除起来也更容易些。我们基于Spring框架做集成,并提供starter依赖组件。

    展开全文
  • 篇文章了解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一个流程或者说是大概,很多细节我也没细说,不过我相信大流程搞明白之后,一些小细节上的东西自己在慢慢研究也是没问题的。

    展开全文
  • Activiti是个项目的名称,Alfresco软件在2010年5月17日宣布Activiti业务流程管理(BPM)开源项目的正式启动,其首席架构师由业务流程管理BPM的专家 Tom Baeyens担任。 Activiti项目是项新的基于Apache许可的...

    工作流

    Activiti是一个项目的名称,Alfresco软件在2010年5月17日宣布Activiti业务流程管理(BPM)开源项目的正式启动,其首席架构师由业务流程管理BPM的专家 Tom Baeyens担任。

    Activiti项目是一项新的基于Apache许可的开源BPM平台,从基础开始构建,旨在提供支持新的BPMN 2.0标准,包括支持对象管理组(OMG),面对新技术的机遇,诸如互操作性和云架构,提供技术实现。

    创始人Tom Baeyens是JBoss jBPM的项目架构师,以及另一位架构师Joram Barrez,一起加入到创建Alfresco这项首次实现Apache开源许可的BPMN 2.0引擎开发中来。

     

    Activiti是一个独立运作和经营的开源项目品牌,并将独立于Alfresco开源ECM系统运行。 Activiti将是一种轻量级,可嵌入的BPM引擎,而且还设计适用于可扩展的云架构。 Activiti将提供宽松的Apache许可2.0,以便这个项目可以广泛被使用,同时促进Activiti BPM引擎和的BPMN 2.0的匹配,该项目现正由OMG通过标准审定。

     

    Activiti官方主页:http://www.activiti.org/index.html

    下载:http://www.activiti.org/download.html

    用户指南:http://activiti.org/userguide/index.html (用户指南来学习Activiti)

    在线API文档: http://activiti.org/javadocs/index.html (开发的时候参阅)

     

    其实简单的来说工作流就是将需要完成某个流程的各个任务组合起来,实现自动化的去完成这些任务,优点就是它实现了工作流程的自动化并且大大提高企业的运作效率;

    它具体的实现过程就是:

    当然最先应该做的就是创建项目导入依赖:

    <!--添加Activiti工作流的支持 一般需要exclusions -->
    <dependency>
      <groupId>org.activiti</groupId>
      <artifactId>activiti-engine</artifactId>
      <version>5.19.0.2</version>
    </dependency>
    <!--添加Activiti工作流对Spring的支持-->
    <dependency>
      <groupId>org.activiti</groupId>
      <artifactId>activiti-spring</artifactId>
      <version>5.19.0.2</version>
    </dependency>
    <dependency>
      <groupId>org.activiti</groupId>
      <artifactId>activiti-bpmn-model</artifactId>
      <version>5.19.0.2</version>
    </dependency>

    <!-- mysql驱动:根据数据库的版本选择驱动版本 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.34</version>
    </dependency>

    1.使用插件画工作图

     

    Idea当中加入插件之后,画如下的流程图:

    整个图的信息:

     

     

    请假节点的信息:

    Name是这个任务的名称“:

    Assignee是执行这个任务的人,此处的意思就是项目经理要请假,其实项目经理这个地方是个变量,设置成为变量的意思就是任何人都可以,而不是只是在这儿设置的项目经理了,当然为了方便理解我们就这样做了;

     

    然后就是审批节点的信息:

    任务是审批,审批的人是总监

     

    然后我们将我们创建的这个文件leave.bpmn赋值一份 然后将其后缀给为xml

    然后我们可以看到配置信息:

    除了头文件以及小编下面截取出来的,其余的都是坐标信息:
    首先是流程节点;
    <process id="myProcess_1" isClosed="false" isExecutable="true" processType="None">
    开始节点
    
      <startEvent id="_2" name="StartEvent"/>
    结束节点
    
      <endEvent id="_3" name="EndEvent"/>
    第一个请假节点且表面请假的人是项目经理
    
      <userTask activiti:assignee="项目经理" activiti:exclusive="true" id="_4" name="请假"/>
    第二个审批节点批准的认时总监
    
      <userTask activiti:assignee="总监" activiti:exclusive="true" id="_5" name="审批"/>
    下面三个就是之前画的流程图中的三条线:2~4、4~6、5~3
    这是于你生成节点的顺序有关的,最先拉经来的胖子在前面这里:
    2是开始
    3是结束
    4是请假
    5是审批
    
      <sequenceFlow id="_6" sourceRef="_2" targetRef="_4"/>
    
      <sequenceFlow id="_7" sourceRef="_4" targetRef="_5"/>
    
      <sequenceFlow id="_8" sourceRef="_5" targetRef="_3"/>
    
    </process>

    以上就是新建的这个bmp文件的xml表示;

     

    2.创建数据库、执行方法

    接下来就测是第一种的到流程引擎的方式

    代码如下:

    @Test
    
    public void shouldAnswerWithTrue()
    
    {
    
        //得到流程引擎的方式一
    
        ProcessEngineConfiguration pec =    ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
    下面是设置数据源
    
        pec.setJdbcUrl("jdbc:mysql://localhost:3306/db_activiti?characterEncoding=utf-8");
    
        pec.setJdbcDriver("com.mysql.jdbc.Driver");
    
        pec.setJdbcUsername("root");
    
        pec.setJdbcPassword("root2");
    设置自动生成表的属性是true, DB_SCHEMA_UPDATE_TRUE它的值就是true
    
        pec.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
    
        ProcessEngine processEngine = pec.buildProcessEngine();

    }

    在执行之前还需要解决一个问题:

    乱码问题:

    Settings—>Editor—>File Encodings utf-8

    idea安装目录bin目录:idea.exe.vmoptions;idea64.exe.vmoptions,在文件末尾添加 -Dfile.encoding=UTF-8 ,然后重启idea

    然后执行这个方法:

    执行的结果:

    然后我们去检查数据库就可以发现它自动生成的表:

     

    5种数据库表说明:

    Activiti的后台是有数据库的支持,所有的表都以ACT_开头。 第二部分是表示表的用途的两个字母标识。用途也和服务的API对应。

    1. ACT_RE_*: 'RE'表示repository。 这个前缀的表包含了流程定义和流程静态资源 (图片,规则,等等)。
    2. ACT_RU_*: 'RU'表示runtime。 这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Activiti只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。
    3. ACT_ID_*: 'ID'表示identity。 这些表包含身份信息,比如用户,组等等。
    4. ACT_HI_*: 'HI'表示history。 这些表包含历史数据,比如历史流程实例, 变量,任务等等。
    5. ACT_GE_*: 'GE'表示general。通用数据, 用于不同场景下,如存放资源文件。

    第二种获取流程引擎的方法

    创建核心配置文件,但是配置文件的名字必须是activiti.cfg.xml原因下面会提到:然后使用加载配置文件的方式产生流程引擎,当然我们的表已经生成,此时就不再执行方法,附上代码:

    //得到流程引擎的方式二,加载配置文件
    ProcessEngineConfiguration pec2 = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
    ProcessEngine processEngine1 = pec2.buildProcessEngine();

    第三种获取流程引擎的方法

    //得到流程引擎的方式三,利用底层封装,来加载配置文件,只需要调用方法即可
    ProcessEngine pec3 = ProcessEngines.getDefaultProcessEngine();

    原因就是源码当中有一个init的方法,里面他会自动去加载配置文件:

    这也就是为什么配置文件的名字是固定,不可变的了,因为默认加载的就是这个配置文件,如果你换了配置文件的名字,会报异常

     

     

    以下附上配置文件的代码:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
          
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          
    xsi:schemaLocation="http://www.springframework.org/schema/beans  
          http://www.springframework.org/schema/beans/spring-beans.xsd"
    >

      <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration">

    以下是数据源的配置

       <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db_activiti?characterEncoding=utf-8"></property>
            <property name="jdbcDriver" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUsername" value="root"></property>
            <property name="jdbcPassword" value="root2"></property>

    以下是自动生成表二设置,和第一种方法的直接设置是一样的
            <property name="databaseSchemaUpdate"    value="true"></property>
        </bean>
     
    </beans>

    3.部署

    在创建完成之后就是部署

    部署流程图就是要将这个流程图部署到流程框架当中去,并且将数据添加到数据库,这个步骤只需要一次即可,因为是可以部署的任务是可以重复使用的,上面执行方法船舰表也是一样,只需要一次即可;

    因为有了配置文件,下面的测试方法当中我们使用的就是第三种方法:

    以下就是我们的部署代码:

    部署我们首先应该得到的就是部署的服务对象

    @Test
    public  void  deploy(){
        ProcessEngine pec=ProcessEngines.getDefaultProcessEngine();

    部署的服务对象
        RepositoryService repositoryService = pec.getRepositoryService();

    部署请假任务:
        Deployment deploy = repositoryService.createDeployment()
                .addClasspathResource("leave.bpmn")
                .name("请假")
                .deploy();

    审批任务会再请假之后自动开启
        System.out.println("请假部署ID"+deploy.getId());
       
    }

    因为是两个任务我们分别部署,在部署完毕之后的输出是:

    然后我们在去看数据库:

    在定义表中(act_re_procdef):

    我们可以很清楚的看到我们定义的任务,它们的key是myProcess_1,也就是我们之前画图时,整个图的ID,可以会看;

    在部署表(act_re_deployment)当中:

    我们可以很清楚的看到我们部署的两个任务请假以及审批

    之后我们去看资源表(act_ge_bytearray):

    我们可以看到我们传到数据库的的资源包括生成的png的图片都在这里,其实生成了以后我们就可以删除我们在项目当中创建的pnm文件了,因为文件以及保存到了数据库所以那些也就没有必要了;

    4.开始任务

    首先我们也必须根据流程引擎对象,来得到我们的运行时服务对象,然后根据我们定义表当中的key来得到流程实例,代码如下:

    @Test
    public void start(){

    根据流程引擎对象得到运行时服务对象:
        ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
        RuntimeService runtimeService = defaultProcessEngine.getRuntimeService();

    根据key得到流程实例
        ProcessInstance myProcess_1 = runtimeService.startProcessInstanceByKey("myProcess_1");
    输出运行时流程实例id以及我们启动的流程它的一个定义id  

     System.out.println("流程实例id"+myProcess_1.getId());
        System.out.println("流程定义ID:"+myProcess_1.getProcessDefinitionId());
    }

    运行之后结果如下:

    流程定义ID我们知道,但是运行时流程id在哪里呢?

    我们来看我们的运行时任务表:

    我们在设立就可以看见我们刚开始的一个任务,就是请假,它的EXECUTION_ID_是2501,而这条任务的id是2504,然后执行的任务name是请假,请假的人是ASSIGNEE_项目经理

    在这里有些读者肯会有疑惑,就是我们部署了两个任务,

    为什么启动了之后就只有请假这一个任务了呢?

    解释一下:原因就是因为Activiti是流程框架,它的节点有特殊性,也就是一个节点的启动是以上一个节点的结束而被触发的,所以在这里当请假这个任务没有完成的时候,审批这个任务是不会出现的,只有当你的请假任务完成了之后,审批的任务就会出现在你运行时的任务表当中,这时你只需要去完成审批的任务即可;

    注意:之前说个我们这里只是以项目经理为例,当这里这里编程变量的时候这里可能会同时出现多个请假的任务,就是多线程,多个请假任务同时进行;

    4.完成任务

    同样完成任务我们首先就要得到我们用来完成任务的服务类,我们是根据运行时任务表当中的id来完成的,具体代码如下:

    @Test
    public void complete(){
        ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = defaultProcessEngine.getTaskService();

    根据ID完成任务
        taskService.complete("2504");
    }

    完成之后我们去刷新我们的任务表:

    这时候出现的就是我们的审批任务了,由于请假任务的完成审批任务就自动被触发了,所以我们不需要再去启动审批任务,而是由框架自动初发;

    在去看我们的历史记录表(act_hi_actinst)

    由下面的一个图可以看出我们的开始节点在你开始这个任务的时候是跟随开启的任务是一起进来的时间是一样的,而它们的结束时间是不一样的,请假的结束时间就是我们完成请假任务的时间

    而我们完成请假任务的时间恰恰是审批任务的开始时间,而在我们没有完成审批任务之前我们的审批任务是没有结束时间的,因为这个时候它还是待处理此状态;

    此时我们在去完成处于运行时任务表当中的审批任务它的id时5002,代码如下:

    @Test
    public void complete2(){
        ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = defaultProcessEngine.getTaskService();
        taskService.complete("5002");
    }

    同样我们在去刷新我们的运行时任务表:

    空空如也,所以说我们的审批任务也完成了;

    在这里我们就会发现一个流程框架的机制:

    那就是:当一个任务被开始的时候,它会出现在我们的运行时任务当中,当这个任务完成了,这个任务的记录就会出现在我们的历史记录表当中,而这时运行时任务表时空的,简单的说,其时运行时的几张表充当的就是相当于电脑内存的作用,做个类比它们就是流程框架的内存;

    这时候我们在回过头去看历史记录表:

    此时审批任务也已经完成,而他完成的时间,也正好就是我们结束节点开始的时间,而结束节点可开始节点一样,它们的开和结束时间是一样的,因为它们一旦开始就不会由等待;

    以上就是我们整个流程框架的一个过程,当然我们这里只是以单线程方式来演示的,实际应用当中,往往是多个任务同时进行的,而流程图也比这个复杂的多,所以it之路长漫漫,唯有的是努力+坚持!!!

    版权声明:本文为博主原创文章,未经博主允许不得转载.https://blog.csdn.net/qq_421128

    展开全文
  • 首先说明一下,本系列《SSM项目搭建系列》是为了复习并且完整的熟悉一下SSM框架搭建项目的流程。本系列博客会参照Nimiky...Spring框架个轻量的J2EE框架,同时Spring 是最受欢迎的企业 Java 应用程序开发框架,核
  • 直播概念和流程框架

    万次阅读 2016-08-27 22:46:12
    、了解直播 热门直播产品 映客,斗鱼,熊猫,虎牙,花椒等等 1.个完整直播app功能(来自落影loyinglin分享) 1、聊天 私聊、聊天室、点亮、推送、黑名单等; 2、礼物 普通礼物、豪华礼物、红包、排行榜、...
  • APQC通用版流程分类框架PCF,管理...要么适用于各行各业,要么面向某些行业,如面向所有行业的APQC流程框架与清单,面向电信运营企业的ETOM流程框架与清单;还有面向某块业务域的流程架构,如SCOR,ITLL,CMMI等。
  • 、了解直播 热门直播产品 映客,斗鱼,熊猫,虎牙,花椒等等 1.个完整直播app功能(来自落影loyinglin分享) 1、聊天 私聊、聊天室、点亮、推送、黑名单等;2、礼物 普通礼物、豪华礼物、红包、排行榜、第三...
  • Hibernate是个开放源代码的对象关系映射(ORM,Object Rational Mapping)框架,它对JDBC进行了非常轻量的对象封装,它将POJO与数据库表建立映射关系,是个全自动的orm框架,hibernate可以自动生成SQL语句,...
  • ITester框架简介上篇的blog主要讲解了接口测试的基础知识,这章将详细介绍接口测试框架图和流程图。ITester是处理API接口测试的轻量自动化测试框架,java语言实现。主要是面向service接口,诸如:HTTP,SOAP,...
  • 快速开发个 Web 框架

    千次阅读 2018-07-28 23:30:01
    Node.js 的框架有很多,常用的几个也有各种各样的难处.怎么使用 Node.js 开发个属于自己的 Web 框架呢?下面我将带领大家在 Koa2 的基础上开发款初步具备 Web ...添砖加瓦,构成个轻量级框架。 后续的开发。 ...
  • SSM框架的原理和运行流程

    万次阅读 多人点赞 2019-06-30 10:27:05
    SSM框架的工作原理及运行流程SSM框架简介springMVCspringmybatis SSM框架简介 SSM(Spring+SpringMVC+MyBatis)框架集由Spring、MyBatis两个开源框架整合而成(SpringMVC是Spring中的部分内容)。SSM常作为数据源...
  • SSM框架流程及优点

    千次阅读 2020-03-19 03:00:22
    SSM框架 SSM(Spring+SpringMVC+MyBatis)框架集由Spring、MyBatis两个开源框架整合而成(SpringMVC是Spring中的部分内容)。在这个快速发展的互联经济的时代,SSM框架提高了开发人员的工作效率,及时的开发出具有...
  • 设计和实现款轻量的爬虫框架

    千次阅读 2018-01-17 09:44:50
    设计和实现款轻量的爬虫框架 说起爬虫,大家能够想起 Python 里赫赫有名的 Scrapy 框架, 在本文中我们参考这个设计思想使用 Java 语言来实现款自己的爬虫框(lun)架(zi)。 我们从起点一步一步分析...
  • 四丶使用python3开发个轻量的Web框架 五丶在框架中实现添加股票功能 六丶在框架中实现删除股票功能 七丶在框架中实现修改股票功能 丶项目说明 1.实现过程 用户通过浏览器向Web服务器请求资源,Web服...
  • 本文同步发表于我的微信公众号,扫扫文章底部的二维码或在微信搜索 郭霖 即可关注,...现在Android上的图片加载框架非常成熟,从最早的老牌图片加载框架UniversalImageLoader,到后来Google推出的Volley,再到后
  • SpringMVC-轻量Web框架

    千次阅读 2016-10-04 16:27:20
    SpringMVC架构 SpringMVC框架个基于请求驱动的Web框架,并且使用了前段控制器模式来进行设计,再根据请求映射规则分发给相应的页面控制器(动作/处理器)进行处理
  • 理解 RN 框架的一些东西,以便裁剪和对 RN 有个更深入的认识,所以本篇总结了我这段时间阅读源码的一些感触,主要总结了 React Native 启动流程、JS 调用 Java 流程、Java 调用 JS 流程
  • 后端框架的学习流程

    千次阅读 2017-01-01 13:43:12
    有不少初学者问到,我想学习后端,但是又不知道该怎么学,所以我决定把这几年的经验和经历整理成篇文章,分析后端的路该怎么走,先说明下面仅仅是个人心得,也许与外面的理论有所不同。(文章最后面会附上学习路线...
  • 开源个功能完整的SpringBoot项目框架

    万次阅读 多人点赞 2019-10-11 14:14:39
    最近想了解一下有关Spring Boot的开源项目,看了很多开源的框架,大多是一些demo或者是个未成形的项目,基本功能都不完整,尤其是用户权限和菜单方面几乎没有完整的。 想到我之前做的框架,里面通用模块有:用户...
  • 这时,坐在旁边位同事这样夸赞你:“你真是位能把三年的工作经验用来当五年干活的人,而且还真正拥有了五年的工作经验!”。作为名半路出家的Java从业者,面对这样的褒奖时内心是十分沉重的,它意味着曾经自己...
  • 是 Spring 框架部分,是在 Spring3.0 后发布的。 SpringMVC 优点 基于 MVC 架构 基于 MVC 架构,功能分工明确。解耦合, 容易理解,上手快;使用简单。 就可以开发个注解的 SpringMVC 项目,SpringMVC 也是轻...
  • Delta - 轻量JavaWeb框架使用文档

    千次阅读 2015-12-24 16:58:40
    Delta 是个基于MVC架构的轻量WEB开发框架,基于jdk1.8开发,目前最新版本更新为 delta_1.1_beta,项目完全开源,并提供包装后的jar包方便用户快速开发。
  • Quartz,水晶、石英,个简单朴素有美丽的名字,在Java程序界,Quartz大名鼎鼎,很多Java应用几乎都集成或构建了个定时任务调度系统,Quartz是个定时任务调度框架。 何为定时任务调度框架?简而言之,它可以...
  • SSH三大框架的工作原理及流程

    千次阅读 2018-09-26 15:47:14
    SSH三大框架的工作原理及流程 2012年03月08日 23:35:28 chenyi0834 阅读数:278610 标签: 框架工作sshspringhibernatestruts 更多 个人分类: sshm   Hibernate工作原理及为什么要用? 原理: 1.通过...
  • 2019 Python接口自动化测试框架实战开发(

    万次阅读 多人点赞 2019-06-28 15:55:25
    说明:该篇博客是博主一字码编写的,实属不易,请尊重原创,谢谢大家! 目录 丶叙述 二丶接口基础知识 三丶接口测试工具 四丶Fiddler的使用 五丶unittest使用 六丶mock服务入门到实战 七丶接口自动化...
  • 怎样从0开始搭建个测试框架_0——总章

    万次阅读 多人点赞 2017-08-02 10:46:01
    此系列教程能让你从个刚刚学会Python与Selenium,不知道下一步该怎么走的小白,到自己搭建测试框架,并在项目中应用哦。
  • Android应用开发编译框架流程与IDE及Gradle概要

    万次阅读 多人点赞 2015-11-07 19:24:43
    1 背景建议阅读本文之前先...本文的核心就是下图:关于Gradle的Android插件本文不会过多的说明,只给个抛砖引玉的提示,详细使用参见文档API及Gradle配置,其实个性化的构建配置一般都是Gradle与Groovy的编写,与Andr

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 243,293
精华内容 97,317
热门标签
关键字:

一级流程框架