Activiti工作流引擎中责任链模式的建立与应用原理

工作流引擎Activiti详解 工作流引擎Activiti1. 什么是工作流1.1 工作流介绍1.2 工作流系统1.3 工作流实现方式1.4 工作流实现原理2. Activiti7概述2.1 Activiti介绍2.2 Activiti使用3. Activiti环境配置3.1 创建数据库3.2 初始化数据库表:3.3 创建数据库表3.4 数据库表命名规则4. Activiti架构简介5. Activiti入门案例5.1 流程定义5.2 部署流程5.3 启动流程5.4 流程定义查询5.5 流程定义删除5.6 流程定义资源查询 1. 什么是工 阅读详情

本文需要一定责任链模式的基础与Activiti工作流知识,主要分成三部分讲解:

一、简单理解责任链模式概念

网上关于责任链模式的介绍很多,菜鸟教程上是这样说的:责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

这个概念术语比较抽象。

过滤器链就像一条铁链,中间的每个过滤器都包含对另一个过滤器的引用,从而把相关的过滤器链接起来,就像一条链的样子。这时请求线程如蚂蚁一样,会沿着这条链一直爬过去-----即,通过各过滤器调用另一个过滤器引用方法chain.doFilter(request, response),实现一层嵌套一层地将请求传递下去,当该请求传递到能被处理的过滤器时,就会被处理,处理完成后转发返回。通过过滤器链,可实现在不同的过滤器当中对请求request做拦截增加,且过滤器之间彼此互不干扰。

整个流程大致如下:

这个过滤器链的概念,其实就是责任链设计模式在Spring Security中的体现。

摘录一段网上关于职责链模式介绍,其主要包含以下角色:

  1. 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。

  2. 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。

  3. 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

二、Activiti工作流里责任链模式的创建

最近在研究Activiti工作流框架,发现其所有实现都是采用命令模式实现,而命令模式当中的Invoker角色又是采用拦截器链式模式,即类似上面提到的过滤器链,即设计模式里的责任链模式。

这里的Activiti工作流版本是6.0。

CommandInterceptor是一个拦截器接口,包含三个方法:

  • setNext()方法是在初始化时,设置每个拦截器对象中包含了下一个拦截器对象,最后形成一条拦截器链;
  • getNext()可在每个拦截器对象中调用下一个拦截器对象;
  • execute()是每个拦截器对请求的处理。若在上一个拦截器链式里不能处理该请求话,就会通过next.execute(CommandConfig var1, Command var2)将请求传递到下一个拦截器做处理,类似上面过滤器里调用下一个过滤器的chain.doFilter(request, response)方法,将请求进行传递;
public interface CommandInterceptor {
    <T> T execute(CommandConfig var1, Command<T> var2);

    CommandInterceptor getNext();

    void setNext(CommandInterceptor var1);
}

抽象类AbstractCommandInterceptor实现了CommandInterceptor拦截器接口,在责任链模式当中充当抽象处理者(Handler)角色。 该类最主要的属性是 protected CommandInterceptor next,在同一包下,直接通过next即可调用下一个拦截器对象。

public abstract class AbstractCommandInterceptor implements CommandInterceptor {
    protected CommandInterceptor next;
    public AbstractCommandInterceptor() {
    }
    public CommandInterceptor getNext() {
        return this.next;
    }
    public void setNext(CommandInterceptor next) {
        this.next = next;
    }
}

接下来,将会分析拦截器链是如何初始化与工作的。

SpringBoot集成Activiti配置如下:

  @Configuration
  public class SpringBootActivitiConfig {
      @Bean
      public ProcessEngine processEngine() {
          ProcessEngineConfiguration pro = ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
          pro.setJdbcDriver("com.mysql.jdbc.Driver");
          pro.setJdbcUrl("xxxx");
          pro.setJdbcUsername("xxxx");
          pro.setJdbcPassword("xxx");
          //避免发布的图片和xml中文出现乱码
          pro.setActivityFontName("宋体");
          pro.setLabelFontName("宋体");
          pro.setAnnotationFontName("宋体");
          //数据库更更新策略
          pro.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
          return pro.buildProcessEngine();
      }
  }

