struts2: 玩转 rest-plugin

近期使用struts2的rest-plugin,参考官方示例struts2-rest-showcase,做了一个restful service小项目,但官网提供的这个示例过于简单,埋下了巨坑无数,下面是一些遇到的问题及解决办法:

注:下面这些问题,很多是相互关联的,要解决一个,得同时解决另一个。 

一、与config-browser-plugin、convension-plugin、非rest Action 共存的问题

rest-plugin的气场实在太强,一旦使用,config-browser-plugin、convension-plugin这二个plugin就挂了

解决思路:将所有rest服务,都放在/rest/路径下,用package的namespace把它隔离出来,其它常规的action,放在其它路径,这样二者就不冲突了

 1     <!-- Overwrite Convention -->
 2     <constant name="struts.convention.action.suffix" value="Controller" />
 3     <constant name="struts.convention.action.mapAllMatches" value="true" />
 4     <!--<constant name="struts.rest.content.restrictToGET" value="false" />-->
 5     <constant name="struts.convention.default.parent.package" value="rest-default" />
 6     <constant name="struts.convention.package.locators" value="action" />
 7     <!-- <constant name="struts.rest.namespace" value="/rest" /> -->
 8     <constant name="struts.convention.action.includeJars" value=".*?/_wl_cls_gen.*?jar(!/)?" />
 9     <constant name="struts.convention.exclude.parentClassLoader" value="true" />
10     <constant name="struts.convention.action.fileProtocols" value="jar,zip,vfsfile,vfszip" />
11
12     <constant name="struts.mapper.class" value="org.apache.struts2.dispatcher.mapper.PrefixBasedActionMapper" />
13     <constant name="struts.mapper.prefixMapping" value="/rest:rest,:struts" />
14     <constant name="struts.mapper.alwaysSelectFullNamespace" value="false" />
15
16         <package name="default" namespace="/rest" extends="rest-default" />

View Code

 

二、拦截器及ModelDrive的问题

如果自定义拦截器(比如:自定义异常拦截器),默认情况下是无法拦截rest的Action

解决办法:

