我们在应用 DWR 调用远程方法时涉及到 JS 与 JAVA 之间参数和返回值的数据转换,例如:
JS 的 123 与 Java 的 int 或 Integer、long 间的转换
JS 的 "2009-06-23" 与 Java 的 java.util.Date 之间的转换
JS 的 "[1,2,3]" 与 Java 的 int[] 间的转换
JS 的 "{id:123, name: 'Unmi'}" 与 Java 的 Class Person{int id; String name} 间的转换
或者更复杂的嵌套类型( "{id:123, name: 'Unmi', blogs:['http://unmi.blogjava.net','http://blog.csdn.net/kypfos']}" ) 与 Java 类型间的转换,等等。那么这一切是怎么进行的呢?其实我们见识过很多组件的类型映射,如 Java 的 PropertyEditor、Hibernate(UserType)、iBatis(TypeHandler) 的类型映射,Struts1/2 中 Form/Model 用的 Converter 等。
这里我来稍稍分析 DWR 的 Converter 实现,以及说明如何定制自己的 Converter。本文所用 DWR 是 2.0.5 版。
1. DWR 内置的 Converter 及应用类型
名称 | 应用类型 | 转换器 |
null | void,java.lang.Void | NullConverter |
enum | EnumConverter | |
primitive | boolean,byte,short,int,long,float,double,char, java.lang.Boolean,java.lang.Byte,java.lang.Short, java.lang.Integer,java.lang.Long,java.lang.Float, java.lang.Double,java.lang.Character |
PrimitiveConverter |
bignumber | java.math.BigInteger,java.math.BigDecimal | BigNumberConverter |
string | java.lang.String | StringConverter |
array | [Z,[B,[S,[I,[J,[F,[D,[C,[L* | ArrayConverter |
map | java.util.Map | MapConverter |
collection | java.util.Collection | CollectionConverter |
date | java.util.Date,java.sql.Date,java.sql.Time, java.sql.Timestamp,java.util.Calendar |
DateConverter |
dom | org.w3c.dom.Node,org.w3c.dom.Element,org.w3c.dom.Document | DOMConverter |
dom4j | org.dom4j.Document,org.dom4j.Element,org.dom4j.Node | DOM4JConverter |
jdom | org.jdom.Document,org.jdom.Element | JDOMConverter |
xom | nu.xom.Document,nu.xom.Element,nu.xom.Node | XOMConverter |
servlet | javax.servlet.ServletConfig,javax.servlet.ServletContext, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.http.HttpSession |
ServletConverter |
bean | BeanConverter | |
object | ObjectConverter | |
hibernate2 | H2BeanConverter | |
hibernate3 | H3BeanConverter | |
url | java.net.URL | URLConverter |
exception | ExceptionConverter | |
miniException | java.lang.Throwable | MinimalistExceptionConverter |
它们是应用启动的时候,通过 org.directwebremoting.servlet.DwrServlet 初始化 dwr-2.0.5.jar!/org/directwebremoting/dwr.xml 文件加载进来的。例如:
<converter id="date" class="org.directwebremoting.convert.DateConverter"/> 注册了 date 转换器
<convert converter="date" match="java.util.Date"/> 应用注册的 date 转换器应用到 java.util.Date 类型
看到上面,你也许会惊讶一下,我们平时可能也就用下 bean 转换器,其他用内置就行。然而 DWR 确为我们考虑的很周到的,包括 hibernate 相关的,URL、Servlet、Dom 等相关类型的转换器。
2. DWR 如何确定用哪个 Converter?
DWR 是根据方法参数来确定入口参数的 Converter、根据返回值类型确定传向 JS 的出口参数的 Converter。总之是以 Java 方法原型为基准来决定每一参数或返回值各自用哪个 Converter 来转换数据。
在 BaseCallMarshaller.marshallInbound(HttpServletRequest request, HttpServletResponse response) 方法中,使用
Class paramType = method.getParameterTypes()[j] 来获得参数的类型,然后从已加载的 Converter Map 中找到 Converter 名称,进而确定 Converter 类名。
而确定返回值类型就不是直接用反射的 method.getReturnType()。而是以反射方式调用方法后,根据具体返回值的类型来确定的。见:
Replay DefaultRemoter.execute(Call) 方法中的
Object reply = chain.doFilter(object, method, call.getParameters()); 再进入到
Object ExecuteAjaxFilter.doFilter(Object obj, Method method, Object[] params Ajax FilterChain){
return method.invoke(obj, params);
}
就是根据上面的返回值,然后在
DefaultConverterManager.convertOutbound(Object, OutboundContext) 方法中的
Converter converter = getConverter(object); //根据返回值 object 确定该用的 Converter。
3. DWR Converter 的调用
多留意下 DWR 自带的 Converter,可以看到所有的 Converter 直接或简接的 extends BaseV20Converter implements Converter,其实 BaseV20Converter(DWR 1.x 中对应为 BaseV10Converter) 本身就实现了 Converter。在 BaseV20Converter 抽象类中默认实现了 Converter 的方法
public void setConverterManager(ConverterManager config) { }
具体的 Converter 只要专心去实现接口 Converter 中的另两个方法:
Object convertInbound(Class paramType, InboundVariable data, InboundContext inctx) throws MarshallException;
OutboundVariable convertOutbound(Object data, OutboundContext outctx) throws MarshallException;
运行时,它们相应的被 ConvertManager(默认为 DefaultConvertManager) 的
Object convertInbound(Class paramType, InboundVariable iv, InboundContext inctx, TypeHintContext incc) throws MarshallException
OutboundVariable convertOutbound(Object object, OutboundContext outctx) throws MarshallException
来调用。
DWR 对每个参数或返回值至少会应用一次 Converter,但对于复杂的类型会递归的调用 Converter,比如,要完成
JS "{id:123, name: 'Unmi', blogs:['http://unmi.blogjava.net','http://blog.csdn.net/kypfos']}" 到 Java 的 Person{int id, String name, String[] blogs;} 的转换,就会使用到 bean->primitive->array 三个 Converter。
4. 定制自己的 Converter
基本上 DWR 内置的 Converter 就够用的,但也有可能需要定定自己的 Converter。从 DWR 的 Converter 实现来看,一般会用两种方式:
1) extends BaseV20Converter implements Converter,实现 Converter 的 converterInbound() 和 converterOutbound() 方法
2) extends BasicObjectConverter implements Converter,或继承 BeanConverter,实现 BasicObjectConverter 的 getPropertyMapFromObject(),getPropertyMapFromObject() 和 createTypeHintContext() 方法。
前一种方式,请参照 org.directwebremoting.convert.DateConverter 的源码实现:
convertInbound() 由 JS 的字符串转换成要求的 Date、Time、Timestamp 或 Calender 对像。
convertOubound() 把 Java 的类型转换成 JS 的 new Date() 类型,注意返回值的写法:
return new SimpleOutboundVariable("new Date(" + millis + ")", outctx, true);
第二种继承 BasicObjectConverter 或是 BeanConverter 的做法,可参考 BeanConverter 的源码实现。表现在 JSON 和 Java 对象间的转换,要是引入解析 JSON 的 JAR 包或许能有不少帮助。
定制 Converter 的内容讲的很少,主要是真有这方面的需要的时候请参考 DWR 的相关源码,实际中理解各个接口方法参数的意义,及返回值的要求。对待开源组件还是要保持阅读源码的好习惯。
好啦,自己的 Converter 写好,需要注册,需要应用。我们还是参考 DWR 的做法,写在自己的 dwr.xml 中。例如定制了 com.unmi.dwr.converter.SpecialConverter,要对 com.unmi.model.SpecialObject 进行出入类型的转换,就这么写:
<converter id="special" class="com.unmi.dwr.converter.SpecialConverter"/> 注册了 special 转换器
<convert converter="special" match="com.unmi.model.SpecialObject"/> 应用注册的 special 转换器应用到 com.unmi.model.SpecialObject 类型
5. 小结
用 DWR 其实也有段时日了,未曾系统的学,总是遇一问题、扫除一个,不免也会去找找相关更系统的资料。然而着下此篇的动机是上周六在书城翻了下 《 DWR 实战》,它实际讲 DWR 本身的较少。最后我第一个想了解了是 DWR 能完成 JS 与 Java 间什么类型的转换,第一手的资料网上也没搜索到,于是进到源码中去,亲身历练,也更加深了印象。
读者也许和我一样目的,只想看看内置的转换器有哪些,能转换哪些类型,那就只需看最为抢眼的那张表格吧。需要定制 Converter 应该很少,就像我们很少定制 Struts 的 Converter、Hibernate 的 UserType 和 iBatis 的 TypeHandler 一样。因此也就对定制 DWR 的 Converter 所用篇幅不多。
对待开源,自己总有个习惯就是必须有相关的源代码伴随在它身边。开源组件的使用一般不难,碰到问题,既然源码都掌握了,我想总能从源码中找出原因来。尚且,对这样的知名组件越发深入,就更能嚼出许多味多。
参考:DWR 2.0.5 的源代码,对 DWR 项目进行单步调试
本文链接 https://yanbin.blog/dwr-converter-mechanism-application/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。