这时,启动项目后,pro.buildProcessEngine()这行代码会初始化Activiti框架,进入里面,会发现它有三种实现,默认是第二种,即ProcessEngineConfigurationImpl。

点进去,Activiti框架具体构建buildProcessEngine方法如下,其中 this.init()的作用是环境初始化,包括配置设置、JDBC连接、bean装载等的:

public ProcessEngine buildProcessEngine() {
    this.init();
    ProcessEngineImpl processEngine = new ProcessEngineImpl(this);
    if (this.isActiviti5CompatibilityEnabled && this.activiti5CompatibilityHandler != null) {
        Context.setProcessEngineConfiguration(processEngine.getProcessEngineConfiguration());
        this.activiti5CompatibilityHandler.getRawProcessEngine();
    }

    this.postProcessEngineInitialisation();
    return processEngine;
}

在this.init()方法里,涉及到责任链模式初始化的方法是this.initCommandExecutors(),里面详情如下:

public void initCommandExecutors() {
    this.initDefaultCommandConfig();
    this.initSchemaCommandConfig();
    //初始化命令调用器
    this.initCommandInvoker();
    //List存放进涉及到的拦截器
    this.initCommandInterceptors();
    //初始化命令执行器
    this.initCommandExecutor();
}

这里只需要关注最后三个方法——

  1. this.initCommandInvoker()

    initCommandInvoker()初始化构建了一个CommandInvoker拦截器,它继承上边提到的拦截器抽象类AbstractCommandInterceptor。这个拦截器在整条过滤器链中是最重要和关键,它排在了整条链的最后,其实,它才是最终执行请求的,前边几个拦截器都是传递请求而已。

    public void initCommandInvoker() {
        if (this.commandInvoker == null) {
            if (this.enableVerboseExecutionTreeLogging) {
                this.commandInvoker = new DebugCommandInvoker();
            } else {
                //初始化执行该行代码
                this.commandInvoker = new CommandInvoker();
            }
        }
    }
    
    

    这里 new CommandInvoker()一个对象,然后将地址复制给this.commandInvoker对象引用,注意,该引用将会用在接下来的initCommandInterceptors()方法里——

  2. this.initCommandInterceptors();

    initCommandInterceptors方法主要作用是创建一个List集合,然后将需要用到的拦截器都保存到该List集合里——

    public void initCommandInterceptors() {
        if (this.commandInterceptors == null) {
            this.commandInterceptors = new ArrayList();
            if (this.customPreCommandInterceptors != null) {
                //用户自定义前置拦截器
                this.commandInterceptors.addAll(this.customPreCommandInterceptors);
            }
            //框架自带默认的拦截器
            this.commandInterceptors.addAll(this.getDefaultCommandInterceptors());
            if (this.customPostCommandInterceptors != null) {
                this.commandInterceptors.addAll(this.customPostCommandInterceptors);
            }
            //命令调用器,在拦截器链最后一个
            this.commandInterceptors.add(this.commandInvoker);
        }
    }
    
    

    this.getDefaultCommandInterceptors()的代码如下:

    public Collection<? extends CommandInterceptor> getDefaultCommandInterceptors() {
        List<CommandInterceptor> interceptors = new ArrayList();
        //日志拦截器
        interceptors.add(new LogInterceptor());
        CommandInterceptor transactionInterceptor = this.createTransactionInterceptor();
        if (transactionInterceptor != null) {
            interceptors.add(transactionInterceptor);
        }
        //
        if (this.commandContextFactory != null) {
            interceptors.add(new CommandContextInterceptor(this.commandContextFactory, this));
        }
        //事务拦截器
        if (this.transactionContextFactory != null) {
            interceptors.add(new TransactionContextInterceptor(this.transactionContextFactory));
        }
    
        return interceptors;
    }
    
    

    可见,方法里的 this.commandInterceptors 就是一个专门储存拦截器对象的List集合——

    protected List<CommandInterceptor> commandInterceptors;
    
    

    这里只需要重点关注this.commandInterceptors.add(this.commandInvoker)这行代码,就是将上边创建的CommandInvoker拦截器对象存储到List里,它是放在initCommandInterceptors()方法最后,某种程度也就意味着,这个拦截器在整条链当中处在最后面的位置。

    执行完该this.initCommandInterceptors()方法后,就可获取到所有的拦截器对象,到这一步时,各拦截器还是互相独立的,仍无法通过next()来进行调用传递,那么,究竟是如何将它们串起来形成一条链呢?

    接下来的this.initCommandExecutor()方法,就是实现将各拦截器串起来形成一条长链。

  3. this.initCommandExecutor();

