转换器
转换器在客户端和服务器之间转换数据.
下面这些转换器有单独章节介绍
- Array Converter
- Bean and Object Converters
- Collection Converter
- Enum Converter
- DOM Objects
- Hibernate整合
- Servlet Objects (HttpServletRequest, HttpSession, etc)
基础的转换器
原生类型,String,像BigDecimal这样的简单对象的转换器已经有了。你不需要在dwr.xml中<allow>部分的<convert>中定义。它们默认支持。
默认支持的类型包括: 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, java.math.BigInteger, java.math.BigDecimal 和 java.lang.String
Date转换器
Date转换器负责在Javascript的Date类型与Java中的Date类型(java.util.Date, java.sql.Date, java.sql.Times or java.sql.Timestamp)之间进行转换。同基础的转换器一样,DateConverter默认是支持的。
如果你有一个Javascript的字符串 (例如"01 Jan 2010") ,你想把它转换成Java的Date类型有两个办法:在javascript中用Date.parse()把它解析成Date类型,然后用DWR的DateConverter传递给服务器;或者把它作为字符串传递给Server,再用Java中的SimpleDateFormat(或者类似的)来解析。
同样,如果你有个Java的Date类型并且希望在HTML使用它。你可以先用SimpleDateFormat把它转换成字符串再使用。也可以直接传Date给Javascript,然后用Javascript格式化。第一种方式简单一些,尽管浪费了你的转换器,而且这样做也会是浏览器上的显示逻辑受到限制。其实后面的方法更好,也有一些工具可以帮你,例如:
其他对象
其实创建自己的转换器也很简单。Converter接口的Javadoc包含了信息。其实这种需要很少出现。在你写自己的Converter之前先看看BeanConverter,它有可能就是你要的。
数组转换器
数组实体不太容易理解。默认情况下DWR能转换所有原生类型的数组,还有所有marshallable对象的数组。这些marshallable对象包括前面介绍的String和Date类型。
高级Java程序员应该能够理解为什么match属性看上去很怪。
<convert converter="array" match="[Z"/> <convert converter="array" match="[B"/> <convert converter="array" match="[S"/> <convert converter="array" match="[I"/> <convert converter="array" match="[J"/> <convert converter="array" match="[F"/> <convert converter="array" match="[D"/> <convert converter="array" match="[C"/> <convert converter="array" match="[L*"/>
上面没有解释*的作用 - 它是通配符,表示匹配接下来的所有字符串。这也是DWR可以转换任意类型的数组的原因。
Bean 和 Object 转换器
两个没有默认打开的转换器是Bean 和 Object 转换器。Bean转换器可以把POJO转换成Javascript的接合数组(类似与Java中的Map),或者反向转换。这个转换器默认情况下是没打开的,因为DWR要获得你的允许才能动你的代码。
Object转换器很相似,不同的是它直接应用于对象的成员,而不是通过getter和setter方法。下面的例子都是可以用object来替换bean的来直接访问对象成员。
如果你有一个在 <create ...> 中声明的远程调用Bean。它有个一参数也是一个bean,并且这个bean有一个setter存在一些安全隐患,那么攻击者就可能利用这一点。
你可以为某一个单独的类打开转换器:
<convert converter="bean" match="your.full.package.BeanName"/>
如果要允许转换一个包或者子包下面的所有类,可以这样写:
<convert converter="bean" match="your.full.package.*"/>
显而易见,这样写是允许转换所有的JavaBean:
<convert converter="bean" match="*"/>
BeanConverter 和 JavaBeans 规范
用于被BeanConverter转换的Bean必须符合JavaBeans的规范,因为转换器用的是Introspection,而不是Reflection。这就是说属性要符合一下条件:有getter和setter,setter有一个参数,并且这个参数的类型是getter的返回类型。setter应该返回void,getter应该没有任何参数。setter没有重载。以上这些属于常识。如果你用的不是JavaBean,那么你应该用ObjectConverter.
设置Javascript变量
DWR可以把Javascript对象(又名maps,或联合数组)转换成JavaBean或者Java对象。
一个简单的例子可以帮助你。假设你有下面的Java代码:
public class Remoted {
public void setPerson(Person p) {
// ...
}
}
public class Person {
public void setName(String name) { ... }
public void setAge(int age) { ... }
// ...
}
如果这个Remoted已经被配置成Creator了,Persion类也定义了BeanConverter,那么你可以通过下面的方式调用Java代码:
var p = { name:"Fred", age:21 };
Remoted.setPerson(p);
限制属性转换
就像你可以在creator的定义中剔出一些方法一样,converter也有类似的定义。
限制属性转换仅仅对于Bean有意义,很明显原生类型是不要需要这个功能的,所以只有BeanConverter及其子类型(HibernateBeanConverter))有这个功能。
语法是这样的:
<convert converter="bean" match="com.example.Fred"> <param name="exclude" value="property1, property2"/> </convert>
这就保证了DWR不会调用 fred.getProperty1() 和fred.getProperty2两个方法。另外如果你喜欢"白名单"而不是"黑名单"的话:
<convert converter="bean" match="com.example.Fred"> <param name="include" value="property1, property2"/> </convert>
安全上比较好的设计是使用"白名单"而不是"黑名单"。
对象的私有成员
通过'object'转换器的参数的一个名为force的参数,可以让DWR通过反射来访问对象私有成员。
语法是这样的:
<convert converter="object" match="com.example.Fred"> <param name="force" value="true"/> </convert>
直到DWR1.1.3,这里有一个bug,public的field反而不能被发现,所以你需要在public成员上设置force=true。
集合类型转换器
有个两个默认的转换器,针对Map和Collection:
<convert converter="collection" match="java.util.Collection"/> <convert converter="map" match="java.util.Map"/>
一般来说这些转换器可以递归转换它们的内容。
但是也有两点不足之处:
- 仅仅用反射机制是没有方法明确集合里面是什么类型的。所以这两个转换器不能把集合里面的东西转换成有意义的Javascript对象。
- 不能明确是那种类型的集合。
虽然我们不能让他们自动的起作用,我们可以在dwr.xml中用signatures语法声明它们类型,使之正确转换。
枚举类型转换器
枚举类型转换器默认是没有打开的。它在Java5中的Enum和Javascript的String之间进行转换。这个转换器默认关闭是因为DWR要在转换你的代码之前得到你的同意。
枚举类型转换器是DWR 1.1版以后才支持的。
你可以这样设置来打开这个转换器:
<convert converter="enum" match="your.full.package.EnumName"/>
设置Javascript变量
一个简单的例子。假设你有下面的Java代码:
public class Remoted {
public void setStatus(Status p) {
// ...
}
}
enum Status {
PASS,
FAIL,
}
如果Remoted类已经配置好Creator,并且Status枚举类型已经设置了EnumConverter。那么你就可以在javascript中这样调用:
Remoted.setStatus("PASS");
如果没有匹配的类型,就会抛出异常。
DOM 对象
DWR可以自动转换来之DOM,DOM4J,JDOM和XOM的DOM树。你可以简单得用上面这些类库返回一个Document、Element或者Node,DWR会把他们自动转换成浏览器的DOM对象。
在程序启动的时候会有一个常见的关于JDOM转换器的警告,你可以放心的忽略它,除非你要用JDOM:
INFO: Missing classdef for converter 'jdom'. Failed to load uk.ltd.getahead.dwr.convert.JDOMConverter. Cause: org/jdom/Document
因为DWR没有办法知道你是否想用JDOM,所以这个信息设在INFO级别的。
如果你曾经尝试过使用JDOM,你会意识到在这种情况下这个转换器不可用的 - 这也是我们显示这个信息的原因。
exist-db.org
我相信DWR能同exist-db很好的工作,因为它是建立在W3C DOM之上的,而DWR也支持这个。
DWR 和 Hibernate
让DWR和Hibernate正常工作的检查列表
- 确保你使用的是最新的DWR。Hibernate转换器是新东西,所以你需要下载最新的
- 确保你已经明白开始指南上所写的内容。
- 确保你的Hiberante在没有DWR的时候工作正常。
- 如果是Spring和Hibernate一起使用,那么你最好先了解一下如何将整合Spring。
- 配置DWR,使之与Hibernate一起工作。 (看下面)。
- 查看演示页面:http://localhost:8080/YOUR-WEBAPP/dwr,确定Spring的Bean可以出现。
HibernateBeanConverter
这个转换器同标准的BeanConverter非常相似,不同之处在于我们可以决定如何处理延迟加载。
使用HibernateBeanConverter可能会带来如下风险:
- 架构: HibernateBeanConverter不符合MVC模式,所以不能把对象在数据曾和表现曾之间进行隔离。这个风险可以通过在上面加上独立的bean来减轻。
- 性能: DWR试图通过相同的序列化方式来转换所有可以得到的属性(除了DWR仅仅读JavaBean属性的时候)。所以可能会出现通过HTTP序列化了你的整个数据的情况。通常这并不是你想要的。要减少这一风险可以使用BeanConverter(HibernateBeanConverter衍生于它)的排除某些属性的功能:
<param name="exclude" value="propertyToExclude1, propertyToExclude2"/>
HibernateBeanConverter会尝试不去读取没有初始化的属性。如果你只是想读取所有的东西那么应该使用BeanConverter。
建议使用Hibernate3,实际上Hibernate2一下的情况,你会发现你得到的都是空的Bean。
Session管理
如果你使用Hibernate对象,你需要知道每一个DWR请求都是一个新的Servlet请求,所以你需要保证为每个请求打开一个Hiberante的Session。
如果你用Spring,那么可以很方便的使用Spring里面的OpenSessionInViewFilter,它可以保证为每个请求打开一个Hiberante的Session。类似的解决方案在其它Framework中也存在。