a) strut2.xml中定义二个package:rest-package、page-package,并在这二个package中,加上自己的拦截器,完整strut2.xml参考下面的内容:

  1 <?xml version="1.0" encoding="UTF-8" ?>
  2
  3
  4 <!DOCTYPE struts PUBLIC
  5     "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
  6     "http://struts.apache.org/dtds/struts-2.3.dtd">
  7
  8 <struts>
  9
 10     <bean name="xmlHandler" type="org.apache.struts2.rest.handler.ContentTypeHandler"
 11         class="com.cnblogs.yjmyzz.handler.XStreamHandler" />
 12
 13     <bean name="jsonHandler" type="org.apache.struts2.rest.handler.ContentTypeHandler"
 14         class="com.cnblogs.yjmyzz.handler.JacksonHandler" />
 15
 16     <!-- Overwrite Convention -->
 17     <constant name="struts.convention.action.suffix" value="Controller" />
 18     <constant name="struts.convention.action.mapAllMatches" value="true" />
 19     <!--<constant name="struts.rest.content.restrictToGET" value="false" />-->
 20     <constant name="struts.convention.default.parent.package"
 21         value="rest-default" />
 22     <constant name="struts.convention.package.locators" value="action" />
 23     <!-- <constant name="struts.rest.namespace" value="/rest" /> -->
 24     <constant name="struts.convention.action.includeJars" value=".*?/_wl_cls_gen.*?jar(!/)?" />
 25     <constant name="struts.convention.exclude.parentClassLoader"
 26         value="true" />
 27     <constant name="struts.convention.action.fileProtocols" value="jar,zip,vfsfile,vfszip" />
 28
 29     <constant name="struts.mapper.class"
 30         value="org.apache.struts2.dispatcher.mapper.PrefixBasedActionMapper" />
 31     <constant name="struts.mapper.prefixMapping" value="/rest:rest,:struts" />
 32     <constant name="struts.mapper.alwaysSelectFullNamespace"
 33         value="false" />
 34
 35     <package name="base-default" extends="struts-default">
 36         <global-results>
 37             <result name="error">/WEB-INF/common/error.jsp</result>
 38         </global-results>
 39
 40         <global-exception-mappings>
 41             <exception-mapping exception="java.lang.Exception"
 42                 result="error" />
 43         </global-exception-mappings>
 44     </package>
 45
 46     <package name="rest-package" namespace="/rest" extends="base-default">
 47         <result-types>
 48             <result-type name="redirect"
 49                 class="org.apache.struts2.dispatcher.ServletRedirectResult">
 50                 <param name="statusCode">303</param>
 51             </result-type>
 52             <result-type name="redirectAction"
 53                 class="org.apache.struts2.dispatcher.ServletActionRedirectResult">
 54                 <param name="statusCode">303</param>
 55             </result-type>
 56         </result-types>
 57         <interceptors>
 58             <interceptor name="rest"
 59                 class="org.apache.struts2.rest.ContentTypeInterceptor" />
 60             <interceptor name="restWorkflow"
 61                 class="org.apache.struts2.rest.RestWorkflowInterceptor" />
 62             <interceptor name="messages"
 63                 class="org.apache.struts2.interceptor.MessageStoreInterceptor" />
 64             <interceptor name="exceptionInterceptor"
 65                 class="com.cnblogs.yjmyzz.interceptor.ExceptionInterceptor">
 66             </interceptor>
 67             <interceptor-stack name="restDefaultStack">
 68                 <interceptor-ref name="exception" />
 69                 <interceptor-ref name="alias" />
 70                 <interceptor-ref name="servletConfig" />
 71                 <interceptor-ref name="messages">
 72                     <param name="operationMode">AUTOMATIC</param>
 73                 </interceptor-ref>
 74                 <interceptor-ref name="prepare" />
 75                 <interceptor-ref name="i18n" />
 76                 <interceptor-ref name="chain" />
 77                 <interceptor-ref name="debugging" />
 78                 <interceptor-ref name="profiling" />
 79                 <interceptor-ref name="actionMappingParams" />
 80                 <interceptor-ref name="scopedModelDriven" />
 81                 <interceptor-ref name="modelDriven">
 82                     <param name="refreshModelBeforeResult">true</param>
 83                 </interceptor-ref>
 84                 <interceptor-ref name="fileUpload" />
 85                 <interceptor-ref name="checkbox" />
 86                 <interceptor-ref name="staticParams" />
 87                 <interceptor-ref name="params">
 88                     <param name="excludeParams">dojo\..*</param>
 89                 </interceptor-ref>
 90                 <interceptor-ref name="rest" />
 91                 <interceptor-ref name="conversionError" />
 92                 <interceptor-ref name="validation">
 93                     <param name="excludeMethods">input,back,cancel,browse,index,show,edit,editNew,deleteConfirm,destroy,create</param>
 94                 </interceptor-ref>
 95                 <interceptor-ref name="restWorkflow">
 96                     <param name="excludeMethods">input,back,cancel,browse,index,show,edit,editNew,deleteConfirm,destroy,create</param>
 97                 </interceptor-ref>
 98                 <interceptor-ref name="exceptionInterceptor" />
 99             </interceptor-stack>
100         </interceptors>
101         <default-interceptor-ref name="restDefaultStack" />
102         <default-class-ref class="org.apache.struts2.rest.RestActionSupport" />
103     </package>
104
105     <package name="page-package" namespace="/" extends="base-default">
106         <interceptors>
107             <interceptor name="exceptionInterceptor"
108                 class="com.cnblogs.yjmyzz.interceptor.ExceptionInterceptor">
109             </interceptor>
110             <interceptor-stack name="appStack">
111                 <interceptor-ref name="defaultStack">
112                     <param name="modelDriven.refreshModelBeforeResult">true</param>
113                 </interceptor-ref>
114                 <interceptor-ref name="exceptionInterceptor" />
115             </interceptor-stack>
116         </interceptors>
117         <default-interceptor-ref name="appStack" />
118     </package>
119
120 </struts>