该方法有两个作用,一个是生成Interceptor拦截器链,一个是创建命令执行器commandExecutor。

public void initCommandExecutor() {
    if (this.commandExecutor == null) {
        CommandInterceptor first = this.initInterceptorChain(this.commandInterceptors);
        this.commandExecutor = new CommandExecutorImpl(this.getDefaultCommandConfig(), first);
    }
}

this.initInterceptorChain(this.commandInterceptors)是将集合里的拦截器初始化生成一条拦截器链,先循环获取List集合里的拦截器对象chain.get(i),然后通过setNext()方法在该拦截器对象chain.get(i)里设置下一个拦截器引用,这样,就可实现责任链里所谓每个接收者都包含对另一个接收者的引用的功能。

public CommandInterceptor initInterceptorChain(List<CommandInterceptor> chain) {
    if (chain != null && !chain.isEmpty()) {
        for(int i = 0; i < chain.size() - 1; ++i) {
            ((CommandInterceptor)chain.get(i)).setNext((CommandInterceptor)chain.get(i + 1));
        }
        return (CommandInterceptor)chain.get(0);
    } else {
        throw new ActivitiException("invalid command interceptor chain configuration: " + chain);
    }
}

那么,这条拦截器链当中,都有哪些拦截器呢?

直接debug到这里,可以看到,总共有4个拦截器对象,按照顺序排,包括LogInterceptor,CommandContextInterceptor,TransactionContextInterceptor,CommandInvoker(在命令模式里,该类相当Invoker角色)。这四个拦截器对象在责任链模式当中充当了具体处理者(Concrete Handler)角色。

责任链模式里剩余客户类(Client)角色应该是命令执行器this.commandExecutor。

因此,工作流引擎当中的责任链模式结构图如下:

组成一条拦截器链如下图所示——

生成拦截器链后,会返回一个(CommandInterceptor)chain.get(0),即拦截器LogInterceptor,为什么只返回第一个拦截器呢,这是一个很巧妙的地方,因为该拦截器里已经一层一层地嵌套进其他拦截器了,因此,只需要返回第一个拦截器,赋值给first即可。

接下来,就会创建命令执行器——

this.commandExecutor = new CommandExecutorImpl(this.getDefaultCommandConfig(), first);

这个命令执行器是整个引擎的底层灵魂,通过它,可以实现责任链模式与命令模式——

拦截器链初始化介绍完成后,接下来开始介绍拦截器链在引擎里的应用方式。

三、Activiti工作流里责任链模式的应用

Activiti引擎的各操作方法其底层基本都是以命令模式来实现的,即调用上面创建的命令执行器this.commandExecutor的execute方法来实现的,例如自动生成28张数据库表的方法,就是通过命令模式去做具体实现的——

this.commandExecutor.execute(processEngineConfiguration.getSchemaCommandConfig(), new SchemaOperationsProcessEngineBuild());

进入到commandExecutor方法里,会发现前边new CommandExecutorImpl(this.getDefaultCommandConfig(), first)建立命令执行器时,已将配置对象和嵌套其他拦截器的LogInterceptor拦截器对象,通过构造器CommandExecutorImpl(CommandConfig defaultConfig, CommandInterceptor first)生成对象时,传参赋值给了相应的对象属性,其中first引用指向LogInterceptor,即拦截器链上的第一个拦截器——

public class CommandExecutorImpl implements CommandExecutor {
    protected CommandConfig defaultConfig;
    protected CommandInterceptor first;

    public CommandExecutorImpl(CommandConfig defaultConfig, CommandInterceptor first) {
        this.defaultConfig = defaultConfig;
        this.first = first;
    }

    public CommandInterceptor getFirst() {
        return this.first;
    }

