最近開發(fā)一個后臺應(yīng)用奕纫,之前一般都是使用 AJAX 來進(jìn)行數(shù)據(jù)交互。但是項目中使用的是 dwr 來進(jìn)行前后端交互辞做。本文不是講如何使用 dwr莺掠,而是想分享一下使用 dwr 遇到的問題以及解決問題的思路露懒。
1闯冷、什么是 dwr
DWR 是一個開源的類庫,可以幫助開發(fā)人員開發(fā)包含AJAX技術(shù)的網(wǎng)站.它可以允許在瀏覽器里的代碼(javascript)使用運(yùn)行在 WEB服務(wù)器上的 JAVA 函數(shù),就像它就在瀏覽器里一樣.
它包含兩個主要的部分:允許 JavaScript 從 WEB 服務(wù)器上一個遵循了 AJAX 原則的 Servlet (小應(yīng)用程序)中獲取數(shù)據(jù).另外一方面一個 JavaScript 庫可以幫助網(wǎng)站開發(fā)人員輕松地利用獲取的數(shù)據(jù)來動態(tài)改變網(wǎng)頁的內(nèi)容.
dwr 采取了一個類似 AJAX 的新方法來動態(tài)生成基于 JAVA 類的 JavaScript 代碼.這樣 WEB 開發(fā)人員就可以在 JavaScript 里使用Java 代碼就像它們是瀏覽器的本地代碼(客戶端代碼)一樣;但是 Java 代碼運(yùn)行在 WEB 服務(wù)器端而且可以自由訪問 WEB 服務(wù)器的資源.出于安全的理由,WEB 開發(fā)者必須適當(dāng)?shù)嘏渲媚男?Java 類可以安全的被外部使用.
2、遇到的問題
在使用 dwr 的時候懈词,定義了一個接口用于前后端交互蛇耀。接口的定義如下:
Object method(String code, Integer id)
但是在 js 調(diào)用的時候,如果我傳入空值也就是''
坎弯,最終調(diào)用后面method
方法的時候 id 會被設(shè)置成 0
蒂窒。后面我又嘗試設(shè)置成null
,就會報如下錯誤:
但是我就想傳到 method
方法的時候 id 的值是 null
;
3荞怒、分析問題
因?yàn)樯厦嬗绣e誤提示,我就猜這個異常是 dwr 框架報出的秧秉。所以我就 copy 出Format error converting
這幾個關(guān)鍵字褐桌,然后通過 idea 進(jìn)行全局搜索。如果根據(jù)關(guān)鍵字搜索到了:
這些信息都存在于 dwr jar 包里的 message.properties
里象迎,它其實(shí)是 dwr 里面用于定義信息的模板文件荧嵌,類似于 i18n
. 里面涉及到的 BigNumberConverter
呛踊、DateConverter
、PrimitiveConverter
這三個類都有一個共同點(diǎn)都是實(shí)現(xiàn)于 Converter啦撮。到了這里大概就有一個思路就了谭网,就在 dwr 在進(jìn)行類型轉(zhuǎn)換的時候拋的異常。然后在這三個類里面的convertInbound
進(jìn)行點(diǎn)斷點(diǎn)赃春,發(fā)現(xiàn)出現(xiàn)的問題類是
PrimitiveConverter
愉择。它的處理邏輯如下:
if (paramType == Integer.TYPE || paramType == Integer.class)
{
if (value.length() == 0)
{
return new Integer(0);
}
return new Integer(value.trim());
}
然后看了一下 PrimitiveConverter#convertInbound
的調(diào)用鏈,看一下是從哪里獲取到這個轉(zhuǎn)換器的织中。
然后看了一下獲取轉(zhuǎn)換器的邏輯:
private Converter getConverter(Class paramType)
{
// Can we find a converter assignable to paramType in the HashMap?
Converter converter = getConverterAssignableFrom(paramType);
...
}
private Converter getConverterAssignableFrom(Class paramType)
{
if (paramType == null)
{
return null;
}
String lookup = paramType.getName();
// Can we find the converter for paramType in the converters HashMap?
Converter converter = (Converter) converters.get(lookup);
if (converter != null)
{
return converter;
}
}
它是根據(jù)類的類全名 (Integer 對應(yīng) java.lang.Integer
) , 從DefaultConverterManager#converters
屬性中獲取锥涕,converters 是一個 HashMap。因這個屬性并沒有初始化狭吼,所以我就猜測應(yīng)該有方法來添加這個值层坠。然后我就看了一下DefaultConverterManager
的方法列表。
然后就看到了 addConverter
方法刁笙。
public void addConverter(String match, String type, Map params) {
Class clazz = (Class) converterTypes.get(type);
if (clazz == null){
return;
}
Converter converter = (Converter) clazz.newInstance();
converter.setConverterManager(this);
for (Iterator it = params.entrySet().iterator(); it.hasNext();)
{
Map.Entry entry = (Entry) it.next();
String key = (String) entry.getKey();
Object value = entry.getValue();
try
{
LocalUtil.setProperty(converter, key, value);
}
catch (NoSuchMethodException ex){
...
}
}
// add the converter for the specified match
addConverter(match, converter);
}
public void addConverter(String match, Converter converter) {
// Check that we don't have this one already
Converter other = (Converter) converters.get(match);
if (other != null)
{
log.warn("Clash of converters for " + match + ". Using " + converter.getClass().getName() + " in place of " + other.getClass().getName());
}
converters.put(match, converter);
}
首先會從 converterTypes 里面根據(jù) type破花,拿到這個對應(yīng)的 Class。然后以 match 為 key疲吸,轉(zhuǎn)換器為 value 保存到 converters
用于參數(shù)轉(zhuǎn)換的時候使用座每。converterTypes
其實(shí)就是是一個 Map,然后我在 addConverter
方法里面打了一個斷點(diǎn)磅氨。查看了 converterTypes
以及converters
這個屬性里面的值:
下面是 converterTypes
屬性的值:
下面是 converters
屬性的值:
通過前面我們可以看到 java.lang.Integer
對應(yīng)的轉(zhuǎn)換器是 DefaultConverterManager#converterTypes
Map 里面 primitive
為 key 的值 PrimitiveConverter
尺栖。然后我看了一下```DefaultConverterManager#addConverterType`` 的調(diào)用鏈。
一共會有兩個地方會到 DefaultConverterManager#addConverterType
方法烦租。其實(shí)最終都是在 AbstractDWRServlet#init
進(jìn)行資源加載延赌。
首先 AbstractDWRServlet
是一個 Servlet,在 Servlet 初始化的時候會調(diào)用且僅會調(diào)用一次 Servlet#init
方法叉橱。然后兩次調(diào)用DefaultConverterManager#addConverterType
都會從AbstractDWRServlet#init
發(fā)起挫以。
在分析這個配置文件之前我們先來看一下 DefaultConverterManager#addConverter
的調(diào)用鏈:
同樣我們可以看到,它也是從 /uk/ltd/getahead/dwr/dwr.xml
以及 /WEB-INF/dwr.xml
配置文件里面讀取數(shù)據(jù)加載的窃祝。下面我們就來分析一下 /uk/ltd/getahead/dwr/dwr.xml
掐松。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN" "http://www.getahead.ltd.uk/dwr/dwr10.dtd">
<dwr>
<init>
<creator id="jsf" class="uk.ltd.getahead.dwr.create.JsfCreator"/>
<creator id="none" class="uk.ltd.getahead.dwr.create.NullCreator"/>
<creator id="new" class="uk.ltd.getahead.dwr.create.NewCreator"/>
<creator id="pageflow" class="uk.ltd.getahead.dwr.create.PageFlowCreator"/>
<creator id="spring" class="uk.ltd.getahead.dwr.create.SpringCreator"/>
<creator id="script" class="uk.ltd.getahead.dwr.create.ScriptedCreator"/>
<creator id="struts" class="uk.ltd.getahead.dwr.create.StrutsCreator"/>
<converter id="null" class="uk.ltd.getahead.dwr.convert.NullConverter"/>
<converter id="enum" class="uk.ltd.getahead.dwr.convert.EnumConverter"/>
<converter id="primitive" class="uk.ltd.getahead.dwr.convert.PrimitiveConverter"/>
<converter id="bignumber" class="uk.ltd.getahead.dwr.convert.BigNumberConverter"/>
<converter id="string" class="uk.ltd.getahead.dwr.convert.StringConverter"/>
<converter id="array" class="uk.ltd.getahead.dwr.convert.ArrayConverter"/>
<converter id="map" class="uk.ltd.getahead.dwr.convert.MapConverter"/>
<converter id="collection" class="uk.ltd.getahead.dwr.convert.CollectionConverter"/>
<converter id="date" class="uk.ltd.getahead.dwr.convert.DateConverter"/>
<converter id="dom" class="uk.ltd.getahead.dwr.convert.DOMConverter"/>
<converter id="dom4j" class="uk.ltd.getahead.dwr.convert.DOM4JConverter"/>
<converter id="jdom" class="uk.ltd.getahead.dwr.convert.JDOMConverter"/>
<converter id="xom" class="uk.ltd.getahead.dwr.convert.XOMConverter"/>
<converter id="servlet" class="uk.ltd.getahead.dwr.convert.ServletConverter"/>
<converter id="bean" class="uk.ltd.getahead.dwr.convert.BeanConverter"/>
<converter id="object" class="uk.ltd.getahead.dwr.convert.ObjectConverter"/>
<converter id="hibernate" class="uk.ltd.getahead.dwr.convert.HibernateBeanConverter"/>
</init>
<allow>
<convert converter="null" match="void"/>
<convert converter="null" match="java.lang.Void"/>
<convert converter="primitive" match="boolean"/>
<convert converter="primitive" match="byte"/>
<convert converter="primitive" match="short"/>
<convert converter="primitive" match="int"/>
<convert converter="primitive" match="long"/>
<convert converter="primitive" match="float"/>
<convert converter="primitive" match="double"/>
<convert converter="primitive" match="char"/>
<convert converter="primitive" match="java.lang.Boolean"/>
<convert converter="primitive" match="java.lang.Byte"/>
<convert converter="primitive" match="java.lang.Short"/>
<convert converter="primitive" match="java.lang.Integer"/>
<convert converter="primitive" match="java.lang.Long"/>
<convert converter="primitive" match="java.lang.Float"/>
<convert converter="primitive" match="java.lang.Double"/>
<convert converter="primitive" match="java.lang.Character"/>
<convert converter="bignumber" match="java.math.BigInteger"/>
<convert converter="bignumber" match="java.math.BigDecimal"/>
<convert converter="string" match="java.lang.String"/>
<convert converter="date" match="java.util.Date"/>
<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*"/>
<!--
The catch for the next 2 is that we really mean java.util.Collection<String>
and java.util.Map<String, String> but we need to do more work before this
syntax is enabled
-->
<convert converter="collection" match="java.util.Collection"/>
<convert converter="map" match="java.util.Map"/>
<convert converter="dom" match="org.w3c.dom.Node"/>
<convert converter="dom" match="org.w3c.dom.Element"/>
<convert converter="dom" match="org.w3c.dom.Document"/>
<convert converter="dom4j" match="org.dom4j.Document"/>
<convert converter="dom4j" match="org.dom4j.Element"/>
<convert converter="dom4j" match="org.dom4j.Node"/>
<convert converter="jdom" match="org.jdom.Document"/>
<convert converter="jdom" match="org.jdom.Element"/>
<convert converter="xom" match="nu.xom.Document"/>
<convert converter="xom" match="nu.xom.Element"/>
<convert converter="xom" match="nu.xom.Node"/>
<convert converter="servlet" match="javax.servlet.ServletConfig"/>
<convert converter="servlet" match="javax.servlet.ServletContext"/>
<convert converter="servlet" match="javax.servlet.http.HttpServletRequest"/>
<convert converter="servlet" match="javax.servlet.http.HttpServletResponse"/>
<convert converter="servlet" match="javax.servlet.http.HttpSession"/>
</allow>
</dwr>
在 init
元素會初始化完成 DefaultConverterManager#converterTypes
屬性的值 key 為init
的 id
,然后再解析allow
元素里面的convert
子元素粪小,通過converter
為 key 去拿 DefaultConverterManager#converterTypes
里面的值大磺,最終以 match 為 key,獲取到的值也就是對應(yīng)的轉(zhuǎn)換器為值探膊,初始化完成DefaultConverterManager#converters
屬性的值杠愧。
然后 /WEB-INF/dwr.xml
里面的解析邏輯與上面的一樣。
4逞壁、解決問題
通過上面的的分析流济,進(jìn)行轉(zhuǎn)換器添加的時候锐锣,也就是調(diào)用DefaultConverterManager#addConverter
方法的時候,當(dāng)遇到DefaultConverterManager#converters
已有參數(shù)轉(zhuǎn)換器的時候 dwr 的邏輯是直接覆蓋:
所以我們只需要在自定義的配置文件中定義好 Integer 的轉(zhuǎn)換邏輯绳瘟,然后在覆蓋DefaultConverterManager#converters
里面 java.lang.Integer 的轉(zhuǎn)換器就行了雕憔。
定義一個java.lang.Integer
的轉(zhuǎn)換器:
public class MyPrimitiveConverter extends PrimitiveConverter {
@Override
public Object convertInbound(Class paramType, InboundVariable iv, InboundContext inctx) throws ConversionException {
if(paramType == Integer.class || paramType == Integer.TYPE) {
String value = iv.getValue();
if("null".equals(value) || StringUtil.isBlank(value)) {
return null;
}
}
return super.convertInbound(paramType, iv, inctx);
}
}
其實(shí)類很簡單,就是把 PrimitiveConverter 的邏輯替換成上面的邏輯糖声。
if (paramType == Integer.TYPE || paramType == Integer.class)
{
if (value.length() == 0)
{
return new Integer(0);
}
return new Integer(value.trim());
}
配置自定義解析文件/WEB-INF/dwr.xml
:
/WEB-INF/dwr.xml
<?xml version="1.0" encoding="UTF-8"?>
<dwr>
<init>
<converter id="primitive" class="com.weihui.basis.web.config.dwr.MyPrimitiveConverter" />
</init>
</allow>
...
<convert match="java.lang.Integer" converter="primitive" />
</allow>
</dwr>
然后我們再來看一下 DefaultConverterManager#converters
里面 java.lang.Integer
對應(yīng)的轉(zhuǎn)換器:
ok ! 大功告成斤彼。希望這篇 blog 不僅是讓你學(xué)會了替換 dwr 里面的參數(shù)轉(zhuǎn)換器,還能夠從我的解決問題的思考方式獲得收獲姨丈。