我目前在做的一个项目以axis2为webservice框架,客户端和服务器端要传输很多复杂对象,在这方面,axis2做得不错,基本满足了我的需要,但当我把客户端提供给要使用的同事时,同事发现了一个问题:就是所有java.util.Date类型的对象,不论从服务器发到客户端的还是从客户端发送到服务器的,都只剩下日期部分(年/月/日),时间部分(时/分/秒)则被抹掉了。。。百思不得其姐啊。。。
这是几个月前的事儿了,那时,这个问题并不影响整个系统的开发,所以一直搁置在那里,最近整个系统接近完成了。做为一个重要但不紧急的问题,我又重新开始研究axis2的代码,着手解决这个问题。
很多人遇到这个问题,把这归结为axis2的bug,但我认为,这是axis2遵循WSDL规范设计的,这个设计的确有些反人类,异于通常技术人员对Date的理解和使用习惯,其实也可以说我们是对webservice的理解不足导致,对于这个问题的理解我也是一点一点加深的。
webservice设计的目标是跨平台的数据交换,所以描述webservice的WSDL( Web Services Description Language)定义了很多基本数据类型(byte,int,long,short,…..),而对于日期时间则分别定义了date,time和dateTime三种不同的类型。

wsdl使用xmlshcema约束文档,这些基本数据类型都是在xmlschema中定义的,
关于wsdl与xmlschema的关系,参见这个博客:(http://blog.csdn.net/ljfbest/article/details/12135035)

顾名思义,date中只包含日期,time中只包含时间(时分秒),dateTime中包含完整的日期和时间。
我们再来看看org.apache.axis2.description.java2wsdl.TypeTable类(axis2-kernel-1.6.2.jar)中java类对date,time,dateTime的映射关系

        simpleTypetoxsd.put("java.util.Date",new QName(Java2WSDLConstants.URI_2001_SCHEMA_XSD, "date", "xs"));simpleTypetoxsd.put("java.util.Calendar",new QName(Java2WSDLConstants.URI_2001_SCHEMA_XSD, "dateTime", "xs"));// SQL date timesimpleTypetoxsd.put("java.sql.Date",new QName(Java2WSDLConstants.URI_2001_SCHEMA_XSD, "date", "xs"));simpleTypetoxsd.put("java.sql.Time",new QName(Java2WSDLConstants.URI_2001_SCHEMA_XSD, "time", "xs"));simpleTypetoxsd.put("java.sql.Timestamp",new QName(Java2WSDLConstants.URI_2001_SCHEMA_XSD, "dateTime", "xs"));

从上面代码片段中可以看出,axis2将java.util.Date是做为date处理的,java.util.Calendar和java.sql.Timestamp对应的才是完整的日期。
也就是说,这个问题最简单的
解决方案一
就是你需要在webservice中使用java.util.Calendar代替java.util.Date来传递完整日期时间数据
那么你可能会问了:java.util.Calendar和java.sql.Timestamp对应的都是dateTime,为什么不能用java.sql.Timestamp来代替java.util.Date?java.sql.Timestamp是java.util.Date子类,用它不是更方便?
这个。。。就是因为java.sql.Timestamp是java.util.Date子类,用它才比较麻烦。。。
看看方案二的第一段代码中的中文注释你就明白了。
如果因为各种原因你不想修改自己的项目代码,坚持要用Date来传递完整的日期时间,怎么办?
下面来说解决方案二:
从修改axis2源代码着手,让Date在转换成字符串时包含时间
这个方案要修改axis2的几处源代码,建议做之前先用svn,git等工具做一个原始备份,以方便对照检查。
请先从axis2官网下载 axis2-1.6.2源代码
在网上找到一些关于这个问题的一些提示(https://issues.apache.org/jira/browse/AXIS2-4075):
从这里我们可以得知所有基本数据类型(String,byte[],int,boolean,long,Date…)的序列化和反序列化都是在org.apache.axis2.databinding.typemapping.SimpleTypeMapper这个类(axis2-adb-1.6.2.jar)中完成的。在这里我们找到了Date转String的代码

    public static String getStringValue(Object obj) {if (obj instanceof Float ||obj instanceof Double) {double data;if (obj instanceof Float) {data = ((Float)obj).doubleValue();} else {data = (Double)obj;}if (Double.isNaN(data)) {return "NaN";} else if (data == Double.POSITIVE_INFINITY) {return "INF";} else if (data == Double.NEGATIVE_INFINITY) {return "-INF";} else {return obj.toString();}} else if (obj instanceof Calendar) {Calendar calendar = (Calendar) obj;return ConverterUtil.convertToString(calendar);} else if (obj instanceof Date) {//因为java.sql.Timestamp是java.util.Date子类所以在这里java.sql.Timestamp被当做Date处理了,所以如果不修改axis2的代码,而只简单地把你项目中代码中的java.util.Date替换成java.sql.Timestamp,还是不能解决问题,我觉得这算是axis2的一个bugSimpleDateFormat zulu = new SimpleDateFormat("yyyy-MM-dd");
//请注意,上面这行中生成的SimpleDateFormat对象的格式中没有时间部分哦        MessageContext messageContext = MessageContext.getCurrentMessageContext();if (messageContext != null) {AxisService axisServce = messageContext.getAxisService();// if the user has given a pirticualr timezone we use it.if (axisServce.getParameter("TimeZone") != null) {zulu.setTimeZone(TimeZone.getTimeZone((String) axisServce.getParameter("TimeZone").getValue()));}}return zulu.format(obj);} else if (obj instanceof URI){return obj.toString();}return obj.toString();}

请注意看这一行

SimpleDateFormat zulu = new SimpleDateFormat(“yyyy-MM-dd”);

看到这里就明白了吧?就是这里,将一个Date对象转为String时,有意丢弃了时间部分。
所以把这一行改为

SimpleDateFormat zulu = new SimpleDateFormat(“yyyy-MM-dd’T’HH:mm:ss.SSSZ”);

Date转换的String就有了时间和时区信息。
那么到这里,这事儿就完了吗?no,no,no事情还远未结束呢!
上面的修改使Date转String已经包含了时间和时区信息,但通过xml传送到客户端/服务器端时,String还要被解析成Date。
显然,经过上面的修改后,转换的String格式与原来已经不同了(多了时间部分),能不能也同样被正确转换成Date呢?
答案是不能。
原因还在org.apache.axis2.databinding.typemapping.SimpleTypeMapper这个类(axis2-adb-1.6.2.jar)中,见下面的代码

    public static Object getSimpleTypeObject(Class parameter, String text) {String name = parameter.getName();if(name.equals(STRING)) {return text;} else  if (text == null || text.length() == 0) {return null;} else if (name.equals(INT)) {return new Integer(text);} else if (name.equals(BOOLEAN)) {return ConverterUtil.convertToBoolean(text);} else if (name.equals(BYTE)) {return new Byte(text);} else if (name.equals(DOUBLE)) {return new Double(text);} else if (name.equals(SHORT)) {return new Short(text);} else if (name.equals(LONG)) {return new Long(text);} else if (name.equals(FLOAT)) {return new Float(text);} else if (name.equals(CHAR)) {return text.toCharArray()[0];} else if (name.equals(W_INT)) {return new Integer(text);} else if (name.equals(W_BOOLEAN)) {return Boolean.valueOf(text);} else if (name.equals(W_BYTE)) {return new Byte(text);} else if (name.equals(W_DOUBLE)) {return new Double(text);} else if (name.equals(W_SHORT)) {return new Short(text);} else if (name.equals(W_LONG)) {return new Long(text);} else if (name.equals(W_FLOAT)) {return new Float(text);} else if (name.equals(W_CHAR)) {return text.toCharArray()[0];} else if (name.equals(W_CALENDAR)) {return makeCalendar(text);} else if (name.equals(W_DATE)) {return makeDate(text);}/** return the correpsonding object for adding data type*/else if(name.equals(BIG_DECIMAL)) {return new java.math.BigDecimal(text);}else if(name.equals(BIG_INTEGER)) {return new java.math.BigInteger(text);}else if(name.equals(DAY)) {return new org.apache.axis2.databinding.types.Day(text);}else if(name.equals(DURATION)) {return new org.apache.axis2.databinding.types.Duration(text);}else if(name.equals(MONTH)) {return new org.apache.axis2.databinding.types.Month(text);}else if(name.equals(MONTH_DAY)) {return new org.apache.axis2.databinding.types.MonthDay(text);}else if(name.equals(TIME)) {return new org.apache.axis2.databinding.types.Time(text);}else if(name.equals(YEAR)) {return new org.apache.axis2.databinding.types.Year(text);}else if(name.equals(YEAR_MONTH)) {return new org.apache.axis2.databinding.types.YearMonth(text);} else if(name.equals(W_URI)) {try {return new URI(text);} catch (URISyntaxException e) {throw new RuntimeException(" Invalid URI " + text, e);}} else {return null;}}
    public static Object makeDate(String source) {return ConverterUtil.convertToDate(source);}

上面的代码中getSimpleTypeObject这个方法在将String转为Date时,调用了makeDate方法,而makeDate则是调用org.apache.axis2.databinding.utils.ConverterUtil中的convertToDate方法实现的,所以我们的目光要转到ConverterUtil这个类了,下面是ConverterUtil中convertToDate方法的原始代码(注意代码中我添加的中文注释):

//convertToDate方法的原始代码public static Date convertToDate(String source) {// the lexical form of the date is '-'? yyyy '-' mm '-' dd zzzzzz?if ((source == null) || source.trim().equals("")) {return null;}source = source.trim();boolean bc = false;if (source.startsWith("-")) {source = source.substring(1);bc = true;}int year = 0;int month = 0;int day = 0;int timeZoneOffSet = TimeZone.getDefault().getRawOffset();if (source.length() >= 10) {//first 10 numbers must give the yearif ((source.charAt(4) != '-') || (source.charAt(7) != '-')){throw new RuntimeException("invalid date format (" + source + ") with out - s at correct place ");}//解析标准的日期格式yyyy-mm-ddyear = Integer.parseInt(source.substring(0,4));month = Integer.parseInt(source.substring(5,7));day = Integer.parseInt(source.substring(8,10));if (source.length() > 10) {//>10代表有时区信息String restpart = source.substring(10);//没有解析 HH:mm:ss.SSS,直接解析时区(timeZone),所以如果参数source中包含了时间部分,会抛出RuntimeException异常if (restpart.startsWith("Z")) {// this is a gmt time zone valuetimeZoneOffSet = 0;} else if (restpart.startsWith("+") || restpart.startsWith("-") || restpart.startsWith("T")) {// this is a specific time format string//注意这里,所识别时区数字格式为08:00,而不是我们常见的0800if (restpart.charAt(3) != ':'){throw new RuntimeException("invalid time zone format (" + source+ ") without : at correct place");}int hours = Integer.parseInt(restpart.substring(1,3));int minits = Integer.parseInt(restpart.substring(4,6));timeZoneOffSet = ((hours * 60) + minits) * 60000;if (restpart.startsWith("-")){timeZoneOffSet = timeZoneOffSet * -1;}} else {throw new RuntimeException("In valid string sufix");}}} else {throw new RuntimeException("In valid string to parse");}Calendar calendar = Calendar.getInstance();calendar.clear();calendar.setLenient(false);calendar.set(Calendar.YEAR, year);//xml month stars from the 1 and calendar month is starts with 0calendar.set(Calendar.MONTH, month - 1);calendar.set(Calendar.DAY_OF_MONTH, day);calendar.set(Calendar.ZONE_OFFSET, timeZoneOffSet);// set the day light off set only if time zoneif (source.length() >= 10) {calendar.set(Calendar.DST_OFFSET, 0);}calendar.getTimeInMillis();if (bc){calendar.set(Calendar.ERA, GregorianCalendar.BC);}return calendar.getTime();}

从上面的代码及注释,可以看出,axis2在对Date转成的String重新解析成Date时,只识别 yyyy-MM-dd(Z)?这种格式,Z在这里代表时区信息,如果遇到yyyy-MM-dd’T’HH:mm:ss.SSSZ格式的时间,是不能正常解析的。所以必须修改convertToDate方法,下面是修改后的convertToDate方法:

//修改后的convertToDate方法代码/*** 针对可变长格式的日期的正则表达式*/private static final Pattern DATE_PATTERN=Pattern.compile("^(\\d{4})(\\-|\\/|\\.)(\\d{1,2})\\2(\\d{1,2})((T((\\d{2}):(\\d{2}):(\\d{2})))?(\\.(\\d{3}))?)((([\\+-])((\\d{2}):?(\\d{2})))|Z)?$");/*** Converts a given string into a date. Code from Axis1 DateDeserializer.** @param source* @return Returns Date.*/public static Date convertToDate(String source) {// the lexical form of the date is '-'? yyyy '-' mm '-' dd zzzzzz?if ((source == null) || source.trim().equals("")) {return null;}source = source.trim();Calendar calendar = Calendar.getInstance();calendar.clear();calendar.setLenient(false);boolean bc = false;if (source.startsWith("-")) {source = source.substring(1);bc = true;}int year = 0;int month = 0;int day = 0;int timeZoneOffSet = TimeZone.getDefault().getRawOffset();int hour = 0;int minute = 0;int second = 0;int miliSecond = 0;Matcher matcher=DATE_PATTERN.matcher(source);if(matcher.find()){//正则表达式匹配,不匹配则抛出RuntimeException异常//date part yyyy-MM-dd 日期部分必须有year = Integer.parseInt(matcher.group(1));month = Integer.parseInt(matcher.group(3));day = Integer.parseInt(matcher.group(4));if(matcher.group(5)!=null&&!matcher.group(5).isEmpty()){//如果有时间部分,就解析,没有就跳过// HH:mm:ss hour = Integer.parseInt(matcher.group(8));minute = Integer.parseInt(matcher.group(9));second = Integer.parseInt(matcher.group(10));if(matcher.group(11)!=null&&!matcher.group(11).isEmpty())//如果有毫秒部分就解析,没有就跳过// .SSSmiliSecond=Integer.parseInt(matcher.group(12));}// timeZoneif(matcher.group(13)!=null&&!matcher.group(13).isEmpty()){//如果有时区部分,就解析,没有就跳过if(matcher.group(13).equals("Z")){// Z结尾,代表GMT 0时区timeZoneOffSet = 0;calendar.setTimeZone(TimeZone.getTimeZone("GMT"));}else{ //数字表示的时区// +0800或+08:00int zhour,zminute;zhour = Integer.parseInt(matcher.group(17));zminute = Integer.parseInt(matcher.group(18));timeZoneOffSet = ((zhour * 60) + zminute) * 60000;if (matcher.group(15).startsWith("-")) {timeZoneOffSet = timeZoneOffSet * -1;}}}}else throw new RuntimeException(String.format("Invalid date string[%s] to parse",source));calendar.set(Calendar.YEAR, year);//xml month stars from the 1 and calendar month is starts with 0calendar.set(Calendar.MONTH, month - 1);calendar.set(Calendar.DAY_OF_MONTH, day);calendar.set(Calendar.HOUR_OF_DAY, hour);calendar.set(Calendar.MINUTE, minute);calendar.set(Calendar.SECOND, second);calendar.set(Calendar.MILLISECOND, miliSecond);calendar.set(Calendar.ZONE_OFFSET, timeZoneOffSet);// set the day light off set only if time zoneif (source.length() >= 10) {calendar.set(Calendar.DST_OFFSET, 0);}calendar.getTimeInMillis();if (bc){calendar.set(Calendar.ERA, GregorianCalendar.BC);}return calendar.getTime();}

这代码利用正则表达式重写了convertToDate方法(话说用正则表达式解析灵活多了,原来那种一个个字符判断的方式,好辛苦,数字符数的好累啊),既可以支持原来所支持的yyyy-MM-ddZ格式的时间,也支持yyyy-MM-dd’T’HH:mm:ss.SSSZ格式的时间String解析,而且这其中时间(‘T’HH:mm:ss)毫秒(.SSS)和时区(.Z)都是可选的(时区格式同时支持0800和08:00),
也就是说
“2015-09-18”,
“2015-09-18T12:32:22”,
“2015-09-18T12:32:22.798”,
“2015-09-18T12:32:22.798+0800”,
“2015-09-18T12:32:22.798+08:00”
这些代表时间的String都能被正确解析了。
好了,到这一步,问题算是基本解决了,但这就完了么?未必哦。
如果你的客户端是使用RPC方式调用(org.apache.axis2.rpc.client.RPCServiceClient),那么到这一步,你已经可以收工了。如果你是用Stub方式,那么这个坑还没填完呢。

如果你不清楚什么是RPC方式什么时Stub方式,参见这个blog Axis2开发WebService客户端 的3种方式

下面是一段axis2的wsdl2java工具生成的Stub代码的片段:

if (localCreateTime==null){// write the nil attributewriteAttribute("xsi","http://www.w3.org/2001/XMLSchema-instance","nil","1",xmlWriter);
}else{xmlWriter.writeCharacters(org.apache.axis2.databinding.utils.ConverterUtil.convertToString(localCreateTime));
}

,在这段代码中可以看到,Stub方式调用webservice时,Date转String是用org.apache.axis2.databinding.utils.ConverterUtil.convertToString(Date value)方法。

//convertToString(Date value)方法代码原始代码,中文注释是本文作者添加public static String convertToString(Date value) {if (isCustomClassPresent) {// this means user has define a seperate converter util classreturn invokeToStringMethod(value,Date.class);} else {// lexical form of the date is '-'? yyyy '-' mm '-' dd zzzzzz?Calendar calendar = Calendar.getInstance();calendar.clear();calendar.setTime(value);if (!calendar.isSet(Calendar.ZONE_OFFSET)){calendar.setTimeZone(TimeZone.getDefault());}StringBuffer dateString = new StringBuffer(16);appendDate(dateString, calendar);//添加日期 yyyy-MM-dd            appendTimeZone(calendar, dateString);//添加时区 +08:00return dateString.toString();}}

再对比处理java.util.Calendar的convertToString(Calendar value)方法

    public static String convertToString(Calendar value) {if (isCustomClassPresent) {return invokeToStringMethod(value,Calendar.class);} else {// lexical form of the calendar is '-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?if (value.get(Calendar.ZONE_OFFSET) == -1){value.setTimeZone(TimeZone.getDefault());}StringBuffer dateString = new StringBuffer(28);appendDate(dateString, value);dateString.append("T");//adding hoursappendTime(value, dateString);appendTimeZone(value, dateString);//添加时间部分 'T'HH:mm:ss.SSSreturn dateString.toString();}}

对比可以发现convertToString(Date value)方法比convertToString(Calendar value)方法果然没有把时间部分加入String.所以结论很简单,还要修改convertToString(Date value)方法:
只需要照猫画虎,参照convertToString(Calendar value)在日期后添加时间就好了:

//修改后的convertToString(Date value)方法public static String convertToString(Date value) {if (isCustomClassPresent) {// this means user has define a seperate converter util classreturn invokeToStringMethod(value,Date.class);} else {// lexical form of the date is '-'? yyyy '-' mm '-' dd zzzzzz?Calendar calendar = Calendar.getInstance();calendar.clear();calendar.setTime(value);if (!calendar.isSet(Calendar.ZONE_OFFSET)){calendar.setTimeZone(TimeZone.getDefault());}StringBuffer dateString = new StringBuffer(16);appendDate(dateString, calendar);//adding HH:mm:ss.SSS//modified by guyadong 2015/09/20 增加时间部分dateString.append("T");appendTime(calendar, dateString);appendTimeZone(calendar, dateString);return dateString.toString();}}

到这里,所有的代码修改就结束了,剩下的,就是用修改后代码生成的两个class文件(org.apache.axis2.databinding.typemapping.SimpleTypeMapper和org.apache.axis2.databinding.utils.ConverterUtil)替代掉axis2-adb-1.6.2.jar中对应的同名文件就大功告成啦。

解决axis2处理java.util.Date类型对象时丢弃时间部分的问题相关推荐

  1. 计算机二级日期格式,09年计算机二级辅导:指定格式的日期字符串转化成java.util.Date类型日期对象...

    09年计算机二级辅导:指定格式的日期字符串转化成java.util.Date类型日期对象 分类:计算机等级| 更新时间:2008-10-16| 来源:教育联展网 将未指定格式的日期字符串转化成java ...

  2. java 指定格式的date_指定格式的日期字符串转化成java.util.Date类型日期对象

    将未指定格式的日期字符串转化成java.util.Date类型日期对象,代码如下: import java.text.DateFormat; import java.text.ParseExcepti ...

  3. 将java.time.LocalDate转换为java.util.Date类型

    本文翻译自:Convert java.time.LocalDate into java.util.Date type I want to convert java.time.LocalDate int ...

  4. Java.util.Date类型的转化成为数据库中的Date类型。

    最完整的一个网页:http://www.zxbc.cn/html/20080905/65272.html 作者:天涯 来源:中国自学编程网 发布日期:1220576012 在JDK 1.1后,java ...

  5. java基础--java.util.Date类型小结

    首先先来了解一下Date数据类型: . Date类型通常要和另一个 java.text.SimpleDateFormat类联合使用. 把long-->Date: public Date(long ...

  6. java calendar去掉时分秒_java 8:只取年月日的java.util.Date(时分秒清零)对象

    废弃的方法(@Deprecated) 如果不想要java.util.Date中的时分秒数据,希望将它清零,只保留年月日,最简单的办法是 调用setHours,setMinutes,setSeconds ...

  7. java.util.Date转换成java.sql.Date

    // 获取当前的系统时间,以java.util.Date对象返回 Date dateUtil = new Date(); // 获取当前的系统时间,以时间戳返回 long l = System.cur ...

  8. [转载] java 中 date类型详解

    参考链接: Java中的date after()方法 举例: Date的after和before方法解释 Date1.after(Date2),当Date1大于Date2时,返回TRUE,当小于等于时 ...

  9. java.util.Date和java.sql.Timestamp转换

    java.sql.Date 只存储日期数据不存储时间数据 // 会丢失时间数据 preparedStatement.setDate(1, new java.sql.Date(date.getTime( ...

最新文章

  1. 机器人进攻民用市场:踏实做好小优美
  2. java 矩阵题目_java练习本(20190604)
  3. c语言编程数学黑洞,一个数学黑洞——6174
  4. C++ 的几个for 循环,范围for语句
  5. android中intent放数据类型,Android Intent传递数据底层分析详细介绍
  6. 作业一——根据成绩计算出至少需要发多少奖金才能让所有的组满意。
  7. 一篇文章为你深度解析HTTPS 协议 1
  8. 《算法导论》——矩阵乘法的Strassen算法
  9. 在训练CNN时,loss稳定在log(类别数)
  10. 图像像素点赋值_OpenCV学习笔记(二)之图像阈值化
  11. RoboWare Studio入门教程(一)
  12. 页面开发配色选择神器
  13. 第三代酷睿i3处理器_轻薄本CPU谁更强?英特尔21款低功耗处理器大排行!
  14. List集合 值进行累加
  15. chrome浏览器(block)屏蔽http文件下载,如何解除?
  16. Excel信息批量替换Word模板生成新文件
  17. 如何确定windows右下角弹出的窗口是哪个程序搞的鬼
  18. JS 异步编程六种方案
  19. ip代理软件的原理到底是什么?适用场景有哪些?
  20. 免费,时长不限制,PC端在线录制屏幕神器题

热门文章

  1. Shell命令:echo介绍,echo如何输出带颜色的文本
  2. 关于js延迟加载的几种方法
  3. Flash/Flex学习笔记(43):动量守恒与能量守恒
  4. 输入一串字符,并判断其中英文字符和数字字符的个数——C语言实现(C语言练习)
  5. BZOJ4585 [Apio2016]烟火表演
  6. 【CSS】字体、行高、文本对齐
  7. 医院客户关系管理/医院随访/CRM/HCRM
  8. vue-router back 返回时携带参数
  9. HDMI特性之Audio Return Channel Control
  10. tcpdump命令帮助和示例