    public void setFirst(CommandInterceptor commandInterceptor) {
        this.first = commandInterceptor;
    }

    public CommandConfig getDefaultConfig() {
        return this.defaultConfig;
    }

    public <T> T execute(Command<T> command) {
        return this.execute(this.defaultConfig, command);
    }

    public <T> T execute(CommandConfig config, Command<T> command) {
        return this.first.execute(config, command);
    }
}

当引擎执行this.commandExecutor.execute(xxx,xxx))类似方法时,其实是执行了this.first.execute(config, command)方法,这里的this.first在构建命令执行器时是通过LogInterceptor传进来的,因此,执行代码其实是调用了LogInterceptor内部的execute()方法,也就是说,开始拦截器链上的第一个LogInterceptor拦截器传递方法execute()请求——

进入到拦截器链上的第一个拦截器LogInterceptor。

根据其内部代码可以看出,这是一个跟日志有关的拦截器,内部并没有多少增强功能,只是做了一个判断是否需要debug日志打印。若需要,则进行debug打印,若不需要,直接进入到 if (!log.isDebugEnabled()) 为true的作用域内部,进而执行this.next.execute(config, command)用以将请求传递给下一个拦截器做处理。

public class LogInterceptor extends AbstractCommandInterceptor {
    private static Logger log = LoggerFactory.getLogger(LogInterceptor.class);
    public LogInterceptor() {
    }
    public <T> T execute(CommandConfig config, Command<T> command) {
        if (!log.isDebugEnabled()) {
            return this.next.execute(config, command);
        } else {
            log.debug("\n");
            log.debug("--- starting {} --------------------------------------------------------", command.getClass().getSimpleName());
            Object var3;
            try {
                var3 = this.next.execute(config, command);
            } finally {
                log.debug("--- {} finished --------------------------------------------------------", command.getClass().getSimpleName());
                log.debug("\n");
            }

            return var3;
        }
    }
}

这里有一个小地方值得稍微打断说下,就这个 if (!log.isDebugEnabled())判断。众生周知,若集成第三方日志插件如logback之类,若其配置里去除debug的打印,即时代码里 存在log.debug("xxxxx")也不会打印到控制台,那么,这里增加一个判断 if (!log.isDebugEnabled())是否多次一举呢?

事实上,这里并非多此一举,增加这个判断,是可以提升代码执行效率的。因为log.debug("xxxxx")里的字符串拼接早于log.debug("xxxxx")方法执行的,也就是说,即使该log.debug("xxxxx")不会打印,但其内部的字符串仍然会进行拼接,而拼接,是需要时间的,虽然很细微,但同样属于影响性能范畴内的。因此,增加一个if判断,若无需要打印debug日志时,那么就无需让其内部的字符串进行自动拼接。

这是一个很小的知识点,但面试过程中其实是有可能会遇到这类与日志相关的面试题的。

接下来,让我们继续回到拦截器链的传递上来。

LogInterceptor拦截器调用this.next.execute(config, command),意味着将请求传递到下一个拦截器上进行处理,根据前边分析,可知下一个拦截器是CommandContextInterceptor,根据代码大概可知,这个拦截器内主要是获取上下文配置对象和信息相关的,这些都是在工作流引擎初始化时生成的,它们被保存在Stack栈里,具体都保存了哪些信息暂不展开分析——

public class CommandContextInterceptor extends AbstractCommandInterceptor {
    ......
public <T> T execute(CommandConfig config, Command<T> command) {
    CommandContext context = Context.getCommandContext();
    boolean contextReused = false;
    if (config.isContextReusePossible() && context != null && context.getException() == null) {
        contextReused = true;
        context.setReused(true);
    } else {
        context = this.commandContextFactory.createCommandContext(command);
    }

    try {
        Context.setCommandContext(context);
        Context.setProcessEngineConfiguration(this.processEngineConfiguration);
        if (this.processEngineConfiguration.getActiviti5CompatibilityHandler() != null) {
            Context.setActiviti5CompatibilityHandler(this.processEngineConfiguration.getActiviti5CompatibilityHandler());
        }
        //继续将命令请求传递到下一个拦截器
        Object var5 = this.next.execute(config, command);
        return var5;
    } catch (Exception var31) {
        context.exception(var31);
    } finally {
     ......
    }

    return null;
}
}

