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,就会报如下错误:

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 里面的参数转换器,还能够从我的解决问题的思考方式获得收获。

猜你喜欢

转载自blog.csdn.net/u012410733/article/details/80201578
dwr