基于struts2 拦截器ResultType为chain的Action之间数据传递 ——表单页面打开优化

简介:

  工作流所使用的表单页面使用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资源,在本系统中如果没有指明类型,resulttype即为dispatcher; 

Freemarker:用于指定是要freemarker作为视图的结果类型

Redirect:用于直接跳转到指定url,相当于HttpServletResponsesendRedirect()方法但无法携带参数;

当然还有其他类型,此处不再赘述。

通过以上的介绍,我们是可以看出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,如需转载请自行联系原作者


相关文章
|
9月前
|
Java 编译器
Java Number & Math 类
4月更文挑战第12天
|
3天前
|
人工智能 自然语言处理 Shell
深度评测 | 仅用3分钟,百炼调用满血版 Deepseek-r1 API,百万Token免费用,简直不要太爽。
仅用3分钟,百炼调用满血版Deepseek-r1 API,享受百万免费Token。阿里云提供零门槛、快速部署的解决方案,支持云控制台和Cloud Shell两种方式,操作简便。Deepseek-r1满血版在推理能力上表现出色,尤其擅长数学、代码和自然语言处理任务,使用过程中无卡顿,体验丝滑。结合Chatbox工具,用户可轻松掌控模型,提升工作效率。阿里云大模型服务平台百炼不仅速度快,还确保数据安全,值得信赖。
129305 24
深度评测 | 仅用3分钟,百炼调用满血版 Deepseek-r1 API,百万Token免费用,简直不要太爽。
|
5天前
|
人工智能 API 网络安全
用DeepSeek,就在阿里云!四种方式助您快速使用 DeepSeek-R1 满血版!更有内部实战指导!
DeepSeek自发布以来,凭借卓越的技术性能和开源策略迅速吸引了全球关注。DeepSeek-R1作为系列中的佼佼者,在多个基准测试中超越现有顶尖模型,展现了强大的推理能力。然而,由于其爆火及受到黑客攻击,官网使用受限,影响用户体验。为解决这一问题,阿里云提供了多种解决方案。
16176 37
|
13天前
|
机器学习/深度学习 人工智能 自然语言处理
PAI Model Gallery 支持云上一键部署 DeepSeek-V3、DeepSeek-R1 系列模型
DeepSeek 系列模型以其卓越性能在全球范围内备受瞩目,多次评测中表现优异,性能接近甚至超越国际顶尖闭源模型(如OpenAI的GPT-4、Claude-3.5-Sonnet等)。企业用户和开发者可使用 PAI 平台一键部署 DeepSeek 系列模型,实现 DeepSeek 系列模型与现有业务的高效融合。
|
4天前
|
并行计算 PyTorch 算法框架/工具
本地部署DeepSeek模型
要在本地部署DeepSeek模型,需准备Linux(推荐Ubuntu 20.04+)或兼容的Windows/macOS环境,配备NVIDIA GPU(建议RTX 3060+)。安装Python 3.8+、PyTorch/TensorFlow等依赖,并通过官方渠道下载模型文件。配置模型后,编写推理脚本进行测试,可选使用FastAPI服务化部署或Docker容器化。注意资源监控和许可协议。
1190 8
|
13天前
|
人工智能 搜索推荐 Docker
手把手教你使用 Ollama 和 LobeChat 快速本地部署 DeepSeek R1 模型,创建个性化 AI 助手
DeepSeek R1 + LobeChat + Ollama:快速本地部署模型,创建个性化 AI 助手
3347 117
手把手教你使用 Ollama 和 LobeChat 快速本地部署 DeepSeek R1 模型,创建个性化 AI 助手
|
8天前
|
人工智能 自然语言处理 API
DeepSeek全尺寸模型上线阿里云百炼!
阿里云百炼平台近日上线了DeepSeek-V3、DeepSeek-R1及其蒸馏版本等六款全尺寸AI模型,参数量达671B,提供高达100万免费tokens。这些模型在数学、代码、自然语言推理等任务上表现出色,支持灵活调用和经济高效的解决方案,助力开发者和企业加速创新与数字化转型。示例代码展示了如何通过API使用DeepSeek-R1模型进行推理,用户可轻松获取思考过程和最终答案。
|
5天前
|
人工智能 自然语言处理 程序员
如何在通义灵码里用上DeepSeek-V3 和 DeepSeek-R1 满血版671B模型?
除了 AI 程序员的重磅上线外,近期通义灵码能力再升级全新上线模型选择功能,目前已经支持 Qwen2.5、DeepSeek-V3 和 R1系列模型,用户可以在 VSCode 和 JetBrains 里搜索并下载最新通义灵码插件,在输入框里选择模型,即可轻松切换模型。
853 14
|
12天前
|
API 开发工具 Python
阿里云PAI部署DeepSeek及调用
本文介绍如何在阿里云PAI EAS上部署DeepSeek模型,涵盖7B模型的部署、SDK和API调用。7B模型只需一张A10显卡,部署时间约10分钟。文章详细展示了模型信息查看、在线调试及通过OpenAI SDK和Python Requests进行调用的步骤,并附有测试结果和参考文档链接。
1875 9
阿里云PAI部署DeepSeek及调用
|
9天前
|
人工智能 数据可视化 Linux
【保姆级教程】3步搞定DeepSeek本地部署
DeepSeek在2025年春节期间突然爆火出圈。在目前DeepSeek的网站中,极不稳定,总是服务器繁忙,这时候本地部署就可以有效规避问题。本文以最浅显易懂的方式带读者一起完成DeepSeek-r1大模型的本地部署。

热门文章

最新文章