CommandContextInterceptor拦截器没有对命令请求做处理,它继续将请求传递到下一个拦截器TransactionContextInterceptor,根据名字就大概可以猜到,这个拦截器主要是增加与事务有关的功能——

public <T> T execute(CommandConfig config, Command<T> command) {
    CommandContext commandContext = Context.getCommandContext();
    boolean isReused = commandContext.isReused();
    Object var9;
    try {
        if (this.transactionContextFactory != null && !isReused) {
            TransactionContext transactionContext = this.transactionContextFactory.openTransactionContext(commandContext);
            Context.setTransactionContext(transactionContext);
            commandContext.addCloseListener(new TransactionCommandContextCloseListener(transactionContext));
        }
        var9 = this.next.execute(config, command);
    } finally {
    ......
    }
    return var9;
}

TransactionContextInterceptor拦截器同样没有对命令请求做处理,而是继续传递到下一个拦截器,也就是最后一个拦截器CommandInvoker,根据名字可以大概得知,这是一个与命令请求有关的拦截器,传递过来的请求将会在这个拦截器里处理——

public class CommandInvoker extends AbstractCommandInterceptor {
    ......
    public <T> T execute(CommandConfig config, final Command<T> command) {
        final CommandContext commandContext = Context.getCommandContext();
        commandContext.getAgenda().planOperation(new Runnable() {
            public void run() {
                commandContext.setResult(command.execute(commandContext));
            }
        });
        this.executeOperations(commandContext);
        if (commandContext.hasInvolvedExecutions()) {
            Context.getAgenda().planExecuteInactiveBehaviorsOperation();
            this.executeOperations(commandContext);
        }
        return commandContext.getResult();
    }
}

进入到其内部,可以发现,这里没有再继续调用this.next.execute(config, command)这样的请求进行传递,而是直接执行command.execute(commandContext),然后将返回值进行返回,其中,command是请求参数当中的第二个参数,让我们回过头看下该请求案例最开始的调用——

this.commandExecutor.execute(processEngineConfiguration.getSchemaCommandConfig(), new SchemaOperationsProcessEngineBuild());

这里的第二个参数是new SchemaOperationsProcessEngineBuild(),不妨进入到SchemaOperationsProcessEngineBuild类中,是吧,其内部同样有一个execute方法——

public final class SchemaOperationsProcessEngineBuild implements Command<Object> {
    public SchemaOperationsProcessEngineBuild() {
    }

    public Object execute(CommandContext commandContext) {
        DbSqlSession dbSqlSession = commandContext.getDbSqlSession();
        if (dbSqlSession != null) {
            dbSqlSession.performSchemaOperationsProcessEngineBuild();
        }

        return null;
    }
}

可见,CommandInvoker拦截器内部执行command.execute(commandContext),就相当于执行了new SchemaOperationsProcessEngineBuild().execute(commandContext),也就是——

 public Object execute(CommandContext commandContext) {
        DbSqlSession dbSqlSession = commandContext.getDbSqlSession();
        if (dbSqlSession != null) {
            dbSqlSession.performSchemaOperationsProcessEngineBuild();
        }
        return null;
    }

这是一种命令模式的实现。

本文主要是分析责任链模式在Activiti框架中的实践,故暂不展开分析框架中的其他设计模式,有兴趣的童鞋可以自行深入研究,在Activiti框架当中,其操作功能底层基本都是以命令模式来实现的。

至此,就大概分析完了责任链模式在Activiti框架的创建和应用,学习完这块内容,我对责任链模式有了更好理解,相对于看网上那些简单以小例子来介绍设计模式的方法,我更喜欢去深入框架当中学习其设计模式,这更能让我明白,这种设计模式在什么场景下适合应用,同时,能潜移默化地影响到我,让我在设计系统架构时,能明白各设计模式的落地场景具体都是怎样的。

【工作流】Activiti工作流引擎架构设计原理总结 Activiti是由Alfresco软件发布的业务流程管理(BPM)框架。它为新的BPMN 2.0标准提供了支持,包括支持对象管理组(OMG),面对新技术的机遇,诸如互操作性和云架构,提供技术实现。 到目前为止,Activiti共有三个版本体系: ... 阅读详情

