工作流所使用的表单页面使用freemarker设计,在发起或审批流程页面将表单作为一个iframe嵌入其中,实现如下:
<iframe id="doJobframe" name="doJobframe" frameborder="0" scrolling="yes" height="100%" width="100%" style="padding: 0; margin-top: 39px;" src="flowFormNextViewIndex.action?taskVo.processDefineId=${taskVo.processDefineId}"> </iframe>
flowFormNextViewIndex.action又指向taskRuncontent.jsp, 在该页面中使用
<form id="frmWorkFlow" name="frmWorkFlow" method="post"> <div type="custform">${taskVo.form}</div> <form>
将获取的form即表单信息展示在审批页面中。
而使用iframe加载页面速度是比较慢的。经过查询资料,我们使用这样一种方式即基于Struts2 Result Type为chain 的Action直接在审批页面中将表单加载,一次请求实现两次动作的跳转。
首先,我们看一下strtus常用的结果类型,
如json,这个我们比较常用,还有默认的类型;
另外,还有chain:从一个action跳转到另外一个action,可携带参数;
Dispatcher:指定jsp作为视图的结果类型,相当于请求转发,即将请求转发到指定的jsp资源,在本系统中如果没有指明类型,result的type即为dispatcher;
Freemarker:用于指定是要freemarker作为视图的结果类型
Redirect:用于直接跳转到指定url,相当于HttpServletResponse的sendRedirect()方法但无法携带参数;
当然还有其他类型,此处不再赘述。
通过以上的介绍,我们是可以看出chain类型是符合我们需求的,因为要跳转到另外一个action,且需要携带参数。
下面来看一下chain的使用方法:
基本用途是构造成一条动作链。前一个Action将控制权转交给后一个Action,而前一个Action的状态在后一个Action里仍然保持着。
我们看看未改造前struts.xml的配置情况:
<!-- 任务处理界面 --> <action name="doJob" class="taskAction" method="doJob"> <result name="success">../workflow/taskRun.jsp</result> </action> <!-- 流程表单预览 --> <action name="flowFormNextViewIndex" class="taskAction" method="queryFlowFormView"> <result name="success">../workflow/taskRunContent.jsp</result> </action>
改造后:
<!-- 任务处理界面 --> <action name="doJob" class="taskAction" method="doJob"> <result name="createjobAndDoJob" type="chain">flowFormNextViewIndex</result> </action> <!-- 流程表单预览 --> <action name="flowFormNextViewIndex" class="taskAction" method="queryFlowFormView"> <result name="success">../workflow/taskRun.jsp</result> </action>
在action中一如旧观,只是dojob中返回值名称由success改为了createjobAndDoJob.
在<result name="createjobAndDoJob" type="chain">flowFormNextViewIndex</result>中可以看到对应createjobAndDoJob的action是flowFormNextViewIndex,这里是直接跳转的。
如果只是这样配置,我们会发现这样一个问题,在flowFormNextViewIndex所对应的方法里相应的参数是拿不到的,这与前面关于result type的叙述又有矛盾。经过查阅资料,ChainingInterceptor.java的实现,发现有这样的注释:
/** * <!-- START SNIPPET: description --> * <p/> * An interceptor that copies all the properties of every object in the value stack to the currently executing object, * except for any object that implements {@link Unchainable}. A collection of optional <i>includes</i> and * <i>excludes</i> may be provided to control how and which parameters are copied. Only includes or excludes may be * specified. Specifying both results in undefined behavior. See the javadocs for {@link ReflectionProvider#copy(Object, Object, * java.util.Map, java.util.Collection, java.util.Collection)} for more information. * <p/> * <p/> * <b>Note:</b> It is important to remember that this interceptor does nothing if there are no objects already on the stack. * <br/>This means two things: * <br/><b>One</b>, you can safely apply it to all your actions without any worry of adverse affects. * <br/><b/>Two</b>, it is up to you to ensure an object exists in the stack prior to invoking this action. The most typical way this is done * is through the use of the <b>chain</b> result type, which combines with this interceptor to make up the action * chaining feature.
在 doJob中的taskVo并没有在value stack 中,所以没有拷贝到flowFormNextViewIndex中。所以我们采用资料提供的方法:
增加一个拦截器,在执行Actioin前判断一下,当前Action是否需要从前面的Action实例中获取数据。
首先定义注解类:
package com.gaochao.oa.module.bpm.workflow.server.servlet; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ChainTransParam { String fieldName() default ""; }
拦截器类的实现:
public class ChainParameterInterceptor extends AbstractInterceptor { private static final long serialVersionUID = -8279316685527646358L; @Override public String intercept(ActionInvocation invocation) throws Exception { ValueStack stack = invocation.getStack(); CompoundRoot root = stack.getRoot(); // 值栈不为null 且已经有前置Action // 栈最顶层(index = 0)为当前Action、紧接着(index = 1) 为前置Action if (root == null || root.size() <= 2) { return invocation.invoke(); } // 当前Action对象 Object target = invocation.getAction(); Field[] fields = target.getClass().getDeclaredFields(); // 遍历此Action对象的属性 是否有RecieveData注解 for (Field field : fields) { if (field.isAnnotationPresent(ChainTransParam.class)) { ChainTransParam rData = field.getAnnotation(ChainTransParam.class); // 取得源数据字段名 String fromName = rData.fieldName(); fromName = StringUtils.isEmpty(fromName) ? field.getName() : fromName; // 取得最近的前置Action Object srcAction = root.get(1); // 取得对应字段的值 Object value = ReflectionUtils.getFieldValue(srcAction, srcAction.getClass(), field.getName()); // 设定值 ReflectionUtils.setFieldValue(target, field.getName(), field.getType(), value); } } return invocation.invoke(); } @SuppressWarnings("unused") private Object findFieldValue(CompoundRoot root, Field field) { Object value = null; int size = root.size(); // 按顺序遍历前置Action for (int index = 1; index < size; index++) { Object srcAction = root.get(index); Object tmp = ReflectionUtils.getFieldValue(srcAction, srcAction.getClass(), field.getName()); // 取得对应字段的值 则返回 // 问题:如果前置Action中该字段本身就为null 则无法处理 if (tmp != null) { break; } } return value; } }
在Action的相关类中加入注解
@ChainTransParam private TaskVo taskVo = new TaskVo();
当在执行flowFormNextViewIndex之前,拦截器会去查找doJob,是否有 taskVo对象,有则将值拷贝到 flowFormNextViewIndex中。ChainTransParam 注解 允许输入参数名,没有输入则默认根据变量名去查找。
另外,ReflectionUtils类的实现如下:
public abstract class ReflectionUtils { private static final Log logger = LogFactory.getLog(ReflectionUtils.class); public static void setFieldValue(Object target, String fname, Class<?> ftype, Object fvalue) { setFieldValue(target, target.getClass(), fname, ftype, fvalue); } public static void setFieldValue(Object target, Class<?> clazz, String fname, Class<?> ftype, Object fvalue) { if (target == null || fname == null || "".equals(fname) || (fvalue != null && !ftype.isAssignableFrom(fvalue.getClass()))) { return; } try { Method method = clazz.getDeclaredMethod( "set" + Character.toUpperCase(fname.charAt(0)) + fname.substring(1), ftype); //if (!Modifier.isPublic(method.getModifiers())) { method.setAccessible(true); //} method.invoke(target, fvalue); } catch (Exception me) { if (logger.isDebugEnabled()) { logger.debug(me); } try { Field field = clazz.getDeclaredField(fname); //if (!Modifier.isPublic(field.getModifiers())) { field.setAccessible(true); //} field.set(target, fvalue); } catch (Exception fe) { if (logger.isDebugEnabled()) { logger.debug(fe); } } } } public static Object getFieldValue(Object target, String fname) { return getFieldValue(target, target.getClass(), fname); } public static Object getFieldValue(Object target, Class<?> clazz, String fname) { if (target == null || fname == null || "".equals(fname)) { return null; } boolean exCatched = false; try { String methodname = "get" + StringUtils.capitalize(fname); Method method = clazz.getDeclaredMethod(methodname); //if (!Modifier.isPublic(method.getModifiers())) { method.setAccessible(true); //} return method.invoke(target); } catch (NoSuchMethodException e) { exCatched = true; } catch (InvocationTargetException e) { exCatched = true; } catch (IllegalAccessException e) { exCatched = true; } if (exCatched) { try { Field field = clazz.getDeclaredField(fname); //if (!Modifier.isPublic(field.getModifiers())) { field.setAccessible(true); //} return field.get(target); } catch (Exception fe) { if (logger.isDebugEnabled()) { logger.debug(fe); } } } return null; } }
在struts-plugin.xml中对自定义拦截器进行配置
<interceptor name="createjobAndDoJob" class="com.gaochao.oa.module.bpm.workflow.server.servlet.ChainParameterInterceptor"/> <interceptor-stack name="gcplatformStack"> <interceptor-ref name="error"/> <interceptor-ref name="module"/> <interceptor-ref name="security"/> <interceptor-ref name="defaultStack"/> <interceptor-ref name="json"/> <interceptor-ref name="createjobAndDoJob" /> </interceptor-stack>
至此,所有的优化过程全部结束,页面打开的速度从3到5秒甚至更长,缩短为感官几无等待。