dwr 自定義轉換器

最近開發一個後臺應用,之前一般都是使用 AJAX 來進行數據交互。但是項目中使用的是 dwr 來進行前後端交互。本文不是講如何使用 dwr,而是想分享一下使用 dwr 遇到的問題以及解決問題的思路。

1、什麼是 dwr

DWR 是一個開源的類庫,可以幫助開發人員開發包含AJAX技術的網站.它可以允許在瀏覽器裏的代碼(javascript)使用運行在 WEB服務器上的 JAVA 函數,就像它就在瀏覽器裏一樣.

它包含兩個主要的部分:允許 JavaScript 從 WEB 服務器上一個遵循了 AJAX 原則的 Servlet (小應用程序)中獲取數據.另外一方面一個 JavaScript 庫可以幫助網站開發人員輕鬆地利用獲取的數據來動態改變網頁的內容.

dwr 採取了一個類似 AJAX 的新方法來動態生成基於 JAVA 類的 JavaScript 代碼.這樣 WEB 開發人員就可以在 JavaScript 裏使用Java 代碼就像它們是瀏覽器的本地代碼(客戶端代碼)一樣;但是 Java 代碼運行在 WEB 服務器端而且可以自由訪問 WEB 服務器的資源.出於安全的理由,WEB 開發者必須適當地配置哪些 Java 類可以安全的被外部使用.

2、遇到的問題

在使用 dwr 的時候,定義了一個接口用於前後端交互。接口的定義如下:

Object method(String code, Integer id)

但是在 js 調用的時候,如果我傳入空值也就是'',最終調用後面method方法的時候 id 會被設置成 0。後面我又嘗試設置成null,就會報如下錯誤:

在這裏插入圖片描述

但是我就想傳到 method 方法的時候 id 的值是 null;

3、分析問題

因爲上面有錯誤提示,我就猜這個異常是 dwr 框架報出的。所以我就 copy 出Format error converting 這幾個關鍵字,然後通過 idea 進行全局搜索。如果根據關鍵字搜索到了:

在這裏插入圖片描述

這些信息都存在於 dwr jar 包裏的 message.properties 裏,它其實是 dwr 裏面用於定義信息的模板文件,類似於 i18n . 裏面涉及到的 BigNumberConverterDateConverterPrimitiveConverter這三個類都有一個共同點都是實現於 Converter。到了這裏大概就有一個思路就了,就在 dwr 在進行類型轉換的時候拋的異常。然後在這三個類裏面的convertInbound進行點斷點,發現出現的問題類是
PrimitiveConverter。它的處理邏輯如下:

if (paramType == Integer.TYPE || paramType == Integer.class)
{
	if (value.length() == 0)
	{
		return new Integer(0);
	}
	return new Integer(value.trim());
}

然後看了一下 PrimitiveConverter#convertInbound 的調用鏈,看一下是從哪裏獲取到這個轉換器的。

在這裏插入圖片描述

然後看了一下獲取轉換器的邏輯:

    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;
        }
    }

它是根據類的類全名 (Integer 對應 java.lang.Integer) , 從DefaultConverterManager#converters屬性中獲取,converters 是一個 HashMap。因這個屬性並沒有初始化,所以我就猜測應該有方法來添加這個值。然後我就看了一下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 裏面根據 type,拿到這個對應的 Class。然後以 match 爲 key,轉換器爲 value 保存到 converters 用於參數轉換的時候使用。converterTypes 其實就是是一個 Map,然後我在 addConverter 方法裏面打了一個斷點。查看了 converterTypes 以及converters這個屬性裏面的值:

下面是 converterTypes 屬性的值:
在這裏插入圖片描述

下面是 converters 屬性的值:
在這裏插入圖片描述

通過前面我們可以看到 java.lang.Integer 對應的轉換器是 DefaultConverterManager#converterTypes Map 裏面 primitive 爲 key 的值 PrimitiveConverter。然後我看了一下```DefaultConverterManager#addConverterType`` 的調用鏈。

在這裏插入圖片描述
一共會有兩個地方會到 DefaultConverterManager#addConverterType 方法。其實最終都是在 AbstractDWRServlet#init 進行資源加載。

在這裏插入圖片描述

首先 AbstractDWRServlet 是一個 Servlet,在 Servlet 初始化的時候會調用且僅會調用一次 Servlet#init方法。然後兩次調用DefaultConverterManager#addConverterType都會從AbstractDWRServlet#init發起。

在分析這個配置文件之前我們先來看一下 DefaultConverterManager#addConverter 的調用鏈:

在這裏插入圖片描述

同樣我們可以看到,它也是從 /uk/ltd/getahead/dwr/dwr.xml 以及 /WEB-INF/dwr.xml 配置文件裏面讀取數據加載的。下面我們就來分析一下 /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 爲initid,然後再解析allow 元素裏面的convert子元素,通過converter爲 key 去拿 DefaultConverterManager#converterTypes 裏面的值,最終以 match 爲 key,獲取到的值也就是對應的轉換器爲值,初始化完成DefaultConverterManager#converters 屬性的值。

然後 /WEB-INF/dwr.xml 裏面的解析邏輯與上面的一樣。

4、解決問題

通過上面的的分析,進行轉換器添加的時候,也就是調用DefaultConverterManager#addConverter方法的時候,當遇到DefaultConverterManager#converters 已有參數轉換器的時候 dwr 的邏輯是直接覆蓋:

在這裏插入圖片描述

所以我們只需要在自定義的配置文件中定義好 Integer 的轉換邏輯,然後在覆蓋DefaultConverterManager#converters裏面 java.lang.Integer 的轉換器就行了。

定義一個java.lang.Integer 的轉換器:

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);
    }
}

其實類很簡單,就是把 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 對應的轉換器:
在這裏插入圖片描述

ok ! 大功告成。希望這篇 blog 不僅是讓你學會了替換 dwr 裏面的參數轉換器,還能夠從我的解決問題的思考方式獲得收穫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章