相关推荐

项目实战第十五讲:使用职责链模式实现类目属性规则引擎(超级复杂)

商品中心规则引擎梳理优化需求使用了设计模式1、梳理规则引擎业务逻辑 2、优化规则,减小规则引擎执行的rt表1、商品属性表 表2、类目属性表 4.1.2、项目整体流程图 首先在商品发布时,从类目属性模块渲染发布页 然后商品在创建或编辑时,执行规则引擎,校验入参的合法性,同时也会修正入参数据 在商品查询时,对出参数据进行订正处理,透出符合要求的数据 商品审核时,对审核数据进行合法性校验 可以分为以下几个步骤–》组装用户信息–》校验拓展信息(分为精度校验和值域范围校验)–》使用了TreeRangeSet这个数

Jet_qi的博客 856

Activiti命令拦截器和设计模式

Activiti命令拦截器和设计模式 使用场景 每个对流程的操作都可以看成一个命令command,Activiti会对每个命令进行拦截处理,Activiti的命令拦截用到了命令模式和责任链模式, 命令模式的本质在于将命令进行封装,发出命令和执行命令分离; 职责链模式只需要将请求放入职责链上,其处理细节和传递都不需要考虑; 执行入口 commandExecutor是命令执行的入口。 在命令来临时,s...

jerryq的博客 806

别再死记硬背了!用一张时序图彻底搞懂Setup和Hold检查的本质

本文深入解析数字IC设计中的Setup和Hold时序检查原理,通过波形图直观展示数据在触发器间的传递过程。详细讲解Setup检查为何关注前一周期的数据,Hold检查为何关注同一周期的数据,并提供实际设计中的考量因素和修复策略,帮助工程师彻底理解时序检查的本质。

weixin_29927965的博客 365

Activiti源码 之Command与 CommandInterceptor

by yan 20220228 Activiti 命令拦截器 执行过程 Activiti每发执行一个Command时都会经过一个责任链模式的命令拦截器(采用命令模式实现的拦截器),执行一系列的CommandInterceptor 。 具体过程如下,以Activiti默认的id生成器为例子: Activiti默认id生成策略 默认采用的是org.activiti.engine.impl.db.DbIdGenerator 时序图 注:如果在高并发的场景下,可能一个线程读取一段block之后,还没有来得及upd

xyzroundo的专栏 1126

Ubuntu 24.04 下 NoMachine 极速安装与远程桌面配置全攻略

本文详细介绍了在Ubuntu 24.04系统下安装和配置NoMachine远程桌面的完整指南。从系统准备、分步安装到性能调优和常见问题解决,帮助用户快速实现低延迟的远程桌面访问。特别适合开发者和IT管理员在Linux环境下高效部署远程工作环境。

moon9的博客 946

Hypermesh到Abaqus CEL网格转换全攻略:解决欧拉体导入难题

本文详细解析了Hypermesh到Abaqus CEL网格转换的全流程,重点解决欧拉体导入难题。通过单元类型转换、INP文件编辑和多组件合并技术,实现流固耦合仿真的高效前处理。文章包含实用脚本和工程案例,帮助工程师突破ABAQUS CEL分析中的网格转换瓶颈。

weixin_30832143的博客 191

Activiti之命令拦截器

Activiti提供了命令拦截器的功能,外界对Activit流程中各个实例进行操作,实际可以被看作是对数据进行相应的操作,在此过程中,Activiti使用了设计模式中的命令模式, 每一个操作数据库的操作,均可被看做为一个命令,然后交由命令执行者去瓦城。除此之外,为了能让使用者可以对这些命令进行相应的拦截,Activiti还使用了设计模式中的 责任链模式,使用者可以在其中添加相应的拦截器。

长江七号的博客 3575

Ubuntu20.04系统Cuckoo sandbox所需环境搭建及安装(超详细图文)

