在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); } }