最近开发一个后台应用,之前一般都是使用 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 里面的参数转换器,还能够从我的解决问题的思考方式获得收获。

dwr 自定义转换器的更多相关文章

  1. HTML5 WebSocket实现点对点聊天的示例代码

    这篇文章主要介绍了HTML5 WebSocket实现点对点聊天的示例代码的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. swift – 使用“如果让…”与许多表达式

    Swift1.2更新从Swift1.2开始,如果允许允许展开多个可选项,那么现在可以写下这个,如下例所示:您甚至可以交换条件,如:以前在Swift1.2之前有效没有一个丑陋的力量包装,你可以这样做:实际上仍然很冗长这是因为可选类型的表单类型?实际上是可选的的缩写,这是一个大致如下的枚举:然后,您可以使用模式匹配作为任何其他枚举。

  3. android – Airbnb探索屏幕等工具栏中的自定义视图

    我想在应用程序(工具栏)的顶部栏区域添加多个输入字段,以便我的应用程序进行搜索.我看到Airbnb做到了最好!我在CoordinatorLayout中尝试了使用AppBarLayout的各种场景,但都失败了.是否有可能获得相同或类似的效果?

  4. QuickBlox WebRtc VideoChat Android

    几天我正在研究quickblox.i让对手观点在我的观点下面,如this.它工作正常,但是当我保持像skype这样的视图时:–对手视图在全屏幕上,我的视图位于对手视图的右上角,它只渲染一个最终渲染的视图.我在quickblox网站上看了quickbloxwebrtc示例.我看到了该示例中的代码,但它包含了一些会议对话,其中包含一些复杂的循环视图编码,对我来说,单个一对一的谈话是必需的,任何人都可以

  5. android – 如何在AlertDialog中显示CalendarView?

    我正在尝试在警报对话框中显示CalendarView,但所有显示的都是月/年和星期几.这些是布局文件的内容:这是我用于将布局添加到AlertDialog的代码:任何帮助都会非常感激,因为我完全被难倒了.该应用程序没有给我任何错误继续.解决方法要正确显示日历,需要最小高度.其余代码工作正常.

  6. 无法使用android.support.v7.widget.AppCompatTextView实例化以下类

    最近我在Android工作室的应用程序中将我的sdk从25改为26,我在我的所有xml中都遇到了这个奇怪的错误.目前错误并没有以我能看到的任何方式影响我的应用程序,但是每次我必须编辑或更改xml中的某些内容时都很烦人.这里的错误:无法实例化以下类–android.support.v7.widget.AppCompatTextView我的朋友:编辑:添加了一些我的xml代码解决方法我也收到了这个错误

  7. android软键盘出现时会破坏布局

    解决方法由于输入进入你的EditText,它总是会移动一些东西.考虑使用两种布局,一种用于键盘关闭时,一种用于键盘启动时.使用ViewSwitcher可能会有所帮助.

  8. android – 我可以像这样创建一个nestedScroll布局吗?

    我认为它可以通过nestedScrollingChildnestedScrollingParent来实现.但我无法理解他们.谁可以帮助我!产品经理坚持设计.ScrollView包含LinearLayout,“TabLayout”和ViewPager.ViewPager包含2个包含RecyclerView的片段或仅包含2个RecyclerView.当ScrollView滚动到Bottom时,Recy

  9. android – 在NestedScrollView onBindViewHolder中的RecyclerView调用所有getItemCount大小

    当我将RecyclerView放入nestedScrollView时,然后onBindViewHolder调用所有行,比如说我有一个大小为30的列表,那么即使没有滚动,也会同时为所有30行调用onBindViewHolder我的xml是但如果我删除nestedScrollView它正常工作.解决方法高度问题引起的问题.1)编辑nestedScrollView&RecyclerView如下:2)确保

  10. android – 将ImageView对齐到屏幕的右上角

    我的布局中有一个imageView,它可以作为不可缩放的背景.但我需要将我的图像放在屏幕的右上角.我现在将我的图像放在屏幕的左上角.这是我对imageview的xml:我也尝试将它放在一个linearlayout中,它具有gravity=“top|right”但是不会删除图片周围的空白区域,因为它具有match_parent大小.我还需要保持图片的比例,所以我不能做fill_parent.我希望有人可以帮助我!解决方法也许加你的ImageView?我有类似的问题.

随机推荐

  1. xe-ajax-mock 前端虚拟服务

    最新版本见Github,点击查看历史版本基于XEAjax扩展的Mock虚拟服务插件;对于前后端分离的开发模式,ajax+mock使前端不再依赖后端接口开发效率更高。CDN使用script方式安装,XEAjaxMock会定义为全局变量生产环境请使用xe-ajax-mock.min.js,更小的压缩版本,可以带来更快的速度体验。

  2. vue 使用 xe-ajax

    安装完成后自动挂载在vue实例this.$ajaxCDN安装使用script方式安装,VXEAjax会定义为全局变量生产环境请使用vxe-ajax.min.js,更小的压缩版本,可以带来更快的速度体验。cdnjs获取最新版本点击浏览已发布的所有npm包源码unpkg获取最新版本点击浏览已发布的所有npm包源码AMD安装require.js安装示例ES6Module安装通过Vue.use()来全局安装示例./Home.vue

  3. AJAX POST数据中文乱码解决

    前端使用encodeURI进行编码后台java.net.URLDecoder进行解码编解码工具

  4. Koa2框架利用CORS完成跨域ajax请求

    实现跨域ajax请求的方式有很多,其中一个是利用CORS,而这个方法关键是在服务器端进行配置。本文仅对能够完成正常跨域ajax响应的,最基本的配置进行说明。这样OPTIONS请求就能够通过了。至此为止,相当于仅仅完成了预检,还没发送真正的请求呢。

  5. form提交时,ajax上传文件并更新到&lt;input&gt;中的value字段

  6. ajax的cache作用

    filePath="+escape;},error:{alert;}});解决方案:1.加cache:false2.url加随机数正常代码:网上高人解读:cache的作用就是第一次请求完毕之后,如果再次去请求,可以直接从缓存里面读取而不是再到服务器端读取。

  7. 浅谈ajax上传文件属性contentType = false

    默认值为contentType="application/x-www-form-urlencoded".在默认情况下,内容编码类型满足大多数情况。在这里,我们主要谈谈contentType=false.在使用ajax上传文件时:在其中先封装了一个formData对象,然后使用post方法将文件传给服务器。说到这,我们发现在JQueryajax()方法中我们使contentType=false,这不是冲突了吗?这就是因为当我们在form标签中设置了enctype=“multipart/form-data”,

  8. 909422229_ajaxFileUpload上传文件

    ajaxFileUpload.js很多同名的,因为做出来一个很容易。我上github搜AjaxFileUpload出来很多类似js。ajaxFileUpload是一个异步上传文件的jQuery插件传一个不知道什么版本的上来,以后不用到处找了。语法:$.ajaxFileUploadoptions参数说明:1、url上传处理程序地址。2,fileElementId需要上传的文件域的ID,即的ID。3,secureuri是否启用安全提交,默认为false。4,dataType服务器返回的数据类型。6,error

  9. AJAX-Cache:一款好用的Ajax缓存插件

    原文链接AJAX-Cache是什么Ajax是前端开发必不可少的数据获取手段,在频繁的异步请求业务中,我们往往需要利用“缓存”提升界面响应速度,减少网络资源占用。AJAX-Cache是一款jQuery缓存插件,可以为$.ajax()方法扩展缓存功能。

  10. jsf – Ajax update/render在已渲染属性的组件上不起作用

    我试图ajax更新一个有条件渲染的组件。我可以确保#{user}实际上是可用的。这是怎么引起的,我该如何解决呢?必须始终在ajax可以重新呈现之前呈现组件。Ajax正在使用JavaScriptdocument.getElementById()来查找需要更新的组件。但是如果JSF没有将组件放在第一位,那么JavaScript找不到要更新的内容。解决方案是简单地引用总是渲染的父组件。

返回
顶部