请勿叫我大神,我只是互联网的搬运工~ 前言 本教程以Ubuntu20桌面版作为示范,服务器版同样适用,但可能稍微麻烦一点,服务器版本的话安装Xmanage套装使用可能会相对轻松一些,由于后面会使用到vbox的使用,对vbox命令行操作不熟悉的小伙伴用xmanage可能会相对轻松一点。 提示:以下是本篇文章正文内容,下面案例可供参考 安装ubuntu20桌面版 步骤1:转到https://ubuntu.com/download/desktop下载下载.iso。 步骤2:使用您选择的虚拟机监控程序(VMWa

ElsonHY的博客 1万+

Activiti工作流学习笔记(四)——工作流引擎中责任链模式的建立与应用原理

工作流引擎中责任链模式的建立与应用原理分析

朱季谦 922

Sigrity System SI SerialLink模式进行Pcie3协议仿真分析操作指导-pcie3_client_single_pre

Sigrity System SI SerialLink模式提供了10个协议合规性检查工具模板,用户可以将根据实际应用替换模板中的SPICE文件,然后进行协议仿真分析,同时软件还提供了目标结果的模板MASK以及该协议需要检查的项目,通过协议分析视为满足设计要求,下面介绍pcie3_client_single_pre仿真分析,具体操作如下。Random(Rj):随机抖动,该抖动用高斯概率分布来描述,通常是由热噪声和其它随机噪声引起的,默认为1.24%UI RMS(平均值),即和标准值的偏差为1.24%UI。

weixin_54787054的博客 598

基于 STM32 的智慧图书馆智能控制系统设计与实现

智慧图书馆智能控制系统集成环境调控、安全监控、借阅管理与信息推送功能,通过 STM32 主控芯片联动传感器、执行器及云平台,实现图书馆智能化管理。系统架构分为感知层(传感器)、控制层(STM32 主控)、执行层(继电器 / 显示屏)及云端层(数据交互),支持温度自动调节、灯光自控、火情安防监控、天气推送及 RFID 借还书管理。本系统通过 STM32 实现多传感器融合与设备控制,结合 RFID 与云平台技术,构建了集环境智能调控、安全监控、便捷借阅于一体的智慧图书馆解决方案。

weixin_42300449的博客 2205

Activiti工作流引擎

Activiti是一个工作流引擎, activiti可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言BPMN2.0进行定义,业务流程按照预先定义的流程进行执行,实现了系统的流程由activiti进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的健壮性,同时也减少了系统开发维护成本。.........

Hu_1991的博客 1万+

【WRF-GHG 教程第一期】概述(Overview)

概述(Overview)

WW、forever的博客 513

Activiti7工作流引擎

工作流引擎实现了一个规范,规范要求:流程管理和状态字段无关,始终都读取业务流程的下一个节点。当业务更新的时候我们只需要更新流程图就可以了。从而实现了业务流程改变,不需要修改代码例如:请假天数小于三天不需要经理审批,请假天数大于三天需要经历审批,那么这就会导致请假不同天数流程不一样。传统的基于状态值的方式,就需要改代码,而基于工作流引擎的方式就不需要修改代码。流程自动推进原理:就拿请假模型来说,工作流引擎会用一张表来记录当前处在的节点。

立志成为架构师 3296

UNet改进(24):注意力机制-从基础原理到高级融合策略

本文系统探讨了如何通过引入各类注意力机制增强UNet在医学图像分割中的性能。文章分为基础篇、进阶篇和高级篇三个层级:基础篇介绍了时序注意力、可变形注意力、分层注意力等8种基础机制;进阶篇提出了5种混合注意力设计;高级篇则探讨了全局+局部+多尺度等复杂组合机制。实验表明,这些注意力模块在心脏MRI、肺部感染等任务中能提升2-15%的分割精度。文章还提供了硬件适配建议和完整实现代码,为研究者提供了全面的技术参考。未来方向包括NAS优化、量子注意力等前沿探索,为UNet在医疗影像等领域的应用革新提供了理论指导。

2501_91516851的博客 765

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

Activity工作流引擎

该博客为尚硅谷课件,放在这主要为了复习用!!

qq_61544409的博客 2万+

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

Activiti7工作流引擎介绍

第1章 什么是工作流 1.1 工作流介绍 工作流(Workflow),就是通过计算机对业务流程自动化执行管理。它主要解决的是“使在多个参与者 之间按照某种预定义的规则自动进行传递文档、信息或任务的过程,从而实现某个预期的业务目标, 或者促使此目标的实现”。 1.2 工作流系统 一个软件系统中具有工作流的功能,我们把它称为工作流系统,一个系统中工作流的功能是什 么?就是对系统的业务流程进行自动化管理...

Marion的博客 1万+

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Activiti7工作流—从零到实战

Activity7、工作流实现方式、工作流实现原理、BPM、BPMN、部署 activity、IDEA配置activity、eclipse配置activity、Activiti Designer 流程设计器、Activiti 服务架构、流程定义、流程实例、流程变量、组任务、排他网关、并行网关

东风麦芽糖 3万+

工作流引擎 Activiti 入门详解

点击关注公众号,利用碎片时间学习1. 什么是工作流1.1 工作流介绍工作流(Workflow),就是通过计算机对业务流程自动化执行管理。它主要解决的是“使在多个参与者之间按照某种预定义的规则自动进行传递文档、信息或任务的过程,从而实现某个预期的业务目标,或者促使此目标的实现”。1.2 工作流系统什么是工作流系统具有工作流程功能的软件系统。用于更好的管理业务流程。适用行业,...

Java笔记虾 6228

flowable、activiti、camunda 三大工作流引擎技术选型指南

在选择Activiti、Camunda还是Flowable时,您应根据具体需求进行权衡。如果您注重流程的直观建模和易用性,同时希望有丰富的社区资源和插件库支持,那么Camunda可能是一个不错的选择。如果您需要高性能和可扩展的解决方案,并且对商业支持和服务有较高要求,那么Camunda的商业版或Flowable可能是更适合您的选项。而如果您正在寻找一个轻量级且易于集成的工作流引擎,并且愿意接受一定程度的配置和部署复杂性,那么Activiti仍然是一个值得考虑的选项之一。

码到三十五 14万+

十四、工作流引擎——Activiti7

工作流Activiti7

kejizhentan的博客 2761

Activiti工作流引擎原理与使用详解

摘要: Activity工作流是以"活动"为核心的流程建模技术,用于自动化业务流程(如审批、数据处理)。其核心包括活动节点、顺序/并行流程、条件分支等机制,通过引擎驱动执行并管理状态。典型应用场景涵盖审批流、RPA、ETL等,支持可视化设计但需权衡调试复杂度。数据库表结构分为定义(ACT_RE_)、运行时(ACT_RU_)、历史(ACT_HI_*)等模块,开发流程包含设计、部署、任务处理及历史追踪。优势在于解耦业务逻辑,但需注意引擎开销和流程复杂度管理。

onlymscn的博客 1152

Activiti7 工作流引擎入门

不是每个人都能进入一线互联网大厂 BATJ普通人,省城,软件公司,银行、政府做出差报销流程、请假流程快速上手工作流(salvio修改)欢迎支持工作流(Workflow),就是通过计算机对业务流程自动化执行管理。它主要解决的是“使在多个参与者之间按照某种预定义的规则自动进行传递文档、信息或任务的过程,从而实现某个预期的业务目标,或者促使此目标的实现”。企业OA:请假----》内网系统----》小李填写请假单7天----》组长同意----》CTO同意------》人事部部长同意-----》结束专门的公司做OA

billzhen的博客 8143

SaaS-IHRM 项目-Activiti7基础原理与使用 工作流引擎 工作流使用

第1章 什么是工作流 1.1工作流介绍 工作流(Workflow),就是通过计算机对业务流程自动化执行管理。它主要解决的是“使在多个参与者 之间按照某种预定义的规则自动进行传递文档、信息或任务的过程,从而实现某个预期的业务目标, 或者促使此目标的实现”。 1.2工作流系统 一个软件系统中具有工作流的功能,我们把它称为工作流系统,一个系统中工作流的功能是什 么?就是对系统的业务流程进行自动化管理,...

kai46385076的专栏 6128
上一篇: SpringBoot之单元测试JUnit5
下一篇: 阿里P8大数据专家,带你深入理解Apache Kafka与实战
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值