4 配置文件读取与数据源配置
          本书代码目录:https://gitee.com/yangenxiong/CrazyActiviti
          要点
               掌握Activiti的配置文件读取方式
               掌握Activiti的数据源配置
   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属性,将会以该值为准。
          本文节选自《疯狂Workflow讲义(第2版)》。