dwr cover converter

最近开发一个后台应用,之前一般都是使用 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,就会报如下错误:

dwr-error.png

但是我就想传到 method 方法的时候 id 的值是 null;

3、分析问题

因为上面有错误提示,我就猜这个异常是 dwr 框架报出的。所以我就 copy 出Format error converting 这几个关键字,然后通过 idea 进行全局搜索。如果根据关键字搜索到了:

dwr.png

这些信息都存在于 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 的调用链,看一下是从哪里获取到这个转换器的。

converter-manager.png

然后看了一下获取转换器的逻辑:

    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的方法列表。

converter-add.png

然后就看到了 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 属性的值:

converter-types.png

下面是 converters 属性的值:

converters.png

通过前面我们可以看到 java.lang.Integer 对应的转换器是 DefaultConverterManager#converterTypes Map 里面 primitive 为 key 的值 PrimitiveConverter。然后我看了一下```DefaultConverterManager#addConverterType`` 的调用链。

init.png

一共会有两个地方会到 DefaultConverterManager#addConverterType 方法。其实最终都是在 AbstractDWRServlet#init 进行资源加载。

init.png

首先 AbstractDWRServlet 是一个 Servlet,在 Servlet 初始化的时候会调用且仅会调用一次 Servlet#init方法。然后两次调用DefaultConverterManager#addConverterType都会从AbstractDWRServlet#init发起。

在分析这个配置文件之前我们先来看一下 DefaultConverterManager#addConverter 的调用链:

init.png

同样我们可以看到,它也是从 /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 的逻辑是直接覆盖:

xxx.png

所以我们只需要在自定义的配置文件中定义好 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 对应的转换器:

xxx.png

ok ! 大功告成。希望这篇 blog 不仅是让你学会了替换 dwr 里面的参数转换器,还能够从我的解决问题的思考方式获得收获。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,657评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,638评论 18 399
  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,523评论 0 4
  • 读《花田半亩》有感 倘若,这世上从来没有我, 那么又有什么遗憾,什么悲伤, 生命是跌撞的曲折,死亡是宁静的星。 归...
    寻租空间阅读 279评论 0 0
  • 接入 接入准备 参考http://baichuan.taobao.com/docs/doc.htm?spm=a3c...
    reezy阅读 29,342评论 6 14