View Code

b) 所有rest Action继承自一个自定义基类,所有常规page的Action,继承自另一个自定义基类

这二个基类用@ParentPackage 指定package,分别对应struts2.xml中的配置,这样运行时,不管是rest action,还是非rest action,都能被拦截器拦截

 1 package com.cnblogs.yjmyzz.action.base;
 2
 3 import org.apache.struts2.convention.annotation.ParentPackage;
 4
 5 import com.opensymphony.xwork2.ModelDriven;
 6 import com.opensymphony.xwork2.ValidationAwareSupport;
 7
 8 @ParentPackage("rest-package")
 9 public abstract class RestBaseAction extends ValidationAwareSupport implements
10         ModelDriven<Object> {
11
12     private static final long serialVersionUID = -8773131281804917145L;
13
14     public abstract Object getModel();
15
16 }

View Code

 1 package com.cnblogs.yjmyzz.action.base;
 2
 3 import org.apache.struts2.convention.annotation.ParentPackage;
 4
 5 import com.opensymphony.xwork2.ActionSupport;
 6
 7 @ParentPackage("page-package")
 8 public class PageBaseAction extends ActionSupport {
 9
10     private static final long serialVersionUID = 2323603138082550798L;
11
12 }

View Code

另外:官方的示例为了简便,在setId方法里,直接给Model赋值了,但这有点误导,因为拦截器拦截到的方法,并不是setId(),而是show()/index()之类的方法,所以应该在show方法里,调用 model = xxx.getModel(id),否则按原来的写法,如果getModel这里报错 -> setId()报错,但show()方法并没有出错,拦截器会认为没有异常发生。

 1     // GET /rest/orders/1
 2     public HttpHeaders show() {
 3         if (id != null) {
 4             // 如果id=x,演示拦截异常处理
 5             if (id.equals("x")) {
 6                 testException();
 7             }
 8             this.model = ordersService.get(id);
 9         }
10         return new DefaultHttpHeaders("show");
11     }
12
13     public void setId(String id) {
14         this.id = id;
15     }

View Code

 

三、返回XML节点的别名(alias)问题

默认情况下,返回的xml根节点为dto对应的完整package名,看上去很别扭

解决方法:

dto的class上,用@XStreamAlias指定别名

1 @XStreamAlias("order")
2 public class Order {}

View Code

然后再创建自己的XmlHandler,为了节省系统开销,下面的代码用了一个单例:

 1 package com.cnblogs.yjmyzz.handler;
 2
 3 import com.thoughtworks.xstream.XStream;
 4
 5 public class XStreamFactory {
 6
 7     private XStreamFactory() {
 8     }
 9
10     private static XStream xStream = null;
11
12     public static XStream getInstance() {
13         if (xStream == null) {
14             xStream = new XStream();
15             xStream.setMode(XStream.NO_REFERENCES);
16         }
17         return xStream;
18     }
19
20 }

View Code

 1 package com.cnblogs.yjmyzz.handler;
 2
 3 import java.io.IOException;
 4 import java.io.Reader;
 5 import java.io.Writer;
 6
 7 import org.apache.struts2.rest.handler.ContentTypeHandler;
 8
 9 import com.cnblogs.yjmyzz.dto.Order;
10 import com.cnblogs.yjmyzz.dto.OrderList;
11 import com.thoughtworks.xstream.XStream;
12
13 public class XStreamHandler implements ContentTypeHandler {
14
15     public String fromObject(Object obj, String resultCode, Writer out)
16             throws IOException {
17         if (obj != null) {
18             XStream xstream = XStreamFactory.getInstance();
19             xstream.processAnnotations(obj.getClass());
20             xstream.toXML(obj, out);
21         }
22         return null;
23     }
24
25     public void toObject(Reader in, Object target) {
26         XStream xstream = XStreamFactory.getInstance();
27         xstream.alias("data", OrderList.class);
28         xstream.alias("order", Order.class);
29         xstream.processAnnotations(target.getClass());
30         xstream.fromXML(in, target);
31     }
32
33     public String getContentType() {
34         return "application/xml";
35     }
36
37     public String getExtension() {
38         return "xml";
39     }
40
41 }

View Code

注:别名一定要在toObject方法里,明确指定,否则别名的注解不起作用。

最后在struts2.xml里,还要注册bean,参考前面完整的xml内容。

 

四、返回JSON的Date属性格式化的问题

默认情况下,如果model有日期型属性,返回的json格式十分长,看上去太臃肿,类似的,可以自己定义ContentTypeHandler来解决

 1 package com.cnblogs.yjmyzz.handler;
 2
 3 import org.codehaus.jackson.map.ObjectMapper;
 4
 5 public class JacksonFactory {
 6
 7     private JacksonFactory() {
 8
 9     }
10
11     private static ObjectMapper objectMapper = null;
12
13     public static ObjectMapper getObjectMapper() {
14         if (objectMapper == null) {
15             objectMapper = new ObjectMapper();
16         }
17         return objectMapper;
18     }
19
20 }

View Code

 1 package com.cnblogs.yjmyzz.handler;
 2
 3 import java.io.IOException;
 4 import java.io.Reader;
 5 import java.io.Writer;
 6
 7 import org.apache.logging.log4j.LogManager;
 8 import org.apache.logging.log4j.Logger;
 9 import org.apache.struts2.rest.handler.ContentTypeHandler;
10 import org.springframework.beans.BeanUtils;
11
12 public class JacksonHandler implements ContentTypeHandler {
13
14     Logger logger = LogManager.getLogger(this.getClass());
15
16     public String fromObject(Object obj, String resultCode, Writer out)
17             throws IOException {
18         if (obj != null) {
19             JacksonFactory.getObjectMapper().writeValue(out, obj);
20         }
21         return null;
22     }
23
24     public void toObject(Reader in, Object target) {
25         try {
26             Object origin = JacksonFactory.getObjectMapper().readValue(in,
27                     target.getClass());
28             BeanUtils.copyProperties(origin, target);
29
30         } catch (Exception e) {
31             e.printStackTrace();
32             logger.error(e);
33         }
34
35     }
36
37     public String getContentType() {
38         return "application/json;charset=UTF-8";
39     }
40
41     public String getExtension() {
42         return "json";
43     }
44
45 }

View Code

 

五、restful service 该返回哪种视图,xhtml? json? xml?

通常用rest-plugin,是为了开发rest-service,但是官网的示例返回的默认都是页面视图,这个显然不适合,最理想情况是,如果在页面上操作,操作完以后,应该返回页面视图(即: xxx.xhtml),如果是用xml参数进来的,应该返回xml视图(即: xxx.xml),如果是ajax用json post过来的,应该返回到json视图(即:xxx.json)

解决办法:根据Request的Header来判断来源,然后做相应的分支处理

 1     // POST /orders
 2     public HttpHeaders create() throws IOException {
 3         ordersService.save(model);
 4         HttpServletResponse response = ServletActionContext.getResponse();
 5         HttpServletRequest request = ServletActionContext.getRequest();
 6         String accept = request.getHeader("Accept");
 7         if (accept.contains("text/html")) { // 页面视图过来的
 8             response.sendRedirect("orders/");
 9         } else if (accept.contains("text/xml")) { // 发送xml过来的
10             response.sendRedirect("orders/" + model.getId() + ".xml");
11         } else { // 其它的返回json视图
12             response.sendRedirect("orders/" + model.getId() + ".json");
13         }
14         return null;
15     }

View Code

 

六、json post到service,model取不到值的问题

这个问题最恶心,连官方默认提供的org.apache.struts2.rest.handler.JsonLibHandler都有问题,原因在json反序列化的机制,大家可以感受下这段代码:

 1     @Test
 2     public void testJson() {
 3         String test = "{\"id\":\"3\",\"clientName\":\"Bob\",\"amount\":33,\"createTime\":\"1413947088717\"}";
 4         Order order = new Order();
 5
 6         System.out.println(order);
 7         System.out.println(order.hashCode());
 8
 9         System.out.println("----");
10
11         toObjectJson(test, order);
12
13         System.out.println("----");
14
15         System.out.println(order);
16         System.out.println(order.hashCode());
17     }
18
19     public void toObjectJson(String in, Object target) {
20         try {
21             target = JacksonFactory.getObjectMapper().readValue(in,
22                     target.getClass());
23             System.out.println(target);
24             System.out.println(target.hashCode());
25
26         } catch (Exception e) {
27             e.printStackTrace();
28
29         }
30     }

View Code

输出结果:

id:null,clientName:null,amount:0,createTime:Wed Oct 22 15:05:12 CST 2014

29791
----
id:3,clientName:Bob,amount:33,createTime:Wed Oct 22 11:04:48 CST 2014
2137470
----
id:null,clientName:null,amount:0,createTime:Wed Oct 22 15:05:12 CST 2014
29791

虽然传递的参数是Object,因java只有值传递,这里传递的值即为对象的“指针地址值”,但是json内部反序列化时,入口并非这个指针值,而是xxx.getClass(),即类型指针,导致最后toObject执行完,原来的指针是啥还是啥,跟反序列过程中"新创建"出来的新Object instance,完全豪无关联。因此,不得不改造成

 1     public void toObject(Reader in, Object target) {
 2         try {
 3             Object origin = JacksonFactory.getObjectMapper().readValue(in,
 4                     target.getClass());
 5             BeanUtils.copyProperties(origin, target);
 6
 7         } catch (Exception e) {
 8             e.printStackTrace();
 9             logger.error(e);
10         }
11
12     }

View Code

手动把新对象的属性,复制到target对象上,这样就保证了反序列后的结果,在toObject执行完以后,会反映到target上。

注:可能有朋友会问了,为什么只有json会这样,xml不会呢?再仔细看下XStreamHandler的toObject方法

1     public void toObject(Reader in, Object target) {
2         XStream xstream = XStreamFactory.getInstance();
3         xstream.alias("data", OrderList.class);
4         xstream.alias("order", Order.class);
5         xstream.processAnnotations(target.getClass());
6         xstream.fromXML(in, target);
7     }

View Code

最后一行xstream.fromXML(in, target);这是开始xml->object的入口,这里传递的就是target的地址对应的值,而不是象json那样是xxx.getClass()。如果进一步看源码,最后会发现执行的是com.thoughtworks.xstream.core.TreeUnmarshaller类里的

1     public TreeUnmarshaller(
2         Object root, HierarchicalStreamReader reader, ConverterLookup converterLookup,
3         Mapper mapper) {
4         this.root = root;
5         this.reader = reader;
6         this.converterLookup = converterLookup;
7         this.mapper = mapper;
8     }

View Code

整个过程,都没有新对象实例创建,所以相应的变化,能一直保持到toObject调用完成后。

 

七、id参数太单一的问题

这个其实并不是大太的问题,GET方式下,url里本来就不适合传递过多参数,实在想用多个参数,做个约定,比如  /orders/show/a-b-c,即id值为"a-b-c",然后拆解一下,a,b,c对应不同的含义即可

POST方式,更不成问题,直接post过来一段xml或json,最终映射成model,想要多少参数都不是问题

 

最后给出源码示例:struts-rest-ex-src.zip (基于官网的rest-showcase修改而来)

时间: 2024-12-23 04:43:45

struts2: 玩转 rest-plugin的相关文章

struts2.1 使用 convention plugin 时配置拦截器问题

问题描述 使用conventionplugin可以不用写action而根据地址栏中写的action名去找相应的页面.我在struts.xml中配置了默认拦截器,我将自己写的拦截器加入到默认拦截器中了,现在的问题是,如果我不写action类而直接利用conventionplugin根据地址栏中的action名查找相应的页面,则拦截器不起作用,如何才能让拦截器起作用而不用再写action类. 解决方案 解决方案二:该回复于2010-05-18 16:24:09被版主删除解决方案三:呵呵,写个过滤器解

Eclips使用秘技(绝对经典)

前言: 本来我都是使用JBuilderX当主力IDE.但使用了Eclipse后发现...Eclipse原来也这么好用...渐渐的就爱上了它...... Eclipse优点:免费.程序代码排版功能.有中文化包.可增设许多功能强大的外挂.支持多种操作系统(Windows.Linux.Solaris.Mac OSX)..等等. 开此篇讨论串的目的,是希望能将Eclipse的一些使用技巧集合起来...欢迎大家继续补充下去...由于Eclipse的版本众多,希望补充的先进们能顺便说明一下您所使用的版本-

C#以post方式调用struts rest-plugin service的问题

struts2: 玩转 rest-plugin一文中,学习了用struts2开发restful service的方法,发现用c#以post方式调用时各种报错,但java.ajax,包括firefox 的rest client插件测试也无问题.   先给出rest service中的这个方法: 1 // POST /orders 2 public HttpHeaders create() throws IOException, ServletException { 3 ordersService.

struts2使用Convention Plugin在weblogic上以war包部署时,找不到Action的解决办法

环境: struts 2.3.16.3 + Convention Plugin 2.3.16.3 实现零配置 现象:以文件夹方式部署在weblogic(10.3.3)上时一切正常,换成war包部署,运行时提示找不到Action   解决办法: 1. 检查生成的war包中\WEB-INF\classes\下有无META-INF目录,如果没有,在eclipse里resource\META-INF下随便放一个文件,比如test.xml,这样maven打包生成war包时,才会在classes下创建MET

详解 ML2 Core Plugin(II) - 每天5分钟玩转 OpenStack(72)

上一节我们讨论了 ML2 Plugin 解决的问题,本节将继续研究 ML2 的架构. ML2 对二层网络进行抽象和建模,引入了 type driver 和 mechanism driver. 这两类 driver 解耦了 Neutron 所支持的网络类型(type)与访问这些网络类型的机制(mechanism),其结果就是使得 ML2 具有非常好的弹性,易于扩展,能够灵活支持多种 type 和 mechanism. Type Driver Neutron 支持的每一种网络类型都有一个对应的 ML

Service Plugin / Agent - 每天5分钟玩转 OpenStack(73)

Core Plugin/Agent 负责管理核心实体:net, subnet 和 port.而对于更高级的网络服务,则由 Service Plugin/Agent 管理.Service Plugin 及其 Agent 提供更丰富的扩展功能,包括路由,load balance,firewall等,如图所示: DHCPdhcp agent 通过 dnsmasq 为 instance 提供 dhcp 服务. Routingl3 agent 可以为 project(租户)创建 router,提供 Neu

详解 ML2 Core Plugin(I) - 每天5分钟玩转 OpenStack(71)

我们在 Neutron Server 小节学习到 Core Plugin,其功能是维护数据库中 network, subnet 和 port 的状态,并负责调用相应的 agent 在 network provider 上执行相关操作,比如创建 network.上一节也介绍了两个 Core Plugin:linux bridge plugin 和 open vswitch plugin.本节将详细讨论更重要的 ML2 Core Plugin. Moduler Layer 2(ML2)是 Neutr

STRUTS2 JSON PLUGIN 如何提交有关系的实体类

问题描述 ACTION中有一个属性private WfClientTbl wfclient;实体 WfClientTbl 为public class WfClientTbl { private String addr1; private String addr2; @ManyToOne(fetch=FetchType.EAGER) @JoinColumn(name="curr") private CurTbl curr;CurTbl 为另外一实体public calss CurTbl

粗浅看Struts2和Hibernate框架

----------------------------------------------------------------------------------------------[版权申明:本文系作者原创,转载请注明出处] 文章出处:http://blog.csdn.net/sdksdk0/article/details/52424578作者:朱培      ID:sdksdk0      邮箱: zhupei@tianfang1314.cn    ------------------