随笔 - 42  文章 - 71  trackbacks - 0
<2008年7月>
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

常用链接

留言簿

随笔档案

文章分类

文章档案

搜索

  •  

最新评论

阅读排行榜

评论排行榜

AXIS 1.4 自定义序列化/反序列化类

Technorati 标签: axis,customized,serializer,deserializer,web service

现在在SOA被大肆鼓吹的时代,再加上确实企业级应用平台中不同系统间的整合越来越多,所以Web Service的地位日益上升。虽然效率上会有所折扣,但是毕竟是一个标准,而且主流的编程语言本身或者加上一些框架都支持Web Service,无论是Server端还是Client端。记得在2001年底就做过一个Java Web Service Client调用Delphi Web Service Server,但是现在后悔的是当时对于Web Service没有认真学习,只是停留在浅尝辄止的地步,正是应了古人的那句:“书到用时方觉少”啊。

现在的项目就是遇到这个情况,大量的跨系统的通讯都是通过Web Service。偏偏有些服务端编写的比较奇怪,而AXIS生成的客户端又更加的奇怪。遇到的问题是这样的,服务端返回的数据中,有几个类型为xsd:dateTime,AXIS映射到Java的类是java.util.Calendar。偏偏服务器有时候有的字段是不会带有任何数据的,有的字段是一个字符"T"(因为Web Service中传输dateTime类型的数据字符串格式为yyyy-MM-dd'T'hh:mm:ss.SSS,例如2008-07-24T23:49:15.000),也就是应该生成对应Java的null对象。可能是考虑到和.NET客户端的兼容性吧(.NET不熟悉,但是为了解决这个问题查看一些资料,都是说对于dateTime类型,如果是空,会在.NET的客户端出现问题),AXIS在处理无数据的dateTime类型的节点时,就粗暴的报错了。没办法,服务器端是没有办法改程序了,只好在客户端下手了,反正这几个字段对于我的客户端不是很重要,只要不要因为这几个特殊的数据影响了其它数据。

第一个反应就是修改AXIS的代码,但是这种方案是不到万不得已是不能用的,太危险了。然后就看AXIS的文档以及API,看到有typeMapping的配置项,但是对于其中的各个属性又没有详细阐述,网上的例子大部分都是针对服务器端的。经过询问其它同事,他们也遇到了类似的问题,他们定了自己的序列化/反序列化类,然后在AXIS产生的客户端Stub代码中以服务命名的方法,例如process方法插入自定义的序列化/反序列化类:

public XXResponse process(XXRequest req) throws java.rmi.RemoteException {
        if (super.cachedEndpoint == null) {
            throw new org.apache.axis.NoEndPointException();
        }
        org.apache.axis.client.Call _call = createCall();
        _call.setOperation(_operations[0]);
        _call.setUseSOAPAction(true);
        _call.setSOAPActionURI("process");
        _call.setEncodingStyle(null);
        _call.setProperty(org.apache.axis.client.Call.SEND_TYPE_ATTR,
                Boolean.FALSE);
        _call.setProperty(org.apache.axis.AxisEngine.PROP_DOMULTIREFS,
                Boolean.FALSE);
        _call
                .setSOAPVersion(org.apache.axis.soap.SOAPConstants.SOAP11_CONSTANTS);
        _call.setOperationName(new javax.xml.namespace.QName("", "process"));

WsUtil.prepareCall(_call); // 这一句是用来插入自定义的Serializer和Deserializer
        setRequestHeaders(_call);

看看WsUtil的prepareCall如何编写的:

public class WsUtil {
    public static void prepareCall(org.apache.axis.client.Call _call) {
        _call.registerTypeMapping(
                        java.util.Date.class,
                        new javax.xml.namespace.QName("http://www.w3.org/2001/XMLSchema", "dateTime"),
                            new CalendarSerializerFactory(java.util.Date.class, new javax.xml.namespace.QName(http://www.w3.org/2001/XMLSchema, "dateTime")),
                            new CalendarDeserializerFactory(java.util.Date.class, new javax.xml.namespace.QName(http://www.w3.org/2001/XMLSchema, "dateTime")), true);

        _call.registerTypeMapping(
                        java.util.Calendar.class,
                        new javax.xml.namespace.QName("http://www.w3.org/2001/XMLSchema", "dateTime"),
                        new CalendarSerializerFactory(java.util.Date.class,
                          new javax.xml.namespace.QName(http://www.w3.org/2001/XMLSchema, "dateTime")),
                          new CalendarDeserializerFactory(java.util.Date.class, new javax.xml.namespace.QName("http://www.w3.org/2001/XMLSchema","dateTime")), true);
    }

这里,通过调用org.apache.axis.client.Call对象的registerTypeMapping方法来插入自定义的Serializer和Deserializer。(以上代码感谢我不认识的那位同事无私的奉献和帮助)

但是这种方法有一个问题就是如果重新生成了客户端代码,需要在Stub类中插入,也是比较具有破坏性的。我就觉得总有一个方法是比较优雅的解决这个问题的,然后顺着这个思路继续往下找,发现可以在Service locator类,也即extends了org.apache.axis.client.Service的那个类来获取到TypeMappingRegistery,于是,不再修改Stub类,在调用者代码中,生成locator对象之后,调用注册TypeMapping的方法:

LabService locator = new LabServiceLocator();
        TypeMappingRegistry tmr = locator.getTypeMappingRegistry();
        TypeMapping tm = tmr.getDefaultTypeMapping();

        tm.register(java.util.Date.class, new QName(
                        "http://www.w3.org/2001/XMLSchema", "dateTime"),
                        new CustomizedCalendarSerializerFactory(java.util.Date.class,
                                new javax.xml.namespace.QName(
                                        "http://www.w3.org/2001/XMLSchema",
                                        "dateTime")),
                        new CustomizedCalendarDeserializerFactory(
                                java.util.Date.class,
                                new javax.xml.namespace.QName(
                                        "http://www.w3.org/2001/XMLSchema",
                                        "dateTime")));
        tm.register(java.util.Calendar.class, new QName(
                "http://www.w3.org/2001/XMLSchema", "dateTime"),
                new CustomizedCalendarSerializerFactory(java.util.Calendar.class,
                        new javax.xml.namespace.QName(
                                "http://www.w3.org/2001/XMLSchema",
                                "dateTime")),
                new CustomizedCalendarDeserializerFactory(
                        java.util.Calendar.class,
                        new javax.xml.namespace.QName(
                                "http://www.w3.org/2001/XMLSchema",
                                "dateTime")));

通过测试,发现此方法可行,基本上比较好了,但是如果这么多的Service每次调用都干这么一件事,也是很麻烦的,而且,如果以后又有其它的自定义Serializer和Deserizlizer,还得修改代码。于是乎继续寻找更好的解决办法。仔细阅读AXIS的文档,发现还有client-config.wsdd可用。在AXIS的代码中,

org/apache/axis/client/client-config.wsdd

给出了一个样本,但是是基本的配置,copy这个样本到你的工程的source文件夹,不需要包即可,只要AXIS在运行的时候,能够在classes目录找到这个文件就可以。

以下是配置文件及相关类的代码:

client-config.wsdd:(为了确保安全,把java.util.Date和java.util.Calendar都注册了, 如果服务器端没有强制指定encodingStyle,就把encodingStyle属性设置为"",不知道为什么,改天抽时间再研究:P)

----------------------------------------------------------------------------------

<deployment xmlns="http://xml.apache.org/axis/wsdd/"
    xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <globalConfiguration>
        <parameter name="disablePrettyXML" value="false" />
    </globalConfiguration>
    <transport name="http"
        pivot="java:org.apache.axis.transport.http.HTTPSender" />
    <transport name="local"
        pivot="java:org.apache.axis.transport.local.LocalSender" />
    <transport name="java"
        pivot="java:org.apache.axis.transport.java.JavaSender" />
    <typeMapping
        encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
        languageSpecificType="java:java.util.Date"
        qname="xsd:dateTime" classname="java.util.Date"
        serializer="lab.serviceclient.mis.CustomizedCalendarSerializerFactory"
        deserializer="lab.serviceclient.mis.CustomizedCalendarDeserializerFactory" />
    <typeMapping
        encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
        languageSpecificType="java:java.util.Calendar"
        qname="xsd:dateTime" classname="java.util.Calendar"
        serializer="lab.serviceclient.mis.CustomizedCalendarSerializerFactory"
        deserializer="lab.serviceclient.mis.CustomizedCalendarDeserializerFactory" />
</deployment>

----------------------------------------------------------------------------------

在client-config.wsdd样本的基础上修改的。

languageSpecificType="java:java.util.Date"以及languageSpecificType="java:java.util.Calendar"表明映射到Java中哪种类型的数据要求使用自定义的Serializer和Deserializer。注意写法,前面有name space "java",

qname就是指返回的XML文件中的节点类型,对于dateTime类型的全称就是xsd:dateTime,其中xsd=http://www.w3.org/2001/XMLSchema",在这个文件的Root节点定义。

serializer和deserializer节点是指向你的自定义serializer和deserializer的工厂类,而不是serializer和deserializer类本身,这个要注意。

由于不需要序列化的自定义,所以一开始我用的AXIS原有的CalendarSerializerFactory,但是发现有问题,参考CustomizedCalendarSerializerFactory中create方法的注释不分。所以后来还是加上了自定义的Serializer,但是很简单了(注意继承的父类):

CustomizedCalendarSerializer.java:

----------------------------------------------------------------------------------

package lab.serviceclient.mis;

import org.apache.axis.encoding.ser.CalendarSerializer;

public class CustomizedCalendarSerializer extends CalendarSerializer {

    private static final long serialVersionUID = 1L;

}

----------------------------------------------------------------------------------

CustomizedCalendarSerializerFactory.java:

----------------------------------------------------------------------------------

package lab.serviceclient.mis;

import javax.xml.namespace.QName;

import org.apache.axis.encoding.ser.BaseSerializerFactory;

public class CustomizedCalendarSerializerFactory extends BaseSerializerFactory {

    private static final long serialVersionUID = 1L;

    public CustomizedCalendarSerializerFactory(Class javaType, QName xmlType) {
        super(CustomizedCalendarSerializer.class, xmlType, javaType);
    }

    // 这个static的create方法是必须的。如果使用前面介绍的编程注册TypeMapping的方式,就不需要这个create方法;如果是定义在client-config.wsdd文件中,

    //AXIS在初始化的时候,org.apache.axis.deployment.wsdd.WSDDDeployment.deployMapping方法会调用factory的create方法,如果没有这个方法,就不能注册成功

    // 对于Deserializer也是一样的
    public static CustomizedCalendarSerializerFactory create(Class javaType, QName xmlType) {
        return new CustomizedCalendarSerializerFactory(javaType, xmlType);
    }

}

----------------------------------------------------------------------------------

CustomizedCalendarDeserializer.java

----------------------------------------------------------------------------------

package lab.serviceclient.mis;

import javax.xml.namespace.QName;

import org.apache.axis.encoding.ser.CalendarDeserializer;

public class CustomizedCalendarDeserializer extends CalendarDeserializer {

    private static final long serialVersionUID = 1L;

    public CustomizedCalendarDeserializer(Class javaType, QName xmlType) {
        super(javaType, xmlType);
    }
    public Object makeValue(String source) {
        System.out.println("========= This is the Customized Calendar Deserializer ========="); //为了测试是否到达了自定义的类
        if ( source == null || source.length() == 0 || "T".equals(source)) return null;
        return super.makeValue(source);
    }
}

----------------------------------------------------------------------------------

CustomizedCalendarDeserializerFactory.java

----------------------------------------------------------------------------------

package lab.serviceclient.mis;

import javax.xml.namespace.QName;

import org.apache.axis.encoding.ser.BaseDeserializerFactory;
import org.apache.axis.encoding.ser.CalendarDeserializer;

public class CustomizedCalendarDeserializerFactory extends BaseDeserializerFactory {
    private static final long serialVersionUID = 1L;

    public CustomizedCalendarDeserializerFactory(Class javaType, QName xmlType) {
        super(CustomizedCalendarDeserializer.class, xmlType, javaType);  
    }
    public static CustomizedCalendarDeserializerFactory create(Class javaType, QName xmlType) {
        return new CustomizedCalendarDeserializerFactory(javaType, xmlType);
    }
}

----------------------------------------------------------------------------------

有了这个方法,就可以不担心服务器端返回奇怪的数据了。

posted on 2008-07-25 14:02 YODA 阅读(4485) 评论(0)  编辑  收藏

只有注册用户登录后才能发表评论。


网站导航: