Jekyll2020-12-29T02:23:06+08:00https://kafeitu.me/feed.xml咖啡兔的博客A flexible Jekyll theme for your blog or site with a minimalist aesthetic.咖啡兔https://henryyan.github.io集成新版(5.17+)Activiti Modeler与Rest服务2015-12-27T00:00:00+08:002015-12-27T00:00:00+08:00https://kafeitu.me/activiti/2015/12/27/integrate-new-activiti-modeler-and-rest<aside class="sidebar__right">
<nav class="toc">
<header><h4 class="nav__title"><i class="fa fa-file-text"></i> Getting Started</h4></header>
<ul class="toc__menu" id="markdown-toc">
<li><a href="#1-简介" id="markdown-toc-1-简介">1. 简介</a> <ul>
<li><a href="#11-新版activiti-modeler特性" id="markdown-toc-11-新版activiti-modeler特性">1.1 新版Activiti Modeler特性</a></li>
</ul>
</li>
<li><a href="#2-官方activiti-explorer的集成方式" id="markdown-toc-2-官方activiti-explorer的集成方式">2. 官方Activiti Explorer的集成方式</a> <ul>
<li><a href="#21-activiti-exploer的内部结构-java" id="markdown-toc-21-activiti-exploer的内部结构-java">2.1 Activiti Exploer的内部结构-Java</a></li>
<li><a href="#22-activiti-exploer的内部结构-web" id="markdown-toc-22-activiti-exploer的内部结构-web">2.2 Activiti Exploer的内部结构-Web</a></li>
</ul>
</li>
<li><a href="#3-整合到自己的项目中" id="markdown-toc-3-整合到自己的项目中">3. 整合到自己的项目中</a> <ul>
<li><a href="#31-activiti-rest接口与spring-mvc配置" id="markdown-toc-31-activiti-rest接口与spring-mvc配置">3.1 Activiti Rest接口与Spring MVC配置</a> <ul>
<li><a href="#311-maven依赖" id="markdown-toc-311-maven依赖">3.1.1 Maven依赖</a></li>
<li><a href="#312-准备基础服务类" id="markdown-toc-312-准备基础服务类">3.1.2 准备基础服务类</a></li>
<li><a href="#313-activiti-spring配置" id="markdown-toc-313-activiti-spring配置">3.1.3 Activiti Spring配置</a></li>
<li><a href="#314-spring-mvc配置" id="markdown-toc-314-spring-mvc配置">3.1.4 Spring MVC配置</a></li>
<li><a href="#315-webxml中配置servlet服务" id="markdown-toc-315-webxml中配置servlet服务">3.1.5 web.xml中配置Servlet服务</a></li>
<li><a href="#316-模型设计器的web资源" id="markdown-toc-316-模型设计器的web资源">3.1.6 模型设计器的Web资源</a></li>
</ul>
</li>
<li><a href="#317-模型控制器" id="markdown-toc-317-模型控制器">3.1.7 模型控制器</a></li>
</ul>
</li>
<li><a href="#4-整合activiti-rest" id="markdown-toc-4-整合activiti-rest">4. 整合Activiti Rest</a> <ul>
<li><a href="#41-maven依赖" id="markdown-toc-41-maven依赖">4.1 Maven依赖</a></li>
<li><a href="#43-activiti组件包扫描" id="markdown-toc-43-activiti组件包扫描">4.3 Activiti组件包扫描</a></li>
<li><a href="#44-添加rest安全认证组件" id="markdown-toc-44-添加rest安全认证组件">4.4 添加Rest安全认证组件</a></li>
<li><a href="#45-spring-mvc配置文件" id="markdown-toc-45-spring-mvc配置文件">4.5 spring mvc配置文件</a></li>
<li><a href="#46-配置servlet映射" id="markdown-toc-46-配置servlet映射">4.6 配置Servlet映射</a></li>
<li><a href="#47-访问rest接口" id="markdown-toc-47-访问rest接口">4.7 访问Rest接口</a></li>
</ul>
</li>
<li><a href="#5-结束语" id="markdown-toc-5-结束语">5. 结束语</a></li>
</ul>
</nav>
</aside>
<p>这又是一片迟来的博客,上一篇博文还是2014年4月24日写的,因为很多内容都在书(《<a href="activiti-in-action.html">Activiti实战</a>》)里了已经有详细的解释了,不过由于书里面使用的是<strong>5.16.4</strong>版本,从<strong>5.17.0</strong>版本后Activiti Modeler的整合方式有些变化,所以写此博问作为补充内容。</p>
<p><strong>声明</strong>:</p>
<ol>
<li>此教程适合Activiti 5.17+版本。</li>
<li>本博客所涉及的内容均可在<a href="https://github.com/henryyan/kft-activiti-demo">kft-activiti-demo</a>中找到。</li>
<li>在线demo可以访问 <a href="http://demo.kafeitu.me:8080/kft-activiti-demo">http://demo.kafeitu.me:8080/kft-activiti-demo</a> 菜单路径:管理模块 -> 流程管理 -> 模型工作区,可以『创建』或者『编辑』模型</li>
</ol>
<h2 id="1-简介">1. 简介</h2>
<p>上一篇介绍整合Activiti Modeler<a href="http://www.kafeitu.me/activiti/2013/03/10/integrate-activiti-modeler.html">《整合Activiti Modeler到业务系统(或BPM平台)》</a>已经有2年多时间了,自从Activiti 5.17版本发布以后该教程已经不适用了,很多网友也反馈不知道怎么把Activiti Modeler整合到自己的项目中去,为此抽时间为适配5.17+版本的集成方法整理成这篇博文,希望对有需求的网友有帮助。</p>
<p>最新版本的kft-activiti-demo已经使用了5.17+版本的Activiti,并且集成了最新的Activiti Modeler组件,可以下载最新源码:<a href="https://github.com/henryyan/kft-activiti-demo">https://github.com/henryyan/kft-activiti-demo</a>。</p>
<h3 id="11-新版activiti-modeler特性">1.1 新版Activiti Modeler特性</h3>
<p>先来欣赏一下新版的界面,相比上一版漂亮了许多,调性高了~~~</p>
<p><img src="/files/2015/12/new-activiti-modeler.png" alt="新版Activiti Modeler" /></p>
<p>界面布局:上(工具区)、左(组件类目)、右(工作区)、右下(属性区)</p>
<p>Activiti Modeler内部的实现上还是以oryx为图形组件为内核,用angular.js作为界面基本元素的基础组件以及调度oryx的API。</p>
<h2 id="2-官方activiti-explorer的集成方式">2. 官方Activiti Explorer的集成方式</h2>
<p>先从Github下载官方Activiti源码,地址:<a href="https://github.com/Activiti/Activiti">https://github.com/Activiti/Activiti</a>。</p>
<h3 id="21-activiti-exploer的内部结构-java">2.1 Activiti Exploer的内部结构-Java</h3>
<p>源码目录(如果是zip下载请先解压缩)中找到<strong>modules/activiti-webapp-explorer2/src/main</strong>子目录,结构如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>├── assembly
├── java
│ └── org
│ └── activiti
├── resources
│ └── org
│ └── activiti
└── webapp
├── META-INF
├── VAADIN
│ ├── themes
│ └── widgetsets
├── WEB-INF
├── diagram-viewer
│ ├── images
│ └── js
└── editor-app
├── configuration
├── css
├── editor
├── fonts
├── i18n
├── images
├── libs
├── partials
├── popups
└── stencilsets
</code></pre></div></div>
<p>我们需要关注的目录是<strong>webapp/editor-app</strong>,以及<strong>java/org/activiti</strong>,目录结构:</p>
<p><img src="/files/2015/12/new-activiti-modeler-explorer-folders.png" alt="Activiti Explorer源码目录" /></p>
<blockquote>
<p>新版本的Activiti Explorer放弃了XML方式的配置方式,采用Bean Configuration的方式代替,上图中<strong>org/activiti/explorer/conf</strong>包中就是各种配置,在<strong>org/activiti/explorer/servlet/WebConfigurer</strong>类用Servlet 3.0方式配置Servlet映射关系,映射的路径为<strong>/service/*</strong>。</p>
</blockquote>
<h3 id="22-activiti-exploer的内部结构-web">2.2 Activiti Exploer的内部结构-Web</h3>
<p>新版本Activiti Modeler的Web资源不再像旧版那么散乱,新版本只需要关注:</p>
<ul>
<li>src/main/webapp/editor-app:目录中包含设计器里面所有的资源:angular.js、oryx.js以及配套的插件及css</li>
<li>src/main/webapp/modeler.html:设计器的主页面,用来引入各种web资源</li>
<li>src/main/resources/stencilset.json: bpmn标准里面各种组件的json定义,editor以import使用。</li>
</ul>
<h2 id="3-整合到自己的项目中">3. 整合到自己的项目中</h2>
<p>了解过网友的需求不知道如何整合新版Activiti Modeler的原因有两个:</p>
<ol>
<li>不知道怎么把注解的方式转换为XML方式</li>
<li>editor-app目录的结构位置</li>
<li>和自己应用的整合参数配置</li>
</ol>
<h3 id="31-activiti-rest接口与spring-mvc配置">3.1 Activiti Rest接口与Spring MVC配置</h3>
<h4 id="311-maven依赖">3.1.1 Maven依赖</h4>
<p>Activiti Modeler对后台服务的调用通过Spring MVC方式实现,所有的Rest资源统一使用注解RestController标注,所以在整合到自己项目的时候需要依赖Spring MVC,Modeler模块使用的后台服务都存放在<strong>activiti-modeler</strong>模块中,在自己的项目中添加依赖:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.activiti<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>activiti-modeler<span class="nt"></artifactId></span>
<span class="nt"><version></span>5.19.0<span class="nt"></version></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.activiti<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>activiti-diagram-rest<span class="nt"></artifactId></span>
<span class="nt"><version></span>5.19.0<span class="nt"></version></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>模块作用:</p>
<ul>
<li>activiti-modeler模块提供模型先关的操作:创建、保存、转换json与xml格式等</li>
<li>activiti-diagram-rest模块用来处理流程图有关的功能:流程图布局(layout)、节点高亮等</li>
</ul>
<h4 id="312-准备基础服务类">3.1.2 准备基础服务类</h4>
<p>复制文件(https://github.com/henryyan/kft-activiti-demo/tree/master/src/main/java/org/activiti/explorer) 里面的java文件到自己项目中。</p>
<h4 id="313-activiti-spring配置">3.1.3 Activiti Spring配置</h4>
<p>创建文件<strong>src/main/resources/beans/beans-activiti.xml</strong>定义Activiti引擎的beans:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><beans</span> <span class="na">xmlns=</span><span class="s">"http://www.springframework.org/schema/beans"</span> <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
<span class="na">xsi:schemaLocation=</span><span class="s">"
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd"</span><span class="nt">></span>
<span class="nt"><context:component-scan</span>
<span class="na">base-package=</span><span class="s">"org.activiti.conf,org.activiti.rest.editor"</span><span class="nt">></span>
<span class="nt"><context:exclude-filter</span> <span class="na">type=</span><span class="s">"annotation"</span> <span class="na">expression=</span><span class="s">"org.springframework.stereotype.Controller"</span><span class="nt">/></span>
<span class="nt"></context:component-scan></span>
<span class="c"><!-- 单例json对象 --></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"objectMapper"</span> <span class="na">class=</span><span class="s">"com.fasterxml.jackson.databind.ObjectMapper"</span><span class="nt">/></span>
<span class="c"><!-- 引擎内部提供的UUID生成器,依赖fastxml的java-uuid-generator模块 --></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"uuidGenerator"</span> <span class="na">class=</span><span class="s">"org.activiti.engine.impl.persistence.StrongUuidGenerator"</span> <span class="nt">/></span>
<span class="c"><!-- Activiti begin --></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"processEngineConfiguration"</span> <span class="na">class=</span><span class="s">"org.activiti.spring.SpringProcessEngineConfiguration"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"dataSource"</span> <span class="na">ref=</span><span class="s">"dataSource"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"transactionManager"</span> <span class="na">ref=</span><span class="s">"transactionManager"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"databaseSchemaUpdate"</span> <span class="na">value=</span><span class="s">"true"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"jobExecutorActivate"</span> <span class="na">value=</span><span class="s">"true"</span><span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"processEngine"</span> <span class="na">class=</span><span class="s">"org.activiti.spring.ProcessEngineFactoryBean"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"processEngineConfiguration"</span> <span class="na">ref=</span><span class="s">"processEngineConfiguration"</span><span class="nt">/></span>
<span class="nt"></bean></span>
<span class="c"><!-- 7大接口 --></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"repositoryService"</span> <span class="na">factory-bean=</span><span class="s">"processEngine"</span> <span class="na">factory-method=</span><span class="s">"getRepositoryService"</span><span class="nt">/></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"runtimeService"</span> <span class="na">factory-bean=</span><span class="s">"processEngine"</span> <span class="na">factory-method=</span><span class="s">"getRuntimeService"</span><span class="nt">/></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"formService"</span> <span class="na">factory-bean=</span><span class="s">"processEngine"</span> <span class="na">factory-method=</span><span class="s">"getFormService"</span><span class="nt">/></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"identityService"</span> <span class="na">factory-bean=</span><span class="s">"processEngine"</span> <span class="na">factory-method=</span><span class="s">"getIdentityService"</span><span class="nt">/></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"taskService"</span> <span class="na">factory-bean=</span><span class="s">"processEngine"</span> <span class="na">factory-method=</span><span class="s">"getTaskService"</span><span class="nt">/></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"historyService"</span> <span class="na">factory-bean=</span><span class="s">"processEngine"</span> <span class="na">factory-method=</span><span class="s">"getHistoryService"</span><span class="nt">/></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"managementService"</span> <span class="na">factory-bean=</span><span class="s">"processEngine"</span> <span class="na">factory-method=</span><span class="s">"getManagementService"</span><span class="nt">/></span>
<span class="nt"></beans></span>
</code></pre></div></div>
<p>在spring初始化的时候引入即可,例如在web.xml中使用通配符方式:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><context-param></span>
<span class="nt"><param-name></span>contextConfigLocation<span class="nt"></param-name></span>
<span class="nt"><param-value></span>classpath*:/beans/beans-*.xml<span class="nt"></param-value></span>
<span class="nt"></context-param></span>
</code></pre></div></div>
<h4 id="314-spring-mvc配置">3.1.4 Spring MVC配置</h4>
<p>创建文件<strong>WEB-INF/spring-mvc-modeler.xml</strong>,内容如下:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><beans</span> <span class="na">xmlns=</span><span class="s">"http://www.springframework.org/schema/beans"</span> <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
<span class="na">xmlns:context=</span><span class="s">"http://www.springframework.org/schema/context"</span>
<span class="na">xmlns:mvc=</span><span class="s">"http://www.springframework.org/schema/mvc"</span>
<span class="na">xsi:schemaLocation=</span><span class="s">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd"</span><span class="nt">></span>
<span class="c"><!-- 自动扫描且只扫描@Controller --></span>
<span class="nt"><context:component-scan</span> <span class="na">base-package=</span><span class="s">"org.activiti.rest.editor,org.activiti.rest.diagram"</span><span class="nt">></span>
<span class="nt"><context:include-filter</span> <span class="na">type=</span><span class="s">"annotation"</span> <span class="na">expression=</span><span class="s">"org.springframework.stereotype.Controller"</span> <span class="nt">/></span>
<span class="nt"></context:component-scan></span>
<span class="nt"><mvc:annotation-driven</span> <span class="nt">/></span>
<span class="nt"></beans></span>
</code></pre></div></div>
<p>上面XML中告知spring mvc扫描路径为**</p>
<h4 id="315-webxml中配置servlet服务">3.1.5 web.xml中配置Servlet服务</h4>
<p>在<strong>web.xml</strong>中配置下面的Servlet:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><servlet></span>
<span class="nt"><servlet-name></span>ModelRestServlet<span class="nt"></servlet-name></span>
<span class="nt"><servlet-class></span>org.springframework.web.servlet.DispatcherServlet<span class="nt"></servlet-class></span>
<span class="nt"><init-param></span>
<span class="nt"><param-name></span>contextConfigLocation<span class="nt"></param-name></span>
<span class="nt"><param-value></span>/WEB-INF/spring-mvc-modeler.xml<span class="nt"></param-value></span>
<span class="nt"></init-param></span>
<span class="nt"><load-on-startup></span>1<span class="nt"></load-on-startup></span>
<span class="nt"></servlet></span>
<span class="nt"><servlet-mapping></span>
<span class="nt"><servlet-name></span>ModelRestServlet<span class="nt"></servlet-name></span>
<span class="nt"><url-pattern></span>/service/*<span class="nt"></url-pattern></span>
<span class="nt"></servlet-mapping></span>
</code></pre></div></div>
<h4 id="316-模型设计器的web资源">3.1.6 模型设计器的Web资源</h4>
<ol>
<li>
<p>直接从Activiti Explorer中复制文件<strong>modeler.html</strong>文件到<strong>src/main/webapp</strong>目录即可,该文件会引入定义基本的布局(div)、引入css以及js文件。</p>
</li>
<li>
<p>修改<strong>editor-app/app-cfg.js</strong>文件的<strong>contextRoot</strong>属性为自己的应用名称,例如<strong>/kft-activiti-demo/service</strong></p>
</li>
</ol>
<h3 id="317-模型控制器">3.1.7 模型控制器</h3>
<p>在<a href="http://www.kafeitu.me/activiti/2013/03/10/integrate-activiti-modeler.html">《整合Activiti Modeler到业务系统(或BPM平台)》</a>中已经介绍过<strong>ModelController</strong>类的作用了,这里需要在基础上稍微做一点调整:</p>
<ul>
<li><strong>create</strong>方法中在创建完Model后跳转页面由<strong>service/editor?id=</strong>改为<strong>modeler.html?modelId=</strong></li>
<li>当从模型列表编辑某一个模型时也需要把路径修改为<strong>modeler.html?modelId=</strong></li>
</ul>
<h2 id="4-整合activiti-rest">4. 整合Activiti Rest</h2>
<p>有了Activiti Modeler的基础只需要依葫芦画瓢即可。</p>
<h3 id="41-maven依赖">4.1 Maven依赖</h3>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.activiti<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>activiti-rest<span class="nt"></artifactId></span>
<span class="nt"><version></span>5.19.0<span class="nt"></version></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<h3 id="43-activiti组件包扫描">4.3 Activiti组件包扫描</h3>
<p>文件<strong>src/main/resources/beans/beans-activiti.xml</strong>context:component-scan标签的<strong>base-package</strong>属性中添加<strong>org.activiti.rest.service</strong>包,包里面包含了所有Rest API的接口Rest Controller。</p>
<h3 id="44-添加rest安全认证组件">4.4 添加Rest安全认证组件</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">org.activiti.conf</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.activiti.rest.security.BasicAuthenticationProvider</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.context.annotation.Bean</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.context.annotation.Configuration</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.security.authentication.AuthenticationProvider</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.security.config.annotation.web.builders.HttpSecurity</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.security.config.annotation.web.configuration.EnableWebSecurity</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.security.config.http.SessionCreationPolicy</span><span class="o">;</span>
<span class="nd">@Configuration</span>
<span class="nd">@EnableWebSecurity</span>
<span class="nd">@EnableWebMvcSecurity</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SecurityConfiguration</span> <span class="kd">extends</span> <span class="nc">WebSecurityConfigurerAdapter</span> <span class="o">{</span>
<span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">AuthenticationProvider</span> <span class="nf">authenticationProvider</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">BasicAuthenticationProvider</span><span class="o">();</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">configure</span><span class="o">(</span><span class="nc">HttpSecurity</span> <span class="n">http</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">http</span><span class="o">.</span><span class="na">authenticationProvider</span><span class="o">(</span><span class="n">authenticationProvider</span><span class="o">())</span>
<span class="o">.</span><span class="na">sessionManagement</span><span class="o">().</span><span class="na">sessionCreationPolicy</span><span class="o">(</span><span class="nc">SessionCreationPolicy</span><span class="o">.</span><span class="na">STATELESS</span><span class="o">).</span><span class="na">and</span><span class="o">()</span>
<span class="o">.</span><span class="na">csrf</span><span class="o">().</span><span class="na">disable</span><span class="o">()</span>
<span class="o">.</span><span class="na">authorizeRequests</span><span class="o">()</span>
<span class="o">.</span><span class="na">anyRequest</span><span class="o">().</span><span class="na">authenticated</span><span class="o">()</span>
<span class="o">.</span><span class="na">and</span><span class="o">()</span>
<span class="o">.</span><span class="na">httpBasic</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="45-spring-mvc配置文件">4.5 spring mvc配置文件</h3>
<p>创建文件<strong>WEB-INF/spring-mvc-rest.xml</strong>:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><beans</span> <span class="na">xmlns=</span><span class="s">"http://www.springframework.org/schema/beans"</span> <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
<span class="na">xmlns:context=</span><span class="s">"http://www.springframework.org/schema/context"</span>
<span class="na">xmlns:mvc=</span><span class="s">"http://www.springframework.org/schema/mvc"</span>
<span class="na">xsi:schemaLocation=</span><span class="s">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd"</span><span class="nt">></span>
<span class="c"><!-- 自动扫描且只扫描@Controller --></span>
<span class="nt"><context:component-scan</span> <span class="na">base-package=</span><span class="s">"org.activiti.rest"</span><span class="nt">></span>
<span class="nt"><context:include-filter</span> <span class="na">type=</span><span class="s">"annotation"</span> <span class="na">expression=</span><span class="s">"org.springframework.stereotype.Controller"</span> <span class="nt">/></span>
<span class="nt"></context:component-scan></span>
<span class="nt"><mvc:annotation-driven</span> <span class="nt">/></span>
<span class="nt"></beans></span>
</code></pre></div></div>
<h3 id="46-配置servlet映射">4.6 配置Servlet映射</h3>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><servlet></span>
<span class="nt"><servlet-name></span>RestServlet<span class="nt"></servlet-name></span>
<span class="nt"><servlet-class></span>org.springframework.web.servlet.DispatcherServlet<span class="nt"></servlet-class></span>
<span class="nt"><init-param></span>
<span class="nt"><param-name></span>contextConfigLocation<span class="nt"></param-name></span>
<span class="nt"><param-value></span>/WEB-INF/spring-mvc-rest.xml<span class="nt"></param-value></span>
<span class="nt"></init-param></span>
<span class="nt"><load-on-startup></span>1<span class="nt"></load-on-startup></span>
<span class="nt"></servlet></span>
<span class="nt"><servlet-mapping></span>
<span class="nt"><servlet-name></span>RestServlet<span class="nt"></servlet-name></span>
<span class="nt"><url-pattern></span>/rest/*<span class="nt"></url-pattern></span>
<span class="nt"></servlet-mapping></span>
</code></pre></div></div>
<h3 id="47-访问rest接口">4.7 访问Rest接口</h3>
<p>现在启动应用可以访问 http://localhost:8080/your-app/rest/management/properties 以Rest方式查看引擎的属性列表,如果在网页中访问会提示输入用户名密码;也可以访问在线demo测试
http://demo.kafeitu.me:8080/kft-activiti-demo/rest/management/properties (用户名:kafeitu,密码:000000)</p>
<h2 id="5-结束语">5. 结束语</h2>
<p>以上步骤如果在实施过程中有问题可以参考<a href="https://github.com/henryyan/kft-activiti-demo">kft-activiti-demo</a>中的配置,有其他问题可以在本博客中留言或者到QQ群问询。</p>
<p>最后再广告一下我的书<a href="/activiti-in-action.html">《Activiti实战》</a>,Tijs强力推荐的哦;同时也感谢一直支持和活跃在Activiti社区的你。</p>咖啡兔https://henryyan.github.ioGetting Started集成Diagram Viewer跟踪流程2014-04-24T00:00:00+08:002014-04-24T00:00:00+08:00https://kafeitu.me/activiti/2014/04/24/diagram-viewer<p>首先这是一篇迟来的教程,因为从5.12版本(目前最新版本为5.15.1)开始就已经提供了Diagram Viewer这个流程图跟踪组件,不管如何总归有人需要用到,所以我觉得还是要和大家分享一下。</p>
<h2 id="1-前言">1. 前言</h2>
<p>目前被大家所采用的流程图跟踪有两种方式:</p>
<ul>
<li>一种是由引擎后台提供图片,可以把当前节点标记用红色</li>
<li>一种是比较灵活的方式,先用引擎接口获取流程图(原图),然后再通过解析引擎的Activity对象逐个解析(主要是判断哪个是当前节点),最后把这些对象组成一个集合转换成JSON格式的数据输出给前端,用Javascript和Css技术实现流程的跟踪</li>
</ul>
<p>这两种方式在kft-activiti-demo中都有演示,这里就不介绍了,参考流程跟踪部门代码即可。</p>
<h2 id="2-diagram-viewer简介">2. Diagram Viewer简介</h2>
<p>Diagram Viewer是官方在5.12版本中添加的新组件,以<a href="http://raphaeljs.com">Raphaël</a>为基础库,用REST(参考:《<a href="/activiti/2013/01/12/kft-activiti-demo-rest.html">如何使用Activiti Rest模块</a>》)方式获取JSON数据生成流程图并把流程的处理过程用不同的颜色加以标注,最终的效果如下图所示。</p>
<p><img src="/files/2014/04/diagram-viewer.jpg" alt="" /></p>
<p>在应用中使用时也很方便,把这个组件的源码复制到项目中再配置一个REST拦截器,最后拼接一个URL即可;举个例子:</p>
<blockquote>
<p>http://demo.kafeitu.me/kft-activiti-demo/diagram-viewer/index.html?processDefinitionId=leave-jpa:1:22&processInstanceId=27</p>
</blockquote>
<p>这个URL中有两个参数:</p>
<ul>
<li>processDefinitionId: 流程定义ID</li>
<li>processInstanceId: 流程实例ID</li>
</ul>
<h2 id="3-集成diagram-viewer">3. 集成Diagram Viewer</h2>
<h3 id="31-创建rest路由类">3.1 创建REST路由类</h3>
<p>REST路由类源码在官方的Activiti Explorer里面有提供,代码如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">org.activiti.explorer.rest</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.activiti.rest.common.api.DefaultResource</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.activiti.rest.common.application.ActivitiRestApplication</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.activiti.rest.common.filter.JsonpFilter</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.activiti.rest.diagram.application.DiagramServicesInit</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.activiti.rest.editor.application.ModelerServicesInit</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.restlet.Restlet</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.restlet.routing.Router</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ExplorerRestApplication</span> <span class="kd">extends</span> <span class="nc">ActivitiRestApplication</span> <span class="o">{</span>
<span class="kd">public</span> <span class="nf">ExplorerRestApplication</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">();</span>
<span class="o">}</span>
<span class="cm">/**
* Creates a root Restlet that will receive all incoming calls.
*/</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kd">synchronized</span> <span class="nc">Restlet</span> <span class="nf">createInboundRoot</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">Router</span> <span class="n">router</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Router</span><span class="o">(</span><span class="n">getContext</span><span class="o">());</span>
<span class="n">router</span><span class="o">.</span><span class="na">attachDefault</span><span class="o">(</span><span class="nc">DefaultResource</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="nc">ModelerServicesInit</span><span class="o">.</span><span class="na">attachResources</span><span class="o">(</span><span class="n">router</span><span class="o">);</span>
<span class="nc">DiagramServicesInit</span><span class="o">.</span><span class="na">attachResources</span><span class="o">(</span><span class="n">router</span><span class="o">);</span>
<span class="nc">JsonpFilter</span> <span class="n">jsonpFilter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">JsonpFilter</span><span class="o">(</span><span class="n">getContext</span><span class="o">());</span>
<span class="n">jsonpFilter</span><span class="o">.</span><span class="na">setNext</span><span class="o">(</span><span class="n">router</span><span class="o">);</span>
<span class="k">return</span> <span class="n">jsonpFilter</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>把这个路由配置到<strong>web.xml</strong>中:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><servlet></span>
<span class="nt"><servlet-name></span>ExplorerRestletServlet<span class="nt"></servlet-name></span>
<span class="nt"><servlet-class></span>org.restlet.ext.servlet.ServerServlet<span class="nt"></servlet-class></span>
<span class="nt"><init-param></span>
<span class="c"><!-- Application class name --></span>
<span class="nt"><param-name></span>org.restlet.application<span class="nt"></param-name></span>
<span class="nt"><param-value></span>org.activiti.explorer.rest.ExplorerRestApplication<span class="nt"></param-value></span>
<span class="nt"></init-param></span>
<span class="nt"></servlet></span>
<span class="nt"><servlet-mapping></span>
<span class="nt"><servlet-name></span>ExplorerRestletServlet<span class="nt"></servlet-name></span>
<span class="nt"><url-pattern></span>/service/*<span class="nt"></url-pattern></span>
<span class="nt"></servlet-mapping></span>
</code></pre></div></div>
<h3 id="32-复制diagram-viewer组件">3.2 复制Diagram Viewer组件</h3>
<p>在官方提供的Zip文件(可以从<a href="http://www.activiti.org/download.html">www.activiti.org/download.html</a>下载)中有一个<strong>wars</strong>目录,用压缩工具解压<strong>activiti-explorer.war</strong>文件,目录结构如下图:</p>
<p><img src="/files/2014/04/activiti-explorer-tree.jpg" alt="" />
<img src="/files/2014/04/diagram-viewer-tree.jpg" alt="" /></p>
<p>把<strong>diagram-viewer</strong>复制到项目的<strong>webapp</strong>目录(或者是WebRoot目录)下,在项目中需要跟踪的地方拼接访问<strong>diagram-viewer/index.html</strong>的URL即可,别忘记了刚刚介绍的两个重要参数。</p>
<blockquote>
<p>http://demo.kafeitu.me/kft-activiti-demo/diagram-viewer/index.html?processDefinitionId=leave-jpa:1:22&processInstanceId=27</p>
</blockquote>
<p>URL中有两个参数:</p>
<ul>
<li>processDefinitionId: 流程定义ID</li>
<li>processInstanceId: 流程实例ID</li>
</ul>
<hr />
<p>这是一个独立的页面,你可以直接打开它或者把它嵌入在一个对话框里面(kft-activiti-demo就是用的嵌入方式)。</p>咖啡兔https://henryyan.github.io首先这是一篇迟来的教程,因为从5.12版本(目前最新版本为5.15.1)开始就已经提供了Diagram Viewer这个流程图跟踪组件,不管如何总归有人需要用到,所以我觉得还是要和大家分享一下。在Activiti中集成JPA(解决动态表单生成的大量数据)2014-04-18T00:00:00+08:002014-04-18T00:00:00+08:00https://kafeitu.me/activiti/2014/04/18/activiti-with-jpa<h2 id="1-为何集成jpa">1. 为何集成JPA</h2>
<p>在《<a href="/activiti/2012/08/05/diff-activiti-workflow-forms.html">比较Activiti中三种不同的表单及其应用</a>》一文中介绍了不同表单的特点以及表现形式,相信这是每个初学者都会面临表单类型的选择。</p>
<p>如果选择了使用<strong>动态表单</strong>那么将面临一个比较“严峻”的问题——大数据量,我们知道动态表单的内容都保存在一张表中(<strong>ACT_HI_DETAIL</strong>),我们也清楚动态表单中每一个Field都会在该表中插入一条记录,假如一个流程共有20个字段,这个数据量大家可以计算一下,每天多少个流程实例,每个月、每年多少?</p>
<p>日积月累的大数据会影响系统的性能,尤其涉及到关联查询时影响更深,除了性能之外动态表单还有一个弊端那就是数据是以<strong>行</strong>的形式存储没有任何<strong>数据结构</strong>可言,流程运行中生成的数据很难被用于分析、查询,如何破解嘞?</p>
<h2 id="2-如何集成jpa">2. 如何集成JPA</h2>
<p>Activiti除了核心的Engine之外对企业现有的技术、平台、架构都有所支持,对于业务实体的持久化当然也会有所支持,那就是EJB的标准之一)——JPA,引擎把JPA的API引入到了内部,使用JPA功能的时候只需要把<strong>entityManagerFactory</strong>配置到引擎配置对象(参考:<a href="/activiti/2013/04/19/about-process-egine-and-configuration.html">谈谈Activiti的引擎与引擎配置对象</a>)即可。</p>
<p>参考用户手册的JPA章节,介绍了引擎配置对象中的几个jpa有关的属性,如下:</p>
<ul>
<li>jpaPersistenceUnitName: 使用持久化单元的名称(要确保该持久化单元在类路径下是可用的)。根据该规范,默认的路径是/META-INF/persistence.xml)。要么使用 jpaEntityManagerFactory 或者jpaPersistenceUnitName。</li>
<li>jpaEntityManagerFactory: 一个实现了javax.persistence.EntityManagerFactory的bean的引用。它将被用来加载实体并且刷新更新。要么使用jpaEntityManagerFactory 或者jpaPersistenceUnitName。</li>
<li>jpaHandleTransaction: 在被使用的EntityManager 实例上,该标记表示流程引擎是否需要开始和提交/回滚事物。当使用Java事物API(JTA)时,设置为false。</li>
<li>jpaCloseEntityManager: 该标记表示流程引擎是否应该关闭从 EntityManagerFactory获取的 EntityManager的实例。当EntityManager 是由容器管理的时候需要设置为false(例如 当使用并不是单一事物作用域的扩展持久化上下文的时候)。</li>
</ul>
<h3 id="21-配置持久化单元或者entitymanagerfactory">2.1 配置持久化单元或者EntityManagerFactory</h3>
<p>要在引擎中使用JPA需要提供EntityManagerFactory或者提供持久化单元名称(引擎会自动查找最终获取到EntityManagerFactory对象),在使用的时候可以根据自己的实际情况进行选择,在<strong>kft-activiti-demo</strong>中使用了<strong>jpaEntityManagerFactory</strong>属性注入EntityManagerFactory对象的方式。</p>
<h3 id="22-standalone模式的jpa配置">2.2 Standalone模式的JPA配置</h3>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt"><property</span> <span class="na">name=</span><span class="s">"jpaPersistenceUnitName"</span> <span class="na">value=</span><span class="s">"kft-jpa-pu"</span> <span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"jpaHandleTransaction"</span> <span class="na">value=</span><span class="s">"true"</span><span class="nt">></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"jpaCloseEntityManager"</span> <span class="na">value=</span><span class="s">"true"</span><span class="nt">></property></span>
</code></pre></div></div>
<h3 id="23-spring托管模式的jpa配置">2.3 Spring(托管)模式的JPA配置</h3>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt"><property</span> <span class="na">name=</span><span class="s">"jpaEntityManagerFactory"</span> <span class="na">ref=</span><span class="s">"entityManagerFactory"</span> <span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"jpaHandleTransaction"</span> <span class="na">value=</span><span class="s">"false"</span><span class="nt">></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"jpaCloseEntityManager"</span> <span class="na">value=</span><span class="s">"false"</span><span class="nt">></property></span>
</code></pre></div></div>
<h2 id="3-实例分析">3. 实例分析</h2>
<p>在最新版本(1.10)的<strong>kft-activiti-demo</strong>中添加了JPA演示,大家可以从<a href="https://github.com/henryyan/kft-activiti-demo">Github</a>上下载源码查看源码。</p>
<p><img src="/files/2014/04/leave-jpa.jpg" alt="请假流程-JPA版本" /></p>
<h3 id="31-相关说明">3.1 相关说明</h3>
<ul>
<li>流程定义文件:<strong>leave-jpa.bpmn</strong></li>
<li>实体文件:<strong>me.kafeitu.demo.activiti.entity.oa.LeaveJpaEntity</strong></li>
<li>实体管理器:<strong>me.kafeitu.demo.activiti.service.oa.leave.LeaveEntityManager</strong></li>
</ul>
<h3 id="32-创建实体">3.2 创建实体</h3>
<p>在流程定义文件中定义了一个流程的<strong>start</strong>类型监听器:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><extensionElements></span>
<span class="nt"><activiti:executionListener</span> <span class="na">event=</span><span class="s">"start"</span> <span class="na">expression=</span><span class="s">"${execution.setVariable('leave', leaveEntityManager.newLeave(execution))}"</span><span class="nt">></activiti:executionListener></span>
<span class="nt"></extensionElements></span>
</code></pre></div></div>
<p>这个监听器的触发的时候会执行一个表达式,调用名称为<strong>leaveEntityManager</strong>的Spring Bean对象的<strong>newLeave</strong>方法,并且把引擎的<strong>Execution</strong>对象传递过去,得到一个LeaveJpaEntity对象后设置到引擎的变量中(名称为<strong>leave</strong>)。</p>
<p>下面是LeaveEntityManager.java的代码:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Entity</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"LEAVE_JPA"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">LeaveJpaEntity</span> <span class="kd">implements</span> <span class="nc">Serializable</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">Long</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">processInstanceId</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">userId</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">Date</span> <span class="n">startTime</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">Date</span> <span class="n">endTime</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">Date</span> <span class="n">realityStartTime</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">Date</span> <span class="n">realityEndTime</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">Date</span> <span class="n">reportBackDate</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">Date</span> <span class="n">applyTime</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">leaveType</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">reason</span><span class="o">;</span>
<span class="cm">/**
* 部门领导是否同意
*/</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">deptLeaderApproved</span><span class="o">;</span>
<span class="cm">/**
* HR是否同意
*/</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">hrApproved</span><span class="o">;</span>
<span class="o">...</span>
<span class="o">}</span>
</code></pre></div></div>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">LeaveEntityManager</span> <span class="o">{</span>
<span class="nd">@PersistenceContext</span>
<span class="kd">private</span> <span class="nc">EntityManager</span> <span class="n">entityManager</span><span class="o">;</span>
<span class="cm">/* 把流程变量的值赋值给JPA实体对象并保存到数据库 */</span>
<span class="nd">@Transactional</span>
<span class="kd">public</span> <span class="nc">LeaveJpaEntity</span> <span class="nf">newLeave</span><span class="o">(</span><span class="nc">DelegateExecution</span> <span class="n">execution</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">LeaveJpaEntity</span> <span class="n">leave</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LeaveJpaEntity</span><span class="o">();</span>
<span class="n">leave</span><span class="o">.</span><span class="na">setProcessInstanceId</span><span class="o">(</span><span class="n">execution</span><span class="o">.</span><span class="na">getProcessInstanceId</span><span class="o">());</span>
<span class="n">leave</span><span class="o">.</span><span class="na">setUserId</span><span class="o">(</span><span class="n">execution</span><span class="o">.</span><span class="na">getVariable</span><span class="o">(</span><span class="s">"applyUserId"</span><span class="o">).</span><span class="na">toString</span><span class="o">());</span>
<span class="n">leave</span><span class="o">.</span><span class="na">setStartTime</span><span class="o">((</span><span class="nc">Date</span><span class="o">)</span> <span class="n">execution</span><span class="o">.</span><span class="na">getVariable</span><span class="o">(</span><span class="s">"startTime"</span><span class="o">));</span>
<span class="n">leave</span><span class="o">.</span><span class="na">setEndTime</span><span class="o">((</span><span class="nc">Date</span><span class="o">)</span> <span class="n">execution</span><span class="o">.</span><span class="na">getVariable</span><span class="o">(</span><span class="s">"endTime"</span><span class="o">));</span>
<span class="n">leave</span><span class="o">.</span><span class="na">setLeaveType</span><span class="o">(</span><span class="n">execution</span><span class="o">.</span><span class="na">getVariable</span><span class="o">(</span><span class="s">"leaveType"</span><span class="o">).</span><span class="na">toString</span><span class="o">());</span>
<span class="n">leave</span><span class="o">.</span><span class="na">setReason</span><span class="o">(</span><span class="n">execution</span><span class="o">.</span><span class="na">getVariable</span><span class="o">(</span><span class="s">"reason"</span><span class="o">).</span><span class="na">toString</span><span class="o">());</span>
<span class="n">leave</span><span class="o">.</span><span class="na">setApplyTime</span><span class="o">(</span><span class="k">new</span> <span class="nc">Date</span><span class="o">());</span>
<span class="n">entityManager</span><span class="o">.</span><span class="na">persist</span><span class="o">(</span><span class="n">leave</span><span class="o">);</span>
<span class="k">return</span> <span class="n">leave</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">LeaveJpaEntity</span> <span class="nf">getLeave</span><span class="o">(</span><span class="nc">Long</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">entityManager</span><span class="o">.</span><span class="na">find</span><span class="o">(</span><span class="nc">LeaveJpaEntity</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">id</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>当启动流程后查看表<strong>LEAVE_JPA</strong>中的数据与表单填写的一致。</p>
<h3 id="33-在流程中更改实体的值">3.3 在流程中更改实体的值</h3>
<p>当<strong>部门领导</strong>或者<strong>人事审批</strong>节点完成时需要把审批结果更新到LeaveJpaEntity属性中(即更新表LEAVE_JPA),所以在这两个任务上添加一个<strong>complete</strong>类型的监听器,如下所示:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><userTask</span> <span class="na">id=</span><span class="s">"deptLeaderAudit"</span> <span class="na">name=</span><span class="s">"部门领导审批"</span> <span class="na">activiti:candidateGroups=</span><span class="s">"deptLeader"</span><span class="nt">></span>
<span class="nt"><extensionElements></span>
<span class="nt"><activiti:taskListener</span> <span class="na">event=</span><span class="s">"complete"</span> <span class="na">expression=</span><span class="s">"${leave.setDeptLeaderApproved(deptLeaderApproved)}"</span><span class="nt">></activiti:taskListener></span>
<span class="nt"></extensionElements></span>
<span class="nt"></userTask></span>
<span class="nt"><userTask</span> <span class="na">id=</span><span class="s">"hrAudit"</span> <span class="na">name=</span><span class="s">"人事审批"</span> <span class="na">activiti:candidateGroups=</span><span class="s">"hr"</span><span class="nt">></span>
<span class="nt"><extensionElements></span>
<span class="nt"><activiti:taskListener</span> <span class="na">event=</span><span class="s">"complete"</span> <span class="na">expression=</span><span class="s">"${leave.setHrApproved(hrApproved)}"</span><span class="nt">></activiti:taskListener></span>
<span class="nt"></extensionElements></span>
<span class="nt"></userTask></span>
</code></pre></div></div>
<h3 id="34-流程结束后删除表单数据">3.4 流程结束后删除表单数据</h3>
<p>熟悉Activiti表的应该知道表单数据会保存在表<strong>ACT_HI_DETAIL</strong>中,特性是字段<strong>TYPE_</strong>字段的值为<strong>FormProperty</strong>,我们只要根据流程实例ID过滤删除记录就可以清理掉已经结束流程的表单数据。</p>
<p>在最新版本的Demo中(1.10版本)添加了一个类用来执行SQL:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ActivitiDao</span> <span class="o">{</span>
<span class="nd">@PersistenceContext</span>
<span class="kd">private</span> <span class="nc">EntityManager</span> <span class="n">entityManager</span><span class="o">;</span>
<span class="cm">/**
* 流程完成后清理detail表中的表单类型数据
* @param processInstanceId
* @return
*/</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">deleteFormPropertyByProcessInstanceId</span><span class="o">(</span><span class="nc">String</span> <span class="n">processInstanceId</span><span class="o">)</span> <span class="o">{</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">entityManager</span><span class="o">.</span><span class="na">createNativeQuery</span><span class="o">(</span><span class="s">"delete from act_hi_detail where proc_inst_id_ = ? and type_ = 'FormProperty' "</span><span class="o">)</span>
<span class="o">.</span><span class="na">setParameter</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">processInstanceId</span><span class="o">).</span><span class="na">executeUpdate</span><span class="o">();</span>
<span class="k">return</span> <span class="n">i</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>流程中定义了一个流程级别的结束监听器<strong>me.kafeitu.demo.activiti.service.oa.leave.LeaveProcessEndListener</strong></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span>
<span class="nd">@Transactional</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">LeaveProcessEndListener</span> <span class="kd">implements</span> <span class="nc">ExecutionListener</span> <span class="o">{</span>
<span class="kd">protected</span> <span class="nc">Logger</span> <span class="n">logger</span> <span class="o">=</span> <span class="nc">LoggerFactory</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="n">getClass</span><span class="o">());</span>
<span class="nd">@Autowired</span>
<span class="nc">ActivitiDao</span> <span class="n">activitiDao</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">notify</span><span class="o">(</span><span class="nc">DelegateExecution</span> <span class="n">execution</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">processInstanceId</span> <span class="o">=</span> <span class="n">execution</span><span class="o">.</span><span class="na">getProcessInstanceId</span><span class="o">();</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">activitiDao</span><span class="o">.</span><span class="na">deleteFormPropertyByProcessInstanceId</span><span class="o">(</span><span class="n">processInstanceId</span><span class="o">);</span>
<span class="n">logger</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"清理了 {} 条历史表单数据"</span><span class="o">,</span> <span class="n">i</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="35-已知问题未解决">3.5 已知问题(未解决)</h3>
<p><img src="/files/2014/04/jpa-delete-detail-problem.jpg" alt="由于引擎的Bug导致数据不能完整删除" /></p>
<p>图中的三条数据因为是在销假任务完成后设置的,不知道是不是引擎的Bug导致插入这三个表单属性比调用流程结束监听器还晚(从引擎的日志中可以分析出来)导致这三条记录不能被删除,因为在删除的时候这三条数据还没有插入到数据库。</p>
<p>这个问题后面会继续跟踪,解决了会在这里更新!!!</p>咖啡兔https://henryyan.github.io1. 为何集成JPA剖析Activiti的Activity2014-01-23T00:00:00+08:002014-01-23T00:00:00+08:00https://kafeitu.me/activiti/2014/01/23/analyse-activity<h2 id="1-窥视activity内部">1. 窥视Activity内部</h2>
<p>在设计流程时每一个组件在Activiti中都可以称之为——Activity,部署流程时引擎把XML文件保存到数据库,当启动流程、完成任务时会从数据库读取XML并转换为Java对象,很多人想在处理任务时获取任务的一些配置,例如某个任务配置了哪些监听器或者条件Flow配置了什么条件表达式。</p>
<h2 id="2-代码">2. 代码</h2>
<p>下面的代码做了简单的演示,根据不同的Activity类型输出属性,读者可以继续探索其他不同类型Activity的属性,最终可以获取到所有Activity的属性。</p>
<script src="https://gist.github.com/8578924.js"></script>
<h2 id="3-输出结果示例">3. 输出结果示例</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{taskDefinition=org.activiti.engine.impl.task.TaskDefinition@19c6e4d1, default=null, name=部门领导审批, documentation=null, type=userTask}
{conditionText=${!deptLeaderPass}, condition=org.activiti.engine.impl.el.UelExpressionCondition@50d8628f, name=不同意, documentation=null}
{conditionText=${deptLeaderPass}, condition=org.activiti.engine.impl.el.UelExpressionCondition@2e2ec3c0, name=同意, documentation=null}
{taskDefinition=org.activiti.engine.impl.task.TaskDefinition@3589f0, default=null, name=调整申请, documentation=null, type=userTask}
{taskDefinition=org.activiti.engine.impl.task.TaskDefinition@3af2ebab, default=null, name=人事审批, documentation=null, type=userTask}
{conditionText=${hrPass}, condition=org.activiti.engine.impl.el.UelExpressionCondition@224e45c9, name=同意, documentation=null}
{conditionText=${!hrPass}, condition=org.activiti.engine.impl.el.UelExpressionCondition@40c7a0b7, name=不同意, documentation=null}
{taskDefinition=org.activiti.engine.impl.task.TaskDefinition@72086f9a, default=null, name=销假, documentation=null, type=userTask}
{conditionText=${reApply}, condition=org.activiti.engine.impl.el.UelExpressionCondition@7d721f3, name=重新申请, documentation=null}
{conditionText=${!reApply}, condition=org.activiti.engine.impl.el.UelExpressionCondition@3cf5dc8a, name=结束流程, documentation=null}
{name=Start, documentation=null, type=startEvent}
{name=End, documentation=null, type=endEvent}
</code></pre></div></div>咖啡兔https://henryyan.github.io1. 窥视Activity内部在Activiti中使用UUID作为主键生成策略2013-08-30T00:00:00+08:002013-08-30T00:00:00+08:00https://kafeitu.me/activiti/2013/08/30/using-uuid-as-primary-key-in-activiti<h2 id="1-默认的主键生成策略">1. 默认的主键生成策略</h2>
<p>了解过Activiit表中数据的同学可能知道记录的主键ID是用自增的生成策略,这样的生成策略有两个优点:</p>
<ul>
<li>有顺序:所有引擎的表在插入新记录时全部使用同一个ID生成器</li>
<li>便于记忆:因为是自增的所以有一定的顺序,便于记忆;例如业务人员让管理员删除一条数据(ID为5位左右的长度),管理员只要看一眼就可以记住</li>
</ul>
<p>当然也有<strong>缺点</strong>:</p>
<ul>
<li>随着时间的推移或者数据量非常大自增的ID生成策略的“便于记忆”优势也就不存在了,因为ID的位数会逐步增加(举个例子,我们公司做的一个小系统,用户量在30人左右,ID的长度已经到了7位数)</li>
<li>并发量高时会<strong>可能</strong>导致主键冲突</li>
</ul>
<p>在引擎初始化的时候会注册ID生成器,看过源码的同学还可能知道有一个类:<strong>org.activiti.engine.impl.db.DbIdGenerator</strong>,这个类实现了一个接口:<strong>org.activiti.engine.impl.cfg.IdGenerator</strong>:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">IdGenerator</span> <span class="o">{</span>
<span class="nc">String</span> <span class="nf">getNextId</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>该接口仅有一个方法,返回一个String类型的字符串,有兴趣的同学可以去看看引擎默认的生成器源码,接下来介绍如何更改引擎的主键生成器。</p>
<h2 id="2-更改默认的主键生成器">2. 更改默认的主键生成器</h2>
<p>UUID是全球唯一的主键生成器,也是除自增策略之外最常用的一种,很幸运:引擎内置了UUID生成器实现。</p>
<p>要更改引擎默认的主键生成器很简单,只需要在配置引擎时覆盖一个属性即可,代码如下:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><bean</span> <span class="na">id=</span><span class="s">"uuidGenerator"</span> <span class="na">class=</span><span class="s">"org.activiti.engine.impl.persistence.StrongUuidGenerator"</span> <span class="nt">/></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"processEngineConfiguration"</span> <span class="na">class=</span><span class="s">"org.activiti.spring.SpringProcessEngineConfiguration"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"idGenerator"</span> <span class="na">ref=</span><span class="s">"uuidGenerator"</span> <span class="nt">/></span>
<span class="nt"></bean></span>
</code></pre></div></div>
<p>ID为“uuidGenerator”的bean对象就是引擎内部提供的UUID生成器,把Bean对象注册好以后覆盖引擎的“idGenerator”属性即可;再次启动系统后创建的新数据都会用UUID生成策略。</p>
<h3 id="21-添加依赖">2.1 添加依赖</h3>
<p>引擎提供的UUID生成器依赖fastxml的一个模块,需要在pom.xml(Maven工程)中添加如下依赖:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.fasterxml.uuid<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>java-uuid-generator<span class="nt"></artifactId></span>
<span class="nt"><version></span>3.1.3<span class="nt"></version></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p><img src="/files/2013/08/uuid-example.png" alt="用UUID生成策略产生的ID" /></p>
<h2 id="3-自定义id生成器">3. 自定义ID生成器</h2>
<ul>
<li>Step 1:创建一个类实现接口<strong>org.activiti.engine.impl.cfg.IdGenerator</strong></li>
<li>Step 2:参考本文第2部分 ^_^</li>
</ul>咖啡兔https://henryyan.github.io1. 默认的主键生成策略Activiti 5.13修复版本5.13.1-kft发布2013-07-17T00:00:00+08:002013-07-17T00:00:00+08:00https://kafeitu.me/activiti/2013/07/17/activiti-5.13.1-kft-released<h2 id="1-bug说明">1. Bug说明</h2>
<p>这个Bug是在5.13发布几天后发现的,关于Bug的描述请异步JIRA:<a href="https://jira.codehaus.org/browse/ACT-1731">ACT-1731</a></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">ProcessInstance</span> <span class="n">processInstanceNew</span> <span class="o">=</span> <span class="n">runtimeService</span><span class="o">.</span><span class="na">createProcessInstanceQuery</span><span class="o">()</span>
<span class="o">.</span><span class="na">includeProcessVariables</span><span class="o">()</span>
<span class="o">.</span><span class="na">processInstanceId</span><span class="o">(</span><span class="n">processInstance</span><span class="o">.</span><span class="na">getProcessInstanceId</span><span class="o">()).</span><span class="na">singleResult</span><span class="o">();</span>
</code></pre></div></div>
<p>第二行代码的<strong>includeProcessVariables()</strong>方法是<strong>5.13</strong>版本新增加的,可以在查询流程实例时把相关变量也一同查询出来,在Bug修复之前得到的结果为空,目前的<strong>5.14-SNAPSHOT</strong>版本已经修复此Bug。</p>
<hr />
<p>Bug的测试类:<a href="https://github.com/henryyan/activiti-unit-test-template/blob/1e088e93241ccd06ae0807fc3f18a94d12a72a52/src/test/java/org/activiti/v513/Act1731Test.java">Act1731Test.java</a></p>
<p>Commits:</p>
<ol>
<li><a href="https://github.com/Activiti/Activiti/commit/6934243a916bed7010c26a006e06724fd5b5ffe7">6934243a916bed7010c26a006e06724fd5b5ffe7</a></li>
<li><a href="https://github.com/Activiti/Activiti/commit/136859b6e4100d80c34bfc5c43964c8c8e4362de">136859b6e4100d80c34bfc5c43964c8c8e4362de</a></li>
<li><a href="https://github.com/Activiti/Activiti/commit/0b7e5fbcb6554e2cf193ecfe3c0777628ca99726">0b7e5fbcb6554e2cf193ecfe3c0777628ca99726</a></li>
</ol>
<h2 id="2-5131-kft修复版本发布">2. 5.13.1-kft修复版本发布</h2>
<p>基于<strong>5.13</strong>版本合并了上面三个commit重新打包并发布到Maven私服(含源码)。</p>
<p>使用Maven的请把私服加入到repositories中:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><repository></span>
<span class="nt"><id></span>kafeitu<span class="nt"></id></span>
<span class="nt"><url></span>http://maven.kafeitu.me/nexus/content/groups/public<span class="nt"></url></span>
<span class="nt"></repository></span>
</code></pre></div></div>
<p>依赖定义:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.activiti<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>activiti-engine<span class="nt"></artifactId></span>
<span class="nt"><version></span>5.13.1-kft<span class="nt"></version></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<hr />
<p>非Maven项目请直接下载Jar包替换现有的<strong>activiti-engine-5.13.jar</strong>,下载地址:
<a href="http://maven.kafeitu.me/nexus/content/repositories/public/org/activiti/activiti-engine/5.13.1-kft/">http://maven.kafeitu.me/nexus/content/repositories/public/org/activiti/activiti-engine/5.13.1-kft/</a></p>咖啡兔https://henryyan.github.io1. Bug说明一个基于Maven的配置文件管理成功案例2013-06-29T00:00:00+08:002013-06-29T00:00:00+08:00https://kafeitu.me/solution/2013/06/29/a-successful-cofiguration-file-management-solution<h2 id="引言">引言</h2>
<p>配置文件几乎是每个项目中不可或缺的文件类型之一,常用的配置文件类型有xml、properties等,配置文件的好处不言而喻,利用配置文件可以灵活地设置软件的运行参数,并且可以在更改配置文件后在不重启应用的情况下即时生效。</p>
<p>写这篇文章的原因是最近在一个项目中引入了我对配置文件的管理方式,我觉得有必要与大家分享,希望能抛砖引玉。</p>
<h2 id="1-我所知道的配置文件管理方式">1. 我所知道的配置文件管理方式</h2>
<p>下面大概列出了几类对配置文件的管理方式,请对号入座^_^</p>
<h3 id="11-配置文件是什么">1.1 配置文件是什么?</h3>
<p>除非应用不需要配置文件(例如Hello World),否则请无视,continue;</p>
<h3 id="12-数据库方式">1.2 数据库方式</h3>
<p>把配置文件保存在数据库,看起来这种方式很不错,配置不会丢失方便管理,其实问题很大;举个简单的例子,在A模块需要调用B模块的服务,访问B模块的URL配置在数据库,首先需要在A中读取B模块的URL,然后再发送请求,问题紧随而来,如果这个功能只有一个开发人员负责还好,假如多个人都要调试那就麻烦了,因为配置保存在数据库(整个Team使用同一个数据库测试),每个开发人员的B模块的访问端口(Web容器的端口)又不相同或者应用的ContextPath不同,要想顺利调试就要更改数据库的B模块URL值,这样多个开发人员就发生了冲突;问题很严重!</p>
<h3 id="13-xml方式">1.3 XML方式</h3>
<p>对于使用SSH或者其他的框架、插件的应用在src/main/resources下面肯定有不少的xml配置文件,今天的主题是应用级的配置管理,所以暂且抛开框架必须的XML配置文件,先来看看下面的XML配置文件内容。</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><SystemConfig></span>
<span class="nt"><param</span> <span class="na">code=</span><span class="s">"SysName"</span> <span class="na">name=</span><span class="s">"系统名称"</span> <span class="na">type=</span><span class="s">"String"</span> <span class="na">value=</span><span class="s">"XXX后台系统"</span><span class="nt">/></span>
<span class="nt"><param</span> <span class="na">code=</span><span class="s">"SysVersion"</span> <span class="na">name=</span><span class="s">"系统版本"</span> <span class="na">type=</span><span class="s">"String"</span> <span class="na">value=</span><span class="s">"1.0"</span><span class="nt">/></span>
<span class="nt"></SystemConfig></span>
</code></pre></div></div>
<p>这种方式在之前很受欢迎,也是系统属性的主要配置方式,不过使用起来总归不太简洁、灵活(不要和我争论XML与Properties)。</p>
<h3 id="14-属性文件方式">1.4 属性文件方式</h3>
<p>下面是kft-activiti-demo中<strong>application.properties</strong>文件的一部分:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sql.type<span class="o">=</span>h2
jdbc.driver<span class="o">=</span>org.h2.Driver
jdbc.url<span class="o">=</span>jdbc:h2:file:~/kft-activiti-demo<span class="p">;</span><span class="nv">AUTO_SERVER</span><span class="o">=</span>TRUE
jdbc.username<span class="o">=</span>sa
jdbc.password<span class="o">=</span>
system.version<span class="o">=</span><span class="k">${</span><span class="nv">project</span><span class="p">.version</span><span class="k">}</span>
activiti.version<span class="o">=</span><span class="k">${</span><span class="nv">activiti</span><span class="p">.version</span><span class="k">}</span>
export.diagram.path<span class="o">=</span><span class="k">${</span><span class="nv">export</span><span class="p">.diagram.path</span><span class="k">}</span>
diagram.http.url<span class="o">=</span><span class="k">${</span><span class="nv">diagram</span><span class="p">.http.url</span><span class="k">}</span>
</code></pre></div></div>
<p>相比而言属性文件方式比XML方式要简洁一些,不用严格的XML标签包装即可设置属性的名称,对于多级配置可以用点号(.)分割的方式。</p>
<h2 id="2-分析我的管理方式">2. 分析我的管理方式</h2>
<p>我在几个项目中所采用的是第4中方式,也就是<strong>属性文件</strong>的方式来管理应用的配置,读取属性文件可以利用Java的Properties对象,或者借助Spring的<a href="http://stackoverflow.com/questions/7219097/spring-utilproperties-injection-via-annotations-into-a-bean">properties</a>模块。</p>
<h3 id="21-利用maven资源过滤设置属性值">2.1 利用Maven资源过滤设置属性值</h3>
<p>简单来说就是利用Maven的Resource Filter功能,pom.xml中的build配置如下:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><build></span>
<span class="nt"><resources></span>
<span class="nt"><resource></span>
<span class="nt"><directory></span>src/main/resources<span class="nt"></directory></span>
<span class="nt"><filtering></span>true<span class="nt"></filtering></span>
<span class="nt"></resource></span>
<span class="nt"></resources></span>
<span class="nt"></build></span>
</code></pre></div></div>
<p>这样在编译时<strong>src/main/resources</strong>目录下面中文件()只要有<strong>${foo}</strong>占位符就可以自动替换成实际的值了,例如1.4节中属性<strong>system.version</strong>使用的是一个占位符而非实际的值,${project.version}表示pom.xml文件中的<strong>project</strong>标签的<strong>version</strong>。</p>
<p>属性<strong>export.diagram.path</strong>、<strong>diagram.http.url</strong>也是使用了占位符的方式,很明显这两个属性的值不是pom.xml文件可以提供,所以如果要动态设置值可以通过在pom.xml中添加<properties>的方式,或者把<properties>放在profile中,如此可以根据不同的环境(开发、UAT、生产)动态设置不同的值。</properties></properties></p>
<hr />
<p>当然也可以在编译时通过给JVM传递参数的方式,例如:</p>
<blockquote>
<p>mvm clean compile -Dexport.diagram.path=/var/kft/diagrams</p>
</blockquote>
<p>编译完成后查看target/classes/application.properties文件的内容,属性<strong>export.diagram.path</strong>的值被正确替换了:</p>
<blockquote>
<p>export.diagram.path=/var/kft/diagrams</p>
</blockquote>
<h3 id="22-读取配置文件">2.2 读取配置文件</h3>
<p>读取配置文件可以直接里面Java的Properties读取,下面的代码简单实现了读取属性集合:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Properties</span> <span class="n">props</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Properties</span><span class="o">();</span>
<span class="nc">ResourceLoader</span> <span class="n">resourceLoader</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DefaultResourceLoader</span><span class="o">();</span>
<span class="nc">Resource</span> <span class="n">resource</span> <span class="o">=</span> <span class="n">resourceLoader</span><span class="o">.</span><span class="na">getResource</span><span class="o">(</span><span class="n">location</span><span class="o">);</span>
<span class="nc">InputStream</span> <span class="n">is</span> <span class="o">=</span> <span class="n">resource</span><span class="o">.</span><span class="na">getInputStream</span><span class="o">();</span>
<span class="n">propertiesPersister</span><span class="o">.</span><span class="na">load</span><span class="o">(</span><span class="n">props</span><span class="o">,</span> <span class="k">new</span> <span class="nc">InputStreamReader</span><span class="o">(</span><span class="n">is</span><span class="o">,</span> <span class="s">"UTF-8"</span><span class="o">));</span>
</code></pre></div></div>
<p>如果在把读取的属性集合保存在一个静态Map对象中就可以在任何可以执行Java代码的地方获取应用的属性了,工具类PropertiesFileUtil简单实现了属性缓存功能:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">PropertyFileUtil</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">Properties</span> <span class="n">properties</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">loadProperties</span><span class="o">(</span><span class="nc">String</span> <span class="n">location</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">ResourceLoader</span> <span class="n">resourceLoader</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DefaultResourceLoader</span><span class="o">();</span>
<span class="nc">Resource</span> <span class="n">resource</span> <span class="o">=</span> <span class="n">resourceLoader</span><span class="o">.</span><span class="na">getResource</span><span class="o">(</span><span class="n">location</span><span class="o">);</span>
<span class="nc">InputStream</span> <span class="n">is</span> <span class="o">=</span> <span class="n">resource</span><span class="o">.</span><span class="na">getInputStream</span><span class="o">();</span>
<span class="nc">PropertiesPersister</span> <span class="n">propertiesPersister</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DefaultPropertiesPersister</span><span class="o">();</span>
<span class="n">propertiesPersister</span><span class="o">.</span><span class="na">load</span><span class="o">(</span><span class="n">properties</span><span class="o">,</span> <span class="k">new</span> <span class="nc">InputStreamReader</span><span class="o">(</span><span class="n">is</span><span class="o">,</span> <span class="s">"UTF-8"</span><span class="o">));</span>
<span class="o">}</span>
<span class="c1">// 获取属性值</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">get</span><span class="o">(</span><span class="nc">String</span> <span class="n">key</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">propertyValue</span> <span class="o">=</span> <span class="n">properties</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="n">key</span><span class="o">);</span>
<span class="k">return</span> <span class="n">propertyValue</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>先<em>抛出</em>一个问题:属性文件中定义了属性的值和平台有关,团队中的成员使用的平台有Window、Linux、Mac,对于这样的情况目前只能修改<strong>application.properties</strong>文件,但是不能把更改提交到SCM上否则会影响其他人的使用……目前没有好办法,稍后给出解决办法。</p>
<h3 id="22-多配置文件重载功能">2.2 多配置文件重载功能</h3>
<p>2.1节中简单的PropertyFileUtil工具只能做到读取一个配置文件,这对于一些多余一个子系统来说就不太能满足需求了。</p>
<p>对于一个项目拆分的多个子系统来说如果每个子系统都配置一套属性集合最后就会出现一个问题——配置重复,修改起来也会比较麻烦;解决的办法很简单——把公共的属性抽取出来单独保存在一个公共的属性文件中,我喜欢命名为:<strong>application.common.properties</strong>。</p>
<p>这个公共的属性文件可以用来保存一些数据库连接、公共目录、子模块的URL等信息,如此一来子系统中的<strong>application.properties</strong>中只需要设置当前子系统需要的属性即可,在读取属性文件时可以依次读取多个(<strong>先</strong>读取application.common.properties,<strong>再</strong>读取application.properties),这样最终获取的属性集合就是两个文件的并集。</p>
<p>在刚刚的PropertyFileUtil类中添加一个<strong>loadProperties</strong>方法,接收的参数是一个可变数组,循环读取属性文件。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* 载入多个properties文件, 相同的属性在最后载入的文件中的值将会覆盖之前的载入.
* 文件路径使用Spring Resource格式, 文件编码使用UTF-8.
*
* @see org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">Properties</span> <span class="nf">loadProperties</span><span class="o">(</span><span class="nc">String</span><span class="o">...</span> <span class="n">resourcesPaths</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="nc">Properties</span> <span class="n">props</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Properties</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="n">location</span> <span class="o">:</span> <span class="n">resourcesPaths</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Resource</span> <span class="n">resource</span> <span class="o">=</span> <span class="n">resourceLoader</span><span class="o">.</span><span class="na">getResource</span><span class="o">(</span><span class="n">location</span><span class="o">);</span>
<span class="nc">InputStream</span> <span class="n">is</span> <span class="o">=</span> <span class="n">resource</span><span class="o">.</span><span class="na">getInputStream</span><span class="o">();</span>
<span class="n">propertiesPersister</span><span class="o">.</span><span class="na">load</span><span class="o">(</span><span class="n">props</span><span class="o">,</span> <span class="k">new</span> <span class="nc">InputStreamReader</span><span class="o">(</span><span class="n">is</span><span class="o">,</span> <span class="no">DEFAULT_ENCODING</span><span class="o">));</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">props</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>有了这个方法我们可以这样调用这个工具类:</p>
<blockquote>
<p>PropertyFileUtil.loadProperties(“application.common.properties”, “application.properties”);</p>
</blockquote>
<p>在<strong>2.1</strong>中抛出的问题也迎刃而解了,把配置文件再根据类型划分:</p>
<ul>
<li>application.common.properties 公共属性</li>
<li>application.properties 各个子系统的属性</li>
<li>application.<strong>local</strong>.properties 本地属性(用于开发时)</li>
</ul>
<blockquote>
<p>请注意:不要把application.local.properties纳入到版本控制(SCM)中,这个文件只能在本地开发环境出现!!!</p>
</blockquote>
<p>最后读取的顺序应该这样写:</p>
<blockquote>
<p>PropertyFileUtil.loadProperties(“application.common.properties”, “application.properties”, “application.local.properties”);</p>
</blockquote>
<h3 id="23-根据环境不同选择不同的配置文件">2.3 根据环境不同选择不同的配置文件</h3>
<p><strong>2.2</strong>的多文件重载解决了<strong>2.1</strong>中的问题,但是现在又遇到一个新问题:如何根据不同的环境读取不同的属性文件。</p>
<ul>
<li><strong>开发时</strong>最后读取<strong>application.local.properties</strong></li>
<li><strong>测试时</strong>最后读取<strong>application.test.properties</strong></li>
<li><strong>生产环境</strong>最后读取<strong>/etc/foo/application.properties</strong></li>
</ul>
<p>这么做的目的在于每一个环境的配置都不相同,第一步读取公共配置,第二步读取子系统的属性,最后读取不同环境使用的特殊配置,这样才能做到最完美、最灵活。</p>
<p>既然属性的值可以通过国占位符的方式替换,我们也可以顺藤摸瓜把读取文件的顺序也管理起来,所以又引入了一个属性文件:<strong>application-file.properties</strong>;它的配置如下:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">A</span><span class="o">=</span>application.common.properties
<span class="nv">B</span><span class="o">=</span>application.properties
<span class="nv">C</span><span class="o">=</span><span class="k">${</span><span class="nv">env</span><span class="p">.prop.application.file</span><span class="k">}</span>
</code></pre></div></div>
<p>占位符<strong>env.prop.application.file</strong>的值可以动态指定,可以利用Maven的Profile功能实现,例如针对开发环境配置一个ID为<strong>dev</strong>的profile并设置默认激活状态;对于部署到测试、生产环境可以在打包时添加-Ptest或者-Pproduct参数使用不同的Profile;关键在于每一个profile中配置的env.prop.application.file值不同,例如:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><profile></span>
<span class="nt"><id></span>dev<span class="nt"></id></span>
<span class="nt"><properties></span>
<span class="nt"><dev.mode></span>true<span class="nt"></dev.mode></span>
<span class="nt"><env.spring.application.file></span>
classpath*:/application.local.properties
<span class="nt"></env.spring.application.file></span>
<span class="nt"></properties></span>
<span class="nt"></profile></span>
<span class="nt"><profile></span>
<span class="nt"><id></span>product<span class="nt"></id></span>
<span class="nt"><properties></span>
<span class="nt"><dev.mode></span>true<span class="nt"></dev.mode></span>
<span class="nt"><env.spring.application.file></span>
file:/etc/foo/application.properties
<span class="nt"></env.spring.application.file></span>
<span class="nt"></properties></span>
<span class="nt"></profile></span>
<span class="nt"><activeProfiles></span>
<span class="nt"><activeProfile></span>dev<span class="nt"></activeProfile></span>
<span class="nt"></activeProfiles></span>
</code></pre></div></div>
<p>而对于生产环境来说可以把<strong>env.spring.application.file</strong>改为<strong>/etc/foo/application.properties</strong>。</p>
<hr />
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o"><</span><span class="n">xmp</span><span class="o">></span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PropertyFileUtil</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">Logger</span> <span class="n">logger</span> <span class="o">=</span> <span class="nc">LoggerFactory</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="nc">PropertyFileUtil</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">Properties</span> <span class="n">properties</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">PropertiesPersister</span> <span class="n">propertiesPersister</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DefaultPropertiesPersister</span><span class="o">();</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">ResourceLoader</span> <span class="n">resourceLoader</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DefaultResourceLoader</span><span class="o">();</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">DEFAULT_ENCODING</span> <span class="o">=</span> <span class="s">"UTF-8"</span><span class="o">;</span>
<span class="cm">/**
* 初始化读取配置文件,读取的文件列表位于classpath下面的application-files.properties<br/>
*
* 多个配置文件会用最后面的覆盖相同属性值
*
* @throws IOException 读取属性文件时
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">init</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">fileNames</span> <span class="o">=</span> <span class="s">"application-files.properties"</span><span class="o">;</span>
<span class="n">innerInit</span><span class="o">(</span><span class="n">fileNames</span><span class="o">);</span>
<span class="o">}</span>
<span class="cm">/**
* 初始化读取配置文件,读取的文件列表位于classpath下面的application-[type]-files.properties<br/>
*
* 多个配置文件会用最后面的覆盖相同属性值
*
* @param type 配置文件类型,application-[type]-files.properties
*
* @throws IOException 读取属性文件时
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">init</span><span class="o">(</span><span class="nc">String</span> <span class="n">type</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">fileNames</span> <span class="o">=</span> <span class="s">"application-"</span> <span class="o">+</span> <span class="n">type</span> <span class="o">+</span> <span class="s">"-files.properties"</span><span class="o">;</span>
<span class="n">innerInit</span><span class="o">(</span><span class="n">fileNames</span><span class="o">);</span>
<span class="o">}</span>
<span class="cm">/**
* 内部处理
* @param fileNames
* @throws IOException
*/</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">innerInit</span><span class="o">(</span><span class="nc">String</span> <span class="n">fileNames</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="nc">ClassLoader</span> <span class="n">loader</span> <span class="o">=</span> <span class="nc">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">().</span><span class="na">getContextClassLoader</span><span class="o">();</span>
<span class="nc">InputStream</span> <span class="n">resourceAsStream</span> <span class="o">=</span> <span class="n">loader</span><span class="o">.</span><span class="na">getResourceAsStream</span><span class="o">(</span><span class="n">fileNames</span><span class="o">);</span>
<span class="c1">// 默认的Properties实现使用HashMap算法,为了保持原有顺序使用有序Map</span>
<span class="nc">Properties</span> <span class="n">files</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LinkedProperties</span><span class="o">();</span>
<span class="n">files</span><span class="o">.</span><span class="na">load</span><span class="o">(</span><span class="n">resourceAsStream</span><span class="o">);</span>
<span class="nc">Set</span><span class="o"><</span><span class="nc">Object</span><span class="o">></span> <span class="n">fileKeySet</span> <span class="o">=</span> <span class="n">files</span><span class="o">.</span><span class="na">keySet</span><span class="o">();</span>
<span class="nc">String</span><span class="o">[]</span> <span class="n">propFiles</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">[</span><span class="n">fileKeySet</span><span class="o">.</span><span class="na">size</span><span class="o">()];</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Object</span><span class="o">></span> <span class="n">fileList</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><</span><span class="nc">Object</span><span class="o">>();</span>
<span class="n">fileList</span><span class="o">.</span><span class="na">addAll</span><span class="o">(</span><span class="n">files</span><span class="o">.</span><span class="na">keySet</span><span class="o">());</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">propFiles</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">fileKey</span> <span class="o">=</span> <span class="n">fileList</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">i</span><span class="o">).</span><span class="na">toString</span><span class="o">();</span>
<span class="n">propFiles</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">files</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="n">fileKey</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">logger</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"读取属性文件:{}"</span><span class="o">,</span> <span class="nc">ArrayUtils</span><span class="o">.</span><span class="na">toString</span><span class="o">(</span><span class="n">propFiles</span><span class="o">));;</span>
<span class="n">properties</span> <span class="o">=</span> <span class="n">loadProperties</span><span class="o">(</span><span class="n">propFiles</span><span class="o">);</span>
<span class="nc">Set</span><span class="o"><</span><span class="nc">Object</span><span class="o">></span> <span class="n">keySet</span> <span class="o">=</span> <span class="n">properties</span><span class="o">.</span><span class="na">keySet</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">Object</span> <span class="n">key</span> <span class="o">:</span> <span class="n">keySet</span><span class="o">)</span> <span class="o">{</span>
<span class="n">logger</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"property: {}, value: {}"</span><span class="o">,</span> <span class="n">key</span><span class="o">,</span> <span class="n">properties</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="n">key</span><span class="o">.</span><span class="na">toString</span><span class="o">()));</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o"></</span><span class="n">xmp</span><span class="o">></span>
</code></pre></div></div>
<blockquote>
<p>默认的Properties类使用的是Hash算法故无序,为了保持多个配置文件的读取顺序与约定的一致
所以需要一个自定义的有序Properties实现,参加:<a href="https://github.com/henryyan/kft-activiti-demo/blob/master/src/main/java/me/kafeitu/demo/activiti/util/LinkedProperties.java#">LinkedProperties.java</a></p>
</blockquote>
<h3 id="24-启动载入与动态重载">2.4 启动载入与动态重载</h3>
<p>为了能让其他类读取到属性需要有一个地方统一管理属性的读取、重载,我们可以创建一个Servlet来处理这件事情,在Servlet的<strong>init()</strong>方法中读取属性(调用PropertiesFileUtil.init()方法),可以根据请求参数的<strong>action</strong>值的不同做出不同的处理。</p>
<p>我们把这个Servlet命名为<strong>PropertiesServlet</strong>,映射路径为:/properties-servlet。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">java.io.IOException</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Set</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">javax.servlet.Servlet</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">javax.servlet.ServletConfig</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">javax.servlet.ServletContext</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">javax.servlet.ServletException</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">javax.servlet.http.HttpServlet</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">javax.servlet.http.HttpServletRequest</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">javax.servlet.http.HttpServletResponse</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">me.kafeitu.demo.activiti.util.PropertyFileUtil</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.apache.commons.lang3.StringUtils</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.slf4j.Logger</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.slf4j.LoggerFactory</span><span class="o">;</span>
<span class="cm">/**
* classpath下面的属性配置文件读取初始化类
*/</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PropertiesServlet</span> <span class="kd">extends</span> <span class="nc">HttpServlet</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">long</span> <span class="n">serialVersionUID</span> <span class="o">=</span> <span class="mi">1L</span><span class="o">;</span>
<span class="kd">protected</span> <span class="nc">Logger</span> <span class="n">logger</span> <span class="o">=</span> <span class="nc">LoggerFactory</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="n">getClass</span><span class="o">());</span>
<span class="cm">/**
* @see Servlet#init(ServletConfig)
*/</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">init</span><span class="o">(</span><span class="nc">ServletConfig</span> <span class="n">config</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">ServletException</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">PropertyFileUtil</span><span class="o">.</span><span class="na">init</span><span class="o">();</span>
<span class="nc">ServletContext</span> <span class="n">servletContext</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="na">getServletContext</span><span class="o">();</span>
<span class="n">setParameterToServerContext</span><span class="o">(</span><span class="n">servletContext</span><span class="o">);;</span>
<span class="n">logger</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"++++ 初始化[classpath下面的属性配置文件]完成 ++++"</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">logger</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"初始化classpath下的属性文件失败"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">doGet</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">req</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">resp</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">ServletException</span><span class="o">,</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="n">doPost</span><span class="o">(</span><span class="n">req</span><span class="o">,</span> <span class="n">resp</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">doPost</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">req</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">resp</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">ServletException</span><span class="o">,</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">action</span> <span class="o">=</span> <span class="nc">StringUtils</span><span class="o">.</span><span class="na">defaultString</span><span class="o">(</span><span class="n">req</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"action"</span><span class="o">));</span>
<span class="n">resp</span><span class="o">.</span><span class="na">setContentType</span><span class="o">(</span><span class="s">"text/plain;charset=UTF-8"</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="s">"reload"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">action</span><span class="o">))</span> <span class="o">{</span> <span class="c1">// 重载</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">PropertyFileUtil</span><span class="o">.</span><span class="na">init</span><span class="o">();</span>
<span class="n">setParameterToServerContext</span><span class="o">(</span><span class="n">req</span><span class="o">.</span><span class="na">getSession</span><span class="o">().</span><span class="na">getServletContext</span><span class="o">());</span>
<span class="n">logger</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"++++ 重新初始化[classpath下面的属性配置文件]完成 ++++,{IP={}}"</span><span class="o">,</span> <span class="n">req</span><span class="o">.</span><span class="na">getRemoteAddr</span><span class="o">());</span>
<span class="n">resp</span><span class="o">.</span><span class="na">getWriter</span><span class="o">().</span><span class="na">print</span><span class="o">(</span><span class="s">"<b>属性文件重载成功!</b><br/>"</span><span class="o">);</span>
<span class="n">writeProperties</span><span class="o">(</span><span class="n">resp</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">logger</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"重新初始化classpath下的属性文件失败"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="s">"getprop"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">action</span><span class="o">))</span> <span class="o">{</span> <span class="c1">// 获取属性</span>
<span class="nc">String</span> <span class="n">key</span> <span class="o">=</span> <span class="nc">StringUtils</span><span class="o">.</span><span class="na">defaultString</span><span class="o">(</span><span class="n">req</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"key"</span><span class="o">));</span>
<span class="n">resp</span><span class="o">.</span><span class="na">getWriter</span><span class="o">().</span><span class="na">print</span><span class="o">(</span><span class="n">key</span> <span class="o">+</span> <span class="s">"="</span> <span class="o">+</span> <span class="nc">PropertyFileUtil</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">key</span><span class="o">));</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="s">"list"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">action</span><span class="o">))</span> <span class="o">{</span> <span class="c1">// 获取属性列表</span>
<span class="n">writeProperties</span><span class="o">(</span><span class="n">resp</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">setParameterToServerContext</span><span class="o">(</span><span class="nc">ServletContext</span> <span class="n">servletContext</span><span class="o">)</span> <span class="o">{</span>
<span class="n">servletContext</span><span class="o">.</span><span class="na">setAttribute</span><span class="o">(</span><span class="s">"prop"</span><span class="o">,</span> <span class="nc">PropertyFileUtil</span><span class="o">.</span><span class="na">getKeyValueMap</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Servlet发布之后就可以动态管理配置了,例如发布到生产环境后如果有配置需要更改(编辑服务器上保存的配置文件)可以访问下面的路径重载配置:</p>
<blockquote>
<p>http://yourhost.com/appname/properties-servlet?action=reload</p>
</blockquote>
<h3 id="25-让人凌乱的占位符配置">2.5 让人凌乱的占位符配置</h3>
<p>下面的log4j代码来自实际项目,看一看${…}的数量,有点小恐怖了,对于大型项目配置文件会更多,占位符也会更多。</p>
<script src="https://gist.github.com/henryyan/5893935.js"></script>
<h2 id="3-配置文件辅助maven插件portable-config-maven-plugin">3. 配置文件辅助Maven插件–portable-config-maven-plugin</h2>
<p><a href="https://github.com/juven/portable-config-maven-plugin">portable-config-maven-plugin</a>是《Maven实战》作者<a href="http://www.juvenxu.com/">Juven Xu</a>刚刚发布的一款插件,这个插件的用途就是在打包时根据不同环境替换原有配置,这个插件独特的地方在于不用使用占位符预先定义在配置文件中,而是直接替换的方式覆盖原有配置。</p>
<p>该插件支持替换properties、xml格式的配置文件,使用方法也很简单,在pom.xml中添加插件的定义:</p>
<pre class="brush:xml;highlight:13">
<plugin>
<groupId>com.juvenxu.portable-config-maven-plugin</groupId>
<artifactId>portable-config-maven-plugin</artifactId>
<version>1.0.1</version>
<executions>
<execution>
<goals>
<goal>replace-package</goal>
</goals>
</execution>
</executions>
<configuration>
<portableConfig>src/main/portable/test.xml</portableConfig>
</configuration>
</plugin>
```
**src/main/portable/test.xml**文件的内容就是需要替换的属性集合,下面列出了properties和xml的不同配置,xml替换使用XPATH规则。
```xml
<?xml version="1.0" encoding="utf-8" ?>
<portable-config>
<config-file path="WEB-INF/classes/db.properties">
<replace key="database.jdbc.username">test</replace>
<replace key="database.jdbc.password">test_pwd</replace>
</config-file>
</portable-config>
```
```xml
<?xml version="1.0" encoding="utf-8" ?>
<portable-config>
<config-file path="WEB-INF/web.xml">
<replace xpath="/web-app/display-name">awesome app</replace>
</config-file>
</portable-config>
```
当然你可以定义几个不同环境的profile来决定使用哪个替换规则,在打包(mvn package)时该插件会被激活执行替换动作。
有了这款插件对于一些默认的属性可以不使用占位符定义,取而代之的是实际的配置,所以对现有的配置无任何影响,又可以灵活的更改配置。
</pre>咖啡兔https://henryyan.github.io引言利用100行代码动态创建并部署流程2013-05-27T00:00:00+08:002013-05-27T00:00:00+08:00https://kafeitu.me/activiti/2013/05/27/dynamic-process-creation-and-deployment-in-100-lines<p>英文原文:<a href="http://stacktrace.be/blog/2013/03/dynamic-process-creation-and-deployment-in-100-lines/">Dynamic Process Creation and Deployment in 100 Lines of Code</a></p>
<p>这是一篇迟到的博文,几个月前我就准备把它整理出来发布,由于时间原因就搁置了。。。</p>
<h2 id="1-关于activiti中的bpmn-model">1. 关于Activiti中的BPMN Model</h2>
<p>在5.12版本中把各个模块进行了大幅度的划分,值得一提的就是activiti-bpmn-的几个模块(activiti-bpmn-converter、activiti-bpmn-layout、activiti-bpmn-model)。</p>
<ul>
<li>activiti-bpmn-model:包含了BPMN2.0规范中部分对应的Java定义(也包括Activiti自己扩展的),描述了一些基本属性、结构;</li>
<li>activiti-bpmn-converter:该模块负责对Model对象与XML进行互转;</li>
<li>activiti-bpmn-layout:可以根据流程定义文件中的XML定义生成BPMN DI信息(定义了流程中每一个活动的坐标、宽度、高度等)。</li>
</ul>
<h2 id="2-activiti-dynamic-process">2. activiti-dynamic-process</h2>
<p>Activiti团队核心成员<strong>frederikheremans</strong>创建了<a href="https://github.com/frederikheremans/activiti-dynamic-process">activiti-dynamic-process</a>项目,该项目利用以上的几个模块演示了如何动态创建流程并部署运行,这几个步骤仅仅用了100行代码(还可以继续精简,但是这不是重点,重点在于体现Activiti的灵活性)。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testDynamicDeploy</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="c1">// 1. Build up the model from scratch</span>
<span class="nc">BpmnModel</span> <span class="n">model</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BpmnModel</span><span class="o">();</span>
<span class="nc">Process</span> <span class="n">process</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Process</span><span class="o">();</span>
<span class="n">model</span><span class="o">.</span><span class="na">addProcess</span><span class="o">(</span><span class="n">process</span><span class="o">);</span>
<span class="n">process</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="s">"my-process"</span><span class="o">);</span>
<span class="n">process</span><span class="o">.</span><span class="na">addFlowElement</span><span class="o">(</span><span class="n">createStartEvent</span><span class="o">());</span>
<span class="n">process</span><span class="o">.</span><span class="na">addFlowElement</span><span class="o">(</span><span class="n">createUserTask</span><span class="o">(</span><span class="s">"task1"</span><span class="o">,</span> <span class="s">"First task"</span><span class="o">,</span> <span class="s">"fred"</span><span class="o">));</span>
<span class="n">process</span><span class="o">.</span><span class="na">addFlowElement</span><span class="o">(</span><span class="n">createUserTask</span><span class="o">(</span><span class="s">"task2"</span><span class="o">,</span> <span class="s">"Second task"</span><span class="o">,</span> <span class="s">"john"</span><span class="o">));</span>
<span class="n">process</span><span class="o">.</span><span class="na">addFlowElement</span><span class="o">(</span><span class="n">createEndEvent</span><span class="o">());</span>
<span class="n">process</span><span class="o">.</span><span class="na">addFlowElement</span><span class="o">(</span><span class="n">createSequenceFlow</span><span class="o">(</span><span class="s">"start"</span><span class="o">,</span> <span class="s">"task1"</span><span class="o">));</span>
<span class="n">process</span><span class="o">.</span><span class="na">addFlowElement</span><span class="o">(</span><span class="n">createSequenceFlow</span><span class="o">(</span><span class="s">"task1"</span><span class="o">,</span> <span class="s">"task2"</span><span class="o">));</span>
<span class="n">process</span><span class="o">.</span><span class="na">addFlowElement</span><span class="o">(</span><span class="n">createSequenceFlow</span><span class="o">(</span><span class="s">"task2"</span><span class="o">,</span> <span class="s">"end"</span><span class="o">));</span>
<span class="c1">// 2. Generate graphical information</span>
<span class="k">new</span> <span class="nf">BpmnAutoLayout</span><span class="o">(</span><span class="n">model</span><span class="o">).</span><span class="na">execute</span><span class="o">();</span>
<span class="c1">// 3. Deploy the process to the engine</span>
<span class="nc">Deployment</span> <span class="n">deployment</span> <span class="o">=</span> <span class="n">activitiRule</span><span class="o">.</span><span class="na">getRepositoryService</span><span class="o">().</span><span class="na">createDeployment</span><span class="o">()</span>
<span class="o">.</span><span class="na">addBpmnModel</span><span class="o">(</span><span class="s">"dynamic-model.bpmn"</span><span class="o">,</span> <span class="n">model</span><span class="o">).</span><span class="na">name</span><span class="o">(</span><span class="s">"Dynamic process deployment"</span><span class="o">)</span>
<span class="o">.</span><span class="na">deploy</span><span class="o">();</span>
<span class="c1">// 4. Start a process instance</span>
<span class="nc">ProcessInstance</span> <span class="n">processInstance</span> <span class="o">=</span> <span class="n">activitiRule</span><span class="o">.</span><span class="na">getRuntimeService</span><span class="o">()</span>
<span class="o">.</span><span class="na">startProcessInstanceByKey</span><span class="o">(</span><span class="s">"my-process"</span><span class="o">);</span>
<span class="c1">// 5. Check if task is available</span>
<span class="nc">List</span> <span class="n">tasks</span> <span class="o">=</span> <span class="n">activitiRule</span><span class="o">.</span><span class="na">getTaskService</span><span class="o">().</span><span class="na">createTaskQuery</span><span class="o">()</span>
<span class="o">.</span><span class="na">processInstanceId</span><span class="o">(</span><span class="n">processInstance</span><span class="o">.</span><span class="na">getId</span><span class="o">()).</span><span class="na">list</span><span class="o">();</span>
<span class="nc">Assert</span><span class="o">.</span><span class="na">assertEquals</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">tasks</span><span class="o">.</span><span class="na">size</span><span class="o">());</span>
<span class="nc">Assert</span><span class="o">.</span><span class="na">assertEquals</span><span class="o">(</span><span class="s">"First task"</span><span class="o">,</span> <span class="n">tasks</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">).</span><span class="na">getName</span><span class="o">());</span>
<span class="nc">Assert</span><span class="o">.</span><span class="na">assertEquals</span><span class="o">(</span><span class="s">"fred"</span><span class="o">,</span> <span class="n">tasks</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">).</span><span class="na">getAssignee</span><span class="o">());</span>
<span class="c1">// 6. Save process diagram to a file </span>
<span class="nc">InputStream</span> <span class="n">processDiagram</span> <span class="o">=</span> <span class="n">activitiRule</span><span class="o">.</span><span class="na">getRepositoryService</span><span class="o">()</span>
<span class="o">.</span><span class="na">getProcessDiagram</span><span class="o">(</span><span class="n">processInstance</span><span class="o">.</span><span class="na">getProcessDefinitionId</span><span class="o">());</span>
<span class="nc">FileUtils</span><span class="o">.</span><span class="na">copyInputStreamToFile</span><span class="o">(</span><span class="n">processDiagram</span><span class="o">,</span> <span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="s">"target/diagram.png"</span><span class="o">));</span>
<span class="c1">// 7. Save resulting BPMN xml to a file</span>
<span class="nc">InputStream</span> <span class="n">processBpmn</span> <span class="o">=</span> <span class="n">activitiRule</span><span class="o">.</span><span class="na">getRepositoryService</span><span class="o">()</span>
<span class="o">.</span><span class="na">getResourceAsStream</span><span class="o">(</span><span class="n">deployment</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="s">"dynamic-model.bpmn"</span><span class="o">);</span>
<span class="nc">FileUtils</span><span class="o">.</span><span class="na">copyInputStreamToFile</span><span class="o">(</span><span class="n">processBpmn</span><span class="o">,</span>
<span class="k">new</span> <span class="nf">File</span><span class="o">(</span><span class="s">"target/process.bpmn20.xml"</span><span class="o">));</span>
<span class="o">}</span>
<span class="kd">protected</span> <span class="nc">UserTask</span> <span class="nf">createUserTask</span><span class="o">(</span><span class="nc">String</span> <span class="n">id</span><span class="o">,</span> <span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="nc">String</span> <span class="n">assignee</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">UserTask</span> <span class="n">userTask</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">UserTask</span><span class="o">();</span>
<span class="n">userTask</span><span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="n">name</span><span class="o">);</span>
<span class="n">userTask</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
<span class="n">userTask</span><span class="o">.</span><span class="na">setAssignee</span><span class="o">(</span><span class="n">assignee</span><span class="o">);</span>
<span class="k">return</span> <span class="n">userTask</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">protected</span> <span class="nc">SequenceFlow</span> <span class="nf">createSequenceFlow</span><span class="o">(</span><span class="nc">String</span> <span class="n">from</span><span class="o">,</span> <span class="nc">String</span> <span class="n">to</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">SequenceFlow</span> <span class="n">flow</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SequenceFlow</span><span class="o">();</span>
<span class="n">flow</span><span class="o">.</span><span class="na">setSourceRef</span><span class="o">(</span><span class="n">from</span><span class="o">);</span>
<span class="n">flow</span><span class="o">.</span><span class="na">setTargetRef</span><span class="o">(</span><span class="n">to</span><span class="o">);</span>
<span class="k">return</span> <span class="n">flow</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">protected</span> <span class="nc">StartEvent</span> <span class="nf">createStartEvent</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">StartEvent</span> <span class="n">startEvent</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StartEvent</span><span class="o">();</span>
<span class="n">startEvent</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="s">"start"</span><span class="o">);</span>
<span class="k">return</span> <span class="n">startEvent</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">protected</span> <span class="nc">EndEvent</span> <span class="nf">createEndEvent</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">EndEvent</span> <span class="n">endEvent</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">EndEvent</span><span class="o">();</span>
<span class="n">endEvent</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="s">"end"</span><span class="o">);</span>
<span class="k">return</span> <span class="n">endEvent</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>部署后获取流程图如下所示:
<img src="/files/2013/05/diagram.png" alt="" /></p>
<h2 id="3-实现步骤">3. 实现步骤</h2>
<p>按照从代码清单中的7步依次分析:</p>
<ol>
<li>利用BPMN-Model创建了开始、结束事件、2个用户任务以及其他的输出流;</li>
<li>利用BpmnAutoLayout类生成BPMN DI信息,这样在部署时引擎可以根据BPMN DI信息生成流程图;</li>
<li>创建DeploymentBuilder对象调用addBpmnModel方法直接通过Model对象部署流程;</li>
<li>启动流程;</li>
<li>获取所有用户任务,并验证任务的信息;</li>
<li>导出流程图,如果没有执行第二步操作导不出流程图;</li>
<li>导出流程定义文件(XML格式,包含BPMN DI信息)</li>
</ol>
<h2 id="4-抛砖引玉">4. 抛砖引玉</h2>
<p>利用这个Demo可以在不借助可视化流程设计器的情况下动态设计流程,进一步提升了应用的灵活性;当然如果你熟悉了以上的几个模块也可以自己扩展实现特定功能,例如Activiti中的Email Task就是一个特殊的活动类型,它继承于Service Task,所以你也可以参考它做自己的实现。</p>咖啡兔https://henryyan.github.io英文原文:Dynamic Process Creation and Deployment in 100 Lines of Code谈谈Activiti的引擎与引擎配置对象2013-04-19T00:00:00+08:002013-04-19T00:00:00+08:00https://kafeitu.me/activiti/2013/04/19/about-process-egine-and-configuration<h2 id="1-引擎配置对象processengineconfiguration">1. 引擎配置对象ProcessEngineConfiguration</h2>
<p>引擎配置是配置Activiti的第一步,无论你使用Standalone还是Spring管理引擎都可以在配置文件中配置参数,虽然目前Activiti支持多种引擎配置对象,但是均继承自一个基础的配置对象(抽象类)<em>org.activiti.engine.ProcessEngineConfiguration</em>。</p>
<p>除了基础的引擎配置对象之外还有一下几个具体的实现,不同的场合使用不用的引擎实现,均继承自<em>org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl</em></p>
<ol>
<li><strong>StandaloneProcessEngineConfiguration:</strong>标准的单机引擎配置对象,默认读取activiti.cfg.xml文件的配置</li>
<li><strong>StandaloneInMemProcessEngineConfiguration</strong>:用于测试环境,jdbcUrl配置为jdbc:h2:mem:activiti,数据库的DDL操作配置:create-drop,在日常的快速测试中经常用到</li>
<li><strong>JtaProcessEngineConfiguration:</strong>顾名思义,支持JTA</li>
<li><strong>SpringProcessEngineConfiguration:</strong>这个恐怕是用的最多的一个,由Spring代理创建引擎,最最重要的是如果把引擎嵌入到业务系统中可以做到业务事务与引擎事务的统一管理</li>
</ol>
<p>至于引擎中可以配置哪些属性在手册里面已经介绍了一部分,还有一部分隐藏的属性未介绍,如果有需要可以查看每个引擎中的<strong>setter</strong>方法覆盖默认值。</p>
<h2 id="2配置引擎的别名以及获取引擎对象">2.配置引擎的别名以及获取引擎对象</h2>
<p>Activiti允许创建多个引擎,每个引擎用别名区分,可以在引擎配置对象中设置一下属性,默认的引擎别名为:<strong>default</strong>。</p>
<h3 id="21-标准方式">2.1 标准方式</h3>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><property</span> <span class="na">name=</span><span class="s">"processEngineName"</span> <span class="na">value=</span><span class="s">"myProcessEngine"</span><span class="nt">></property></span>
</code></pre></div></div>
<p>其中的<strong>myProcessEngine</strong>即为引擎的别名,当需要获取引擎对象时可以通过下面的代码获取:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">ProcessEngine</span> <span class="n">myProcessEngine</span> <span class="o">=</span> <span class="nc">ProcessEngines</span><span class="o">.</span><span class="na">getProcessEngine</span><span class="o">(</span><span class="s">"myProcessEngine"</span><span class="o">);</span>
</code></pre></div></div>
<p>当然如果只有一个引擎获取就更简单了,下面的代码可以直接获取一个默认的引擎对象。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">ProcessEngine</span> <span class="n">processEngine</span> <span class="o">=</span> <span class="nc">ProcessEngines</span><span class="o">.</span><span class="na">getDefaultProcessEngine</span><span class="o">();</span>
<span class="c1">// 等价于 ProcessEngines.getProcessEngine("default");</span>
</code></pre></div></div>
<h3 id="22-spring方式">2.2 Spring方式</h3>
<p>如果使用了Spring代理引擎可以使用“Spring”风格方式获取引擎对象,例如下面的配置:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><bean</span> <span class="na">id=</span><span class="s">"processEngineConfiguration"</span> <span class="na">class=</span><span class="s">"org.activiti.spring.SpringProcessEngineConfiguration"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"dataSource"</span> <span class="na">ref=</span><span class="s">"dataSource"</span><span class="nt">></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"transactionManager"</span> <span class="na">ref=</span><span class="s">"transactionManager"</span><span class="nt">></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"databaseSchemaUpdate"</span> <span class="na">value=</span><span class="s">"true"</span><span class="nt">></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"jobExecutorActivate"</span> <span class="na">value=</span><span class="s">"false"</span><span class="nt">></property></span>
...
<span class="nt"></bean></span>
</code></pre></div></div>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Controller</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/workflow"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ActivitiController</span> <span class="o">{</span>
<span class="nd">@Autowired</span>
<span class="nc">ProcessEngineFactoryBean</span> <span class="n">processEngineFactoryBean</span><span class="o">;</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/print"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ModelAndView</span> <span class="nf">processList</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">ProcessEngine</span> <span class="n">processEngine</span> <span class="o">=</span> <span class="n">processEngineFactoryBean</span><span class="o">.</span><span class="na">getObject</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="23-在引擎外部设置引擎配置对象">2.3 在引擎外部设置引擎配置对象</h3>
<p>或许这个小节的标题看不懂了。。。</p>
<p>原因是这样的,众所周知,在默认的配置情况下部署包含中文的流程文件会导致中文乱码(Linux、Windows,Mac平台没问题),所以需要覆盖引擎默认的字体配置属性(活动的字体与输出流文字字体),例如下面的配置:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><bean</span> <span class="na">id=</span><span class="s">"processEngineConfiguration"</span> <span class="na">class=</span><span class="s">"org.activiti.spring.SpringProcessEngineConfiguration"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"activityFontName"</span> <span class="na">value=</span><span class="s">"宋体"</span><span class="nt">></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"labelFontName"</span> <span class="na">value=</span><span class="s">"宋体"</span><span class="nt">></property></span>
...
<span class="nt"></bean></span>
</code></pre></div></div>
<p>字体名称根据平台的不同其值也不同,例如在Windows平台下可以使用诸如<strong>宋体</strong>、<strong>微软雅黑</strong>等,但是在Linux平台下引擎没有这些字体(或者名称不同)需要特殊设置,kft-activiti-demo的在线demo部署在Ubuntu Server上,而且是纯英文系统没有中文字体,为了解决部署后以及流程跟踪时的中文乱码问题我从Windows平台复制了宋体字体文件解决,字体的文件名为<strong>simsun.ttc</strong>,使用的配置如下所示:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><bean</span> <span class="na">id=</span><span class="s">"processEngineConfiguration"</span> <span class="na">class=</span><span class="s">"org.activiti.spring.SpringProcessEngineConfiguration"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"activityFontName"</span> <span class="na">value=</span><span class="s">"simsun"</span><span class="nt">></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"labelFontName"</span> <span class="na">value=</span><span class="s">"simsun"</span><span class="nt">></property></span>
...
<span class="nt"></bean></span>
</code></pre></div></div>
<p>这样就解决了部署流程时的中文乱码问题,但是没有解决流程跟踪时的乱码问题。。。</p>
<p>原因在于流程图生成工具类<strong>ProcessDiagramGenerator</strong>会从当前的ThreadLocal中获取引擎配置对象,但是目前仅仅是引擎自动在内部实现时把引擎配置对象设置到ThreadLocal中,所以很多人遇到在Struts(2)或者Spring MVC中直接调用下面的代码得到的图片是乱码:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">InputStream</span> <span class="n">imageStream</span> <span class="o">=</span> <span class="nc">ProcessDiagramGenerator</span><span class="o">.</span><span class="na">generateDiagram</span><span class="o">(</span><span class="n">bpmnModel</span><span class="o">,</span> <span class="s">"png"</span><span class="o">,</span> <span class="n">activeActivityIds</span><span class="o">);</span>
</code></pre></div></div>
<p>既然知道引擎从ThreadLocal中获取引擎配置对象,而且我们已经获取了引擎对象(也就是说可以从中获取引擎配置对象),解决问题的办法很简单,手动把引擎配置对象设置到ThreadLocal中就解决问题了;下面的代码在kft-activiti-demo的ActivitiController类中。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/process/trace/auto/{executionId}"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">readResource</span><span class="o">(</span><span class="nd">@PathVariable</span><span class="o">(</span><span class="s">"executionId"</span><span class="o">)</span> <span class="nc">String</span> <span class="n">executionId</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">response</span><span class="o">)</span>
<span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">ProcessInstance</span> <span class="n">processInstance</span> <span class="o">=</span> <span class="n">runtimeService</span><span class="o">.</span><span class="na">createProcessInstanceQuery</span><span class="o">()</span>
<span class="o">.</span><span class="na">processInstanceId</span><span class="o">(</span><span class="n">executionId</span><span class="o">).</span><span class="na">singleResult</span><span class="o">();</span>
<span class="nc">BpmnModel</span> <span class="n">bpmnModel</span> <span class="o">=</span> <span class="n">repositoryService</span><span class="o">.</span><span class="na">getBpmnModel</span><span class="o">(</span><span class="n">processInstance</span><span class="o">.</span><span class="na">getProcessDefinitionId</span><span class="o">());</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">String</span><span class="o">></span> <span class="n">activeActivityIds</span> <span class="o">=</span> <span class="n">runtimeService</span><span class="o">.</span><span class="na">getActiveActivityIds</span><span class="o">(</span><span class="n">executionId</span><span class="o">);</span>
<span class="c1">// 不使用spring请使用下面的两行代码</span>
<span class="c1">// ProcessEngineImpl defaultProcessEngine = (ProcessEngineImpl) ProcessEngines.getDefaultProcessEngine();</span>
<span class="c1">// Context.setProcessEngineConfiguration(defaultProcessEngine.getProcessEngineConfiguration());</span>
<span class="c1">// 使用spring注入引擎请使用下面的这行代码</span>
<span class="nc">Context</span><span class="o">.</span><span class="na">setProcessEngineConfiguration</span><span class="o">(</span><span class="n">processEngine</span><span class="o">.</span><span class="na">getProcessEngineConfiguration</span><span class="o">());</span>
<span class="nc">InputStream</span> <span class="n">imageStream</span> <span class="o">=</span> <span class="nc">ProcessDiagramGenerator</span><span class="o">.</span><span class="na">generateDiagram</span><span class="o">(</span><span class="n">bpmnModel</span><span class="o">,</span> <span class="s">"png"</span><span class="o">,</span> <span class="n">activeActivityIds</span><span class="o">);</span>
<span class="c1">// 输出资源内容到相应对象</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">b</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="mi">1024</span><span class="o">];</span>
<span class="kt">int</span> <span class="n">len</span><span class="o">;</span>
<span class="k">while</span> <span class="o">((</span><span class="n">len</span> <span class="o">=</span> <span class="n">imageStream</span><span class="o">.</span><span class="na">read</span><span class="o">(</span><span class="n">b</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">1024</span><span class="o">))</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="o">)</span> <span class="o">{</span>
<span class="n">response</span><span class="o">.</span><span class="na">getOutputStream</span><span class="o">().</span><span class="na">write</span><span class="o">(</span><span class="n">b</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="n">len</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>关键就在于在调用生成流程图的方法之前调用一次Context.setProcessEngineConfiguration()方法即可,这样引擎就可以获取到引擎配置对象,从而获取到自定义的字体名称属性,乱码问题自然没有了。</p>
<h2 id="3-结束语">3. 结束语</h2>
<p>本文大概介绍了一下引擎配置对象以及如何获取引擎对象,并且就大家关注最多的中文乱码问题给出了完美解决办法,想了解引擎配置对象的本文可以作为一个引子,打开你探索引擎内部的大门;里面有很多属性都可以配置,花点时间研究会有意外收获,引擎的架构做的很棒,想玩转它就多花点时间探索它的奥秘,通过引擎配置对象可以打造完全定制化的引擎工作方式。</p>咖啡兔https://henryyan.github.io1. 引擎配置对象ProcessEngineConfiguration博客论坛正式上线2013-04-13T00:00:00+08:002013-04-13T00:00:00+08:00https://kafeitu.me/blog/2013/04/13/forum<p>这是一篇<strong>水文</strong>,只是告诉你博客的论坛上线了,免得你不知道在导航的最后添加了一个链接,希望这个图标能告诉你这是一个论坛,图标下面的“FORUM”希望你也能知道这还是一个论坛。</p>
<p>从去年开始不少人建议我搞一个论坛,但是现在所有流行的论坛都不符合我的要求,我没有那么多精力去经营它(太复杂),如果说需要把知识沉淀下来我有博客可以发布文章,唯独缺少的就是大家经常讨论的问题不好沉淀(QQ群的聊天记录太零碎);前段时间看到一个国外的<a href="http://moot.it">Moot</a>,第一眼看到它简直帅爆了,太简洁了(剩余的只有文字),优雅的排版、动态的消息提醒,虽然简洁但是常用的功能都能支持,当晚就搞了一个试用,效果很不错!!!</p>
<p>当你用IE打开只看到“loading…”时请别说:“怎么回事?”,直接弃用IE使用Chrome、Firefox或者Safari,这个论坛普通人不会来的,来的只有像你这样的码农,我们深受IE的摧残,请远离它!!!</p>
<p>来张漂亮的截图吧,哎呀,真美。。。(老罗的风格…)。</p>
<p><strong>Activiti论坛传送门</strong>:<a href="http://www.kafeitu.me/forum.html#!/activiti">http://www.kafeitu.me/forum.html#!/activiti</a></p>
<p><img src="/files/2013/04/forum.png" alt="论坛截图" /></p>咖啡兔https://henryyan.github.io这是一篇水文,只是告诉你博客的论坛上线了,免得你不知道在导航的最后添加了一个链接,希望这个图标能告诉你这是一个论坛,图标下面的“FORUM”希望你也能知道这还是一个论坛。