工作流所使用的表单页面使用freemarker设计,在发起或审批流程页面将表单作为一个iframe嵌入其中,实现如下:
1
2
|
<
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, 在该页面中使用
1
2
3
|
<
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的配置情况:
1
2
3
4
5
6
7
8
|
<!-- 任务处理界面 -->
<
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
>
|
改造后:
1
2
3
4
5
6
7
8
|
<!-- 任务处理界面 -->
<
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的实现,发现有这样的注释:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/**
* <!-- 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实例中获取数据。
首先定义注解类:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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
""
;
}
|
拦截器类的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
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的相关类中加入注解
1
2
|
@ChainTransParam
private
TaskVo taskVo =
new
TaskVo();
|
当在执行flowFormNextViewIndex之前,拦截器会去查找doJob,是否有 taskVo对象,有则将值拷贝到 flowFormNextViewIndex中。ChainTransParam 注解 允许输入参数名,没有输入则默认根据变量名去查找。
另外,ReflectionUtils类的实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
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中对自定义拦截器进行配置
1
2
3
4
5
6
7
8
9
|
<
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秒甚至更长,缩短为感官几无等待。
本文转自 gaochaojs 51CTO博客,原文链接:http://blog.51cto.com/jncumter/1637232,如需转载请自行联系原作者