天天看点

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,放在其它路径,这样二者就不冲突了

struts2: 玩转 rest-plugin
struts2: 玩转 rest-plugin

View Code

二、拦截器及ModelDrive的问题

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

解决办法:

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

struts2: 玩转 rest-plugin
struts2: 玩转 rest-plugin

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

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

struts2: 玩转 rest-plugin
struts2: 玩转 rest-plugin
struts2: 玩转 rest-plugin
struts2: 玩转 rest-plugin

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

struts2: 玩转 rest-plugin
struts2: 玩转 rest-plugin

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

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

解决方法:

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

struts2: 玩转 rest-plugin
struts2: 玩转 rest-plugin

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

struts2: 玩转 rest-plugin
struts2: 玩转 rest-plugin
struts2: 玩转 rest-plugin
struts2: 玩转 rest-plugin

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

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

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

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

struts2: 玩转 rest-plugin
struts2: 玩转 rest-plugin
struts2: 玩转 rest-plugin
struts2: 玩转 rest-plugin

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

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

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

struts2: 玩转 rest-plugin
struts2: 玩转 rest-plugin

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

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

struts2: 玩转 rest-plugin
struts2: 玩转 rest-plugin

输出结果:

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

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

struts2: 玩转 rest-plugin
struts2: 玩转 rest-plugin

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

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

struts2: 玩转 rest-plugin
struts2: 玩转 rest-plugin

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

struts2: 玩转 rest-plugin
struts2: 玩转 rest-plugin

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

七、id参数太单一的问题

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

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