跨语种WebService数据类型转换(Exception:java.lang.NumberFormatException: Invalid date)

在JavaEye问答频道发帖,迟迟不见回复,偶尔发现此贴已解心中疑惑。

 

Exception:java.lang.NumberFormatException: Invalid date 

    项目遇到这个情况,服务端返回的数据中,有几个类型为 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);
}
}

你可能感兴趣的:(SOA,Java,WebService,Apache,XML,SOAP)