本文节选自《疯狂工作流讲义(第2版)》
京东购买地址:https://item.jd.com/12246565.html
疯狂Activiti电子书:https://my.oschina.net/JavaLaw/blog/1570397
工作流Activiti教学视频:https://my.oschina.net/JavaLaw/blog/1577577
4 配置文件读取与数据源配置
4.1流程引擎配置对象
ProcessEngineConfiguration对象代表一个Activiti流程引擎的全部配置,该类提供一系列创建ProcessEngineConfiguration实例的静态方法,这些方法用于读取和解析相应的配置文件,并返回ProcessEngineConfiguration的实例。除这些静态方法外,该类为其他可配置的引擎属性提供相应的setter和getter方法。本小节主要讲解如何使用这些静态方法创建ProcessEngineConfiguration实例。
4.1.1 读取默认的配置文件
ProcessEngineConfiguration的createProcessEngineConfigurationFromResourceDefault方法,使用Activiti默认的方式创建ProcessEngineConfiguration的实例。这里所说的默认方式,是指由Activiti决定读取配置文件的位置、文件的名称和配置bean的名称这些信息。Activiti默认到ClassPath下读取名为“activiti.cfg.xml”的Activiti配置文件,启动并获取名称为“processEngineConfiguration”的bean的实例。解析XML与创建该bean实例的过程,由Spring代为完成。
使用过Spring的朋友可以知道,只需要指定Spring的XML配置文件,创建相应的BeanFactory实例,再通过getBean(bean名称)方法即可获取相应对象的实例,ProcessEngineConfiguration使用Spring框架的DefaultListableBeanFactory作为BeanFactory。
代码清单4-1使用createProcessEngineConfigurationFromResourceDefault方法创建ProcessEngineConfiguration实例。
代码清单4-1:codes\04\4.1\create-default\src\org\crazyit\activiti\CreateDefault.java
//使用Activiti默认的方式创建ProcessEngineConfiguration
ProcessEngineConfiguration config = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResourceDefault();
代码清单4-1中,Activiti默认到ClassPath下读取activiti.cfg.xml文件,如果找不到该配置文件则抛出FileNotFoundException,如果找不到名称为processEngineConfiguration的bean,则抛出org.springframework.beans.factory.NoSuchBeanDefinitionException,本例中的activiti.cfg.xml内容如代码清单4-2所示。
代码清单4-2:codes\04\4.1\create-default\resource\activiti.cfg.xml
<!-- 只配置相应的数据库属性 -->
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/act" />
<property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
<property name="jdbcUsername" value="root" />
<property name="jdbcPassword" value="123456" />
</bean>
此处需要注意的是,代码清单4-2中所使用的ProcessEngineConfiguration为StandaloneProcessEngineConfiguration类,ProcessEngineConfiguration为抽象类,不能直接作为bean的class进行配置,ProcessEngineConfiguration的子类将在下面章节进行描述。
4.1.2 读取自定义的配置文件
在4.1.1章节中可知,默认情况下Activiti将到ClassPath下读取activiti.cfg.xml文件,如果希望Activiti读取另外名称的配置文件,可以使用createProcessEngineConfigurationFromResource方法创建ProcessEngineConfiguration,该方法参数为一个字符串对象,调用该方法时,需要告诉Activiti配置文件位置。代码清单4-3调用createProcessEngineConfigurationFromResource(String resource)方法。
代码清单4-3:codes\04\4.1\create-resource\src\org\crazyit\activiti\CreateFromResource_1.java
// 指定配置文件创建ProcessEngineConfiguration
ProcessEngineConfiguration config = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("my-activiti1.xml");
代码清单4-3中,Activiti会到ClassPath下查找my-activiti1.xml配置文件,并创建名称为“processEngineConfiguration”的bean,此处创建bean的过程与4.1.1中描述一致,my-activiti1.xml文件的配置内容与代码清单4-2一致。
ProcessEngineConfiguration中还有一个createProcessEngineConfigurationFromResource的重载方法,该方法需要提供两个参数来创建ProcessEngineConfiguration,第一个参数为Activiti配置文件的位置,第二个参数为创建bean的名称。
代码清单4-4调用createProcessEngineConfigurationFromResource(String resource, String beanName)的方法。
代码清单4-4:codes\04\4.1\create-resource\src\org\crazyit\activiti\CreateFromResource_2.java
// 指定配置文件创建ProcessEngineConfiguration
ProcessEngineConfiguration config = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource(
"my-activiti2.xml", "test2");
System.out.println(config.getProcessEngineName());
代码清单4-4中,告诉Activiti需要到ClassPath下查找my-activiti2.xml文件,并且创建名字为“test”的bean。如果找不到名称为“test”的bean,则抛出NoSuchBeanDefinitionException,以下的代码会抛该异常,因为找不到名称为test2的bean。
ProcessEngineConfiguration config = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("my-activiti2.xml", "test2");
4.1.3 读取输入流的配置
ProcessEngineConfiguration中提供了一个createProcessEngineConfigurationFromInputStream方法,该方法使得Activiti配置文件的加载不再局限于项目的ClassPath,只要得到配置文件的输入流,即可创建ProcessEngineConfiguration。
同样的,createProcessEngineConfigurationFromInputStream方法也提供了两个重载的方法,可以指定在解析XML时bean的名称。
代码清单4-5中使用createProcessEngineConfigurationFromInputStream方法(没有指定bean名称)。
代码清单4-5:codes\04\4.1\create-stream\src\org\crazyit\activiti\CreateInputStream.java
File file = new File("resource/input-stream.xml");
// 得到文件输入流
InputStream fis = new FileInputStream(file);
// 使用createProcessEngineConfigurationFromInputStream方法创建ProcessEngineConfiguration
ProcessEngineConfiguration config = ProcessEngineConfiguration
.createProcessEngineConfigurationFromInputStream(fis);
4.1.4 使用createStandaloneInMemProcessEngineConfiguration方法
使用该方法创建ProcessEngineConfiguration,并不需要指定任何参数,该方法直接返回一个StandaloneInMemProcessEngineConfiguration实例,该类为ProcessEngineConfiguration的子类。使用该方法创建ProcessEngineConfiguration,并不会读取任何的Activiti配置文件,这样意味着流程引擎配置的全部属性,都会使用默认值,与其他子类不一样的是,创建的StandaloneInMemProcessEngineConfiguration实例,只特别指定了databaseSchemaUpdate属性和jdbcUrl属性,详细请见代码清单4-6。
代码清单4-6:
codes\04\4.1\create-standalone-inmem\src\org\crazyit\activiti\CreateStandaloneInMem.java
ProcessEngineConfiguration config = ProcessEngineConfiguration
.createStandaloneInMemProcessEngineConfiguration();
// 值为create-drop
System.out.println(config.getDatabaseSchemaUpdate());
// 值为jdbc:h2:mem:activiti
System.out.println(config.getJdbcUrl());
该方法不需要读取任何的配置文件,ClassPath下也没有任何的Activiti配置文件,如果需要改变相关的配置,可以调用ProcessEngineConfiguration中相应的setter方法进行修改。方法createStandaloneInMemProcessEngineConfiguration返回的是一个org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration实例,如果使用配置文件的方式创建ProcessEngineConfiguration,可以将该类配置为bean的class,但使用时需要注意该类中属性的默认值。
注:ProcessEngineConfiguration的各个属性及其作用,将在下面章节中逐一描述。
4.1.5 使用createStandaloneProcessEngineConfiguration方法
与4.1.4类似的是,createStandaloneProcessEngineConfiguration方法返回的是一个StandaloneProcessEngineConfiguration实例,并且需要注意的是,4.1.4中的StandaloneInMemProcessEngineConfiguration类是本小节StandaloneProcessEngineConfiguration类的子类。代码清单4-7中输出了StandaloneProcessEngineConfiguration类的databaseSchemaUpdate和jdbcUrl的值。
代码清单4-7:codes\04\4.1\create-standalone\src\org\crazyit\activiti\CreateStandalone.java
ProcessEngineConfiguration config = ProcessEngineConfiguration
.createStandaloneProcessEngineConfiguration();
// 默认值为 false
System.out.println(config.getDatabaseSchemaUpdate());
// 默认值为 jdbc:h2:tcp://localhost/~/activiti
System.out.println(config.getJdbcUrl());
从代码清单4-7中可以明显看出,父类StandaloneProcessEngineConfiguration的databaseSchemaUpdate和jdbcUrl属性值分别为“false”和“jdbc:h2:tcp://localhost/~/activiti”,而其子类StandaloneInMemProcessEngineConfiguration(4.1.4章节),这两个属性值分别为“create-drop”和“jdbc:h2:mem:activiti”。
4.2数据源配置
Activiti在启动时,会读取数据源配置,用于对数据库进行相应的操作。在前面章节中得知,Activiti会先读取配置文件,然后取得配置的bean,并对其进行初始化,本小节将讲解配置bean的一系列参数,并了解其作用。
4.2.1Activiti支持的数据库
Activiti默认H2数据库,H2是一个开源的嵌入式数据库,使用Java语言编写。使用H2数据库并不需要另外安装服务器或者客户端,只需要提供一个jar包即可使用。在实际的企业应用中,很少会使用这种轻量级的嵌入式数据库,因此H2数据更适合使用于单元测试。除H2数据库,Activiti还为以下的数据库提供支持:
MySQL:主流数据库之一,它是一个开源的小型关系型数据库,由它体积小、速度快,得到相当多开发者的青睐,并且最重要的是,它是免费的。
Oracle:目前世界上最流行的商业数据库,价格昂贵,但是它高效的性能、可靠的数据管理,仍令不少企业心甘情愿为其掏钱。
Postgres:PostgreSQL是另外一款开源的数据库。
DB2:由IBM公司研发的一款关系型数据库,其良好的伸缩性、数据库的高效性,让它成为继Oracle之后,又一应用广泛的商业数据库。
MSSQL:微软研发的一款数据库产品,目前也支持在Linux下使用。
4.2.2Activiti与Spring
Spring是目前非常流行的一个轻量级J2EE框架,它提供了一套轻量级的企业应用解决方案,它包括IoC容器、AOP面向切面技术以及Web MVC框架等。
使用Activiti的项目,并不意味着一定要使用Spring,Activiti可以在没有Spring的环境中使用。虽然Activiti并不需要使用Spring环境,但是Activiti在创建流程引擎时,使用了Spring的XML解析与依赖注入功能,ProcessEngineConfiguration对应的配置,即为Spring中的一个bean。
使用过Spring的读者,看到ProcessEngineConfiguration对应的配置,会感到非常熟悉,没有使用过的读者也不必感到气馁,因为Activiti也可以在一个完全没有Spring的环境中运行。
4.2.3JDBC配置
JDBC连接数据库,需要使用jdbc url、jdbc驱动、数据库用户名和密码,以下代码为连接MySQL的配置:
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<!-- JDBC url -->
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/act" />
<!-- JDBC驱动 -->
<property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
<!-- 数据库用户名 -->
<property name="jdbcUsername" value="root" />
<!-- 数据库密码 -->
<property name="jdbcPassword" value="123456" />
</bean>
以上代码配置一个bean,表示一个ProcessEngineConfiguration,并且使用“设值注入”的方式将四个数据库属性设置到该bean中,换言之,该ProcessEngineConfiguration类中,肯定有相应属性的setter方法。该bean的实现类以及这些属性,将在下面章节中作详细讲解。
4.2.4DBCP数据源配置
DBCP是Apache提供的一个数据库连接池。ProcessEngineConfiguration中提供了一个dataSource属性,如果用户不希望将JDBC的相关连接属性交给Activiti,可以自己创建数据库连接,然后通过这个dataSource属性设置到ProcessEngineConfiguration中。为Activiti的ProcessEngineConfiguration设置dataSource,可以采用配置或者编写代码的方式。代码清单4-8为使用配置方式使用DBCP数据源。
代码清单4-8:codes\04\4.2\ds-dbcp\resource\dbcp-config.xml
<!-- 使用DBCP数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/act" />
<property name="username" value="root" />
<property name="password" value="123456" />
</bean>
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="dataSource" ref="dataSource" />
</bean>
代码清单4-8中的dataSource,配置了一个DBCP的dataSource bean,然后在processEngineConfiguration的bean中注入该dataSource。在初始化流程引擎配置时,只需根据情况调用ProcessEngineConfiguration的createXXX方法即可,如以下代码所示:
// 读取 dbcp-config.xml配置
ProcessEngineConfiguration config = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("dbcp-config.xml");
// 能正常输出,即完成配置
DataSource ds = config.getDataSource();
// 查询数据库元信息,如果能查询则表示连接成功
ds.getConnection().getMetaData();
// 结果为 org.apache.commons.dbcp.BasicDataSource
System.out.println(ds.getClass().getName());
本例使用了createProcessEngineConfigurationFromResource方法读取Activiti的配置文件。除使用配置外,也可以通过编码方式设置相应的dataSource,只需要先创建一个DataSource对象,然后设置到ProcessEngineConfiguration中即可,代码清单4-9为通过编码方式设置DBCP数据源。
代码清单4-9:codes\04\4.2\ds-dbcp\src\org\crazyit\activiti\DBCPConfig.java
// 读取 dbcp-config.xml配置
ProcessEngineConfiguration config = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("dbcp-config.xml");
// 能正常输出,即完成配置
DataSource ds = config.getDataSource();
// 查询数据库元信息,如果能查询则表示连接成功
ds.getConnection().getMetaData();
// 结果为 org.apache.commons.dbcp.BasicDataSource
System.out.println(ds.getClass().getName());
代码清单4-9中,先创建DataSource对象,然后为该对象设置相应的数据库连接属性,然后读取Activiti配置文件,得到ProcessEngineConfiguration对象,并将DataSource设置到该对象中。ProcessEngineConfiguration的bean配置不需要设置任何属性:
<!-- 不初始化任何属性 -->
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
</bean>
在笔者成书时,DBCP项目已经发展到2.1版本,本小节的案例就是基于该版本。
4.2.5C3P0数据源配置
与DBCP类似,C3P0也是一个开源的数据库连接池,它们都被广泛的应用到开源项目以及企业用中同。与DBCP类似,可以在Activiti中使用C3P0数据源,配置方式大致相同,代码清单4-10为C3P0 bean的配置。
代码清单4-10:codes\04\4.2\ds-c3p0\resource\config\c3p0-config.xml
<!-- 使用C3P0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/act" />
<property name="user" value="root" />
<property name="password" value="123456" />
</bean>
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="dataSource" ref="dataSource" />
</bean>
注:此处需要注意的是,DBCP与C3P0的属性名称不一样,可以到两个数据源的官方文档查看更详细的配置。
除了配置方式外,也可以像DBCP一样使用编码方式创建数据源,设置方式基本与DBCP一致,只是创建DataSource实例的方式不一样而已。代码清单4-11展示如何创建C3P0数据源。
代码清单4-11:codes\04\4.2\ds-c3p0\src\org\crazyit\activiti\C3P0Coding.java:
// 创建C3P0数据源
ComboPooledDataSource ds = new ComboPooledDataSource();
// 设置JDBC连接的各个属性
ds.setUser("root");
ds.setPassword("123456");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/act");
ds.setDriverClass("com.mysql.jdbc.Driver");
// 验证是否连接成功
ds.getConnection().getMetaData();
// 读取Activiti配置文件
ProcessEngineConfiguration config = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("config/c3p0-coding.xml");
// 为ProcessEngineConfiguration设置dataSource属性
config.setDataSource(ds);
System.out.println(config.getDataSource());
4.2.6 Activiti其他数据源配置
如果不使用第三方数据源,直接使用Activiti提供的数据源,那么还可以指定其他一些数据库属性。Activiti默认使用的是myBatis的数据连接池,因此ProcessEngineConfiguration中也提供了一些MyBatis的配置:
jdbcMaxActiveConnections:在数据库连接池内最大的活跃连接数,默认值为10。
jdbcMaxIdleConnections:连接池最大的空闲连接数。
jdbcMaxCheckoutTime:当连接池内的连接耗尽,外界向连接池请求连接时,创建连接的等待时间,单位为毫秒,默认值为20000,即20秒。
jdbcMaxWaitTime:当整个连接池需要重新获取连接的时候,设置等待时间,单位为毫秒,默认值为20000,即20秒。
4.2.7 数据库策略配置
ProcessEngineConfiguration提供了databaseSchemaUpdate属性,该项可以设置流程引擎启动和关闭时数据库执行的策略。Activiti的官方文档中,databaseSchemaUpdate有以下三个值:
false:false为默认值,设置为该值后,Activiti在启动时,会对比数据库表中保存的版本,如果没有表或者版本不匹配时,将在启动时抛出异常。
true:设置为该值后,Activiti会对数据库中所有的表进行更新,如果表不存在,则Activiti会自动创建。
create-drop:Activiti启动时,会执行数据库表的创建操作,在Activiti关闭时,执行数据库表的删除操作。
代码清单4-12将databaseSchemaUpdate配置为false。
代码清单4-12:codes\04\4.2\schema-update\resource\schemaUpdate-false.xml
<!-- 将databaseSchemaUpdate设置为false -->
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/act" />
<property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
<property name="jdbcUsername" value="root" />
<property name="jdbcPassword" value="123456" />
<property name="databaseSchemaUpdate" value="false"/>
</bean>
使用以下代码启动Activiti流程引擎:
//读取Activiti配置
ProcessEngineConfiguration config = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("schemaUpdate-false.xml");
//启动Activiti
config.buildProcessEngine();
如果没有数据表,则会抛出异常。这里需要注意的是,如果想看到抛出异常的效果,需要将相应数据库里面的表全部删除。如果想执行数据库表结构更新,可以将该配置设置为true,将全部数据库表删除后,再启动Activiti,即可看到Activiti已经建好全部的表,值为true的配置与代码在此不再赘述,读者可看以下配置文件与Java类:
配置文件:codes\04\4.2\schema-update\resource\schemaUpdate-true.xml
Java类:
codes\04\4.2\schema-update\src\org\crazyit\activiti\DatabaseSchemaUpdateTrue.java
将databaseSchemaUpdate设置为create-drop后,Activiti会先检查数据表是否存在,如果表已经存在,则抛出异常并停止创建流程引擎。代码清单4-13使用create-drop属性启动Activiti。
代码清单4-13:
codes\04\4.2\schema-update\src\org\crazyit\activiti\DatabaseSchemaUpdateCreateDrop.java
// 读取Activiti配置
ProcessEngineConfiguration config = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("schemaUpdate-create-drop.xml");
// 启动Activiti
ProcessEngine engine = config.buildProcessEngine();
// 关闭流程引擎
engine.close();
注意代码清单4-13中的close方法,如果想要Activiti执行“drop”操作,必须要调用ProcessEngine的close方法,否则将不会删除表。一般情况下,将databaseSchemaUpdate配置为create-drop,更适合在单元测试中使用。
除了false、true和create-drop三个值外,databaseSchemaUpdate还有一个drop-create值,跟create-drop类似,drop-create会在流程引擎启动时,先将原来全部的数据表删除,再进行创建,与create-drop不同的是,不管是否调用ProcessEngine的close方法,都会执行create操作。同样地,该值在单元测试中使用比较合适,在流程引擎初始化时将原有的数据删除,在实际应用中,此举会带来较大的风险,Activiti的官方文档并没有提供该项配置,读者知道即可。
注:使用各种方法读取Activiti配置,均不会创建数据库表,Activiti的数据库表只会在流程引擎创建的时候,才会按照配置的策略进行创建。
4.2.8 databaseType配置
根据前一小节得知,将databaseSchemaUpdate设置为create-drop或者drop-create时,Activiti在启动和初始化时,会执行相应的创建表和删除表操作,Activiti支持多种数据库,每种数据库的创建表与删除表的语法有可能不一样,因此,需要指定databaseType属性,来告诉Activiti,目前使用了何种数据库(当然,如果设置true而数据库中没有表的话,也需要知道使用哪种数据库)。databaseType属性支持这些值:h2, mysql, oracle, postgres, mssql, db2,没有指定值时,databaseType为null。指定databaseType属性,目的是为了确定执行创建(或删除)表的SQL脚本。
代码清单4-14,将该属性设置为oracle。
代码清单4-14:codes\04\4.2\db-type\resource\database-type.xml
<!-- 将databaseType设置为oracle -->
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/act" />
<property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
<property name="jdbcUsername" value="root" />
<property name="jdbcPassword" value="123456" />
<property name="databaseSchemaUpdate" value="create-drop"/>
<property name="databaseType" value="oracle"/>
</bean>
使用以上配置,然后启动和关闭Activiti,会抛出MySQL异常,因为Activiti会根据该值去使用Oracle创建表和删除表的脚本,Oracle的SQL脚本在MySQL上面执行,肯定会出错。
实际上,可以根本不需要指定该属性,Activiti就可以知道使用的是哪种数据库,因为配置数据源时就提供了JDBC连接属性给Activiti,根据这些属性创建JDBC连接,得到Connection对象后,可以调用getMetaData方法获取当前数据库的元数据,完全可以判断出当前所使用的数据库。的确,Activiti也是这样做的,但是为什么另外提供一个databaseType属性如此多此一举呢?笔者认为,Activiti为防止适配数据库类型出现异常,就提供多一个这样的值来给使用者选择,确保能适配到准确的数据库类型。
注:没有配置databaseType属性,Activiti会使用Connection的getMetaData方法获取数据库元数据,但是一旦配置了databaseType属性,将会以该值为准。
本文节选自《疯狂工作流讲义(第2版)》
京东购买地址:https://item.jd.com/12246565.html
疯狂Activiti电子书:https://my.oschina.net/JavaLaw/blog/1570397
工作流Activiti教学视频:https://my.oschina.net/JavaLaw/blog/1577577
本书代码目录:https://gitee.com/yangenxiong/CrazyActiviti