上善若水
          In general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. To do is to be -Nietzsche, To bei is to do -Kant, Do be do be do -Sinatra
          posts - 146,comments - 147,trackbacks - 0

          現(xiàn)在參與的項(xiàng)目是一個(gè)純Application Server,整個(gè)Server都是自己搭建的,使用JMS消息實(shí)現(xiàn)客戶端和服務(wù)器的交互,交互的數(shù)據(jù)格式采用XML。說(shuō)來(lái)慚愧,開(kāi)始為了趕進(jìn)度,所有XML消息都是使用字符串拼接的,而XML的解析則是使用DOM方式查找的。我很早就看這些代碼不爽了,可惜一直沒(méi)有時(shí)間去重構(gòu),最近項(xiàng)目加了幾個(gè)人,而且美國(guó)那邊也開(kāi)始漸漸的把這個(gè)項(xiàng)目開(kāi)發(fā)的控制權(quán)交給我們了,所以我開(kāi)始有一些按自己的方式開(kāi)發(fā)的機(jī)會(huì)了。因而最近動(dòng)手開(kāi)始重構(gòu)這些字符串拼接的代碼。

          對(duì)XMLJava Bean的解析框架,熟悉一點(diǎn)的只有DigesterXStreamDigester貌似只能從XML文件解析成Java Bean對(duì)象,所以只能選擇XStream來(lái)做了,而且同組的其他項(xiàng)目也有在用XStream。一直聽(tīng)說(shuō)XStream的使用比較簡(jiǎn)單,而且我對(duì)ThoughtWorks這家公司一直比較有好感,所以還以為引入XStream不會(huì)花太多時(shí)間,然而使用以后才發(fā)現(xiàn)XStream并沒(méi)有想象的你那么簡(jiǎn)單。不過(guò)這個(gè)也有可能是因?yàn)槲也幌敫淖冊(cè)瓉?lái)的XML數(shù)據(jù)格式,而之前的XML數(shù)據(jù)格式的設(shè)計(jì)自然不會(huì)考慮到如何便利的使用XStream。因而記錄在使用過(guò)程中遇到的問(wèn)題,供后來(lái)人參考,也為自己以后如果打算開(kāi)其源碼提供參考。廢話就到這里了,接下來(lái)步入正題。

          首先對(duì)于簡(jiǎn)單的引用,XStream使用起來(lái)確實(shí)比較簡(jiǎn)單,比如自定義標(biāo)簽的屬性、使用屬性和使用子標(biāo)簽的定義等:

          @XStreamAlias("request")
          public class XmlRequest1 {
              
          private static XStream xstream;
              
          static {
                  xstream 
          = new XStream();
                  xstream.autodetectAnnotations(
          true);
              }
             
              @XStreamAsAttribute
              
          private String from;
             
              @XStreamAsAttribute
              @XStreamAlias(
          "calculate-method")
              
          private String calculateMethod;
             
              @XStreamAlias(
          "request-time")
              private Date requestTime;
           
              @XStreamAlias(
          "input-files")
              
          private List<InputFileInfo> inputFiles;
             
              
          public static String toXml(XmlRequest1 request) {
                  StringWriter writer 
          = new StringWriter();
                  writer.append(Constants.XML_HEADER);
                  xstream.toXML(request, writer);
                  
          return writer.toString();
              }
              
          public static XmlRequest1 toInstance(String xmlContent) {
                  
          return (XmlRequest1)xstream.fromXML(xmlContent);
          }

              @XStreamAlias(
          "input-file")
              
          public static class InputFileInfo {
                  
          private String type;
                  
          private String fileName;
                  
              }
              
          public static void main(String[] args) {
                  XmlRequest1 request 
          = buildXmlRequest();
                  System.out.println(XmlRequest1.toXml(request));
              }
              
          private static XmlRequest1 buildXmlRequest() {
                  
              }
          }

           對(duì)以上Request定義,我們可以得到如下結(jié)果:

          <?xml version="1.0" encoding="UTF-8"?>
          <request from="levin@host" calculate-method="advanced">
           
          <request-time>2012-11-28 17:11:54.664 UTC</request-time>
           
          <input-files>
              
          <input-file>
                
          <type>DATA</type>
                
          <fileName>data.2012.11.29.dat</fileName>
              
          </input-file>
              
          <input-file>
                
          <type>CALENDAR</type>
                
          <fileName>calendar.2012.11.29.dat</fileName>
              
          </input-file>
           
          </input-files>
          </request>

          可惜這個(gè)世界不會(huì)那么清凈,這個(gè)格式有些時(shí)候貌似并不符合要求,比如request-time的格式、input-files的格式,我們實(shí)際需要的格式是這樣的:

          <?xml version="1.0" encoding="UTF-8"?>
          <request from="levin@host" calculate-method="advanced">
           
          <request-time>20121128T17:51:05</request-time>
           
          <input-file type="DATA">data.2012.11.29.dat</input-file>
           
          <input-file type="CALENDAR">calendar.2012.11.29.dat</input-file>
          </request>

          對(duì)不同Date格式的支持可以是用Converter實(shí)現(xiàn),在XStream中默認(rèn)使用自己實(shí)現(xiàn)的DateConverter,它支持的格式是:yyyy-MM-dd HH:mm:ss.S 'UTC',然而我們現(xiàn)在需要的格式是yyyy-MM-dd’T’HH:mm:ss,如果使用XStream直接注冊(cè)DateConverter,可以使用配置自己的DateConverter,但是由于DateConverter的構(gòu)造函數(shù)的定義以及@XStreamConverter的構(gòu)造函數(shù)參數(shù)的支持方式的限制,貌似DateConverter不能很好的支持注解方式的注冊(cè),因而我時(shí)間了一個(gè)自己的DateConverter以支持注解:

          public class LevinDateConverter extends DateConverter {
              
          public LevinDateConverter(String dateFormat) {
                  
          super(dateFormat, new String[] { dateFormat });
              }
          }

          requestTime字段中需要加入以下注解定義:

          @XStreamConverter(value=LevinDateConverter.class, strings={"yyyyMMdd'T'HH:mm:ss"})
          @XStreamAlias(
          "request-time")
          private Date requestTime;

          對(duì)集合類,XStream提供了@XStreamImplicit注解,以將集合中的內(nèi)容攤平到上一層XML元素中,其中itemFieldName的值為其使用的標(biāo)簽名,此時(shí)InputFileInfo類中不需要@XStreamAlias標(biāo)簽的定義:

          @XStreamImplicit(itemFieldName="input-file")
          private List<InputFileInfo> inputFiles;

          對(duì)InputFileInfo中的字段,type作為屬性很容易,只要為它加上@XStreamAsAttribute注解即可,而將fileName作為input-file標(biāo)簽的一個(gè)內(nèi)容字符串,則需要使用ToAttributedValueConverter,其中Converter的參數(shù)為需要作為字符串內(nèi)容的字段名:

          @XStreamConverter(value=ToAttributedValueConverter.class, strings={"fileName"})
          public static class InputFileInfo {
              @XStreamAsAttribute
              
          private String type;
          private String fileName;

          }

          XStream對(duì)枚舉類型的支持貌似不怎么好,默認(rèn)注冊(cè)的EnumSingleValueConverter只是使用了Enum提供的name()和靜態(tài)的valueOf()方法將enum轉(zhuǎn)換成String或?qū)?/span>String轉(zhuǎn)換回enum。然而有些時(shí)候XML的字符串和類定義的enum值并不完全匹配,最常見(jiàn)的就是大小寫(xiě)的不匹配,此時(shí)需要寫(xiě)自己的Converter。在這種情況下,我一般會(huì)在enum中定義一個(gè)name屬性,這樣就可以自定義enum的字符串表示。比如有TimePeriodenum

          public enum TimePeriod {
              MONTHLY(
          "monthly"), WEEKLY("weekly"), DAILY("daily");
             
              
          private String name;
             
              
          public String getName() {
                  
          return name;
              }
             
              
          private TimePeriod(String name) {
                  
          this.name = name;
              }
             
              
          public static TimePeriod toEnum(String timePeriod) {
                  
          try {
                      
          return Enum.valueOf(TimePeriod.class, timePeriod);
                  } 
          catch(Exception ex) {
                      
          for(TimePeriod period : TimePeriod.values()) {
                          
          if(period.getName().equalsIgnoreCase(timePeriod)) {
                              
          return period;
                          }
                      }
                      
          throw new IllegalArgumentException("Cannot convert <" + timePeriod + "> to TimePeriod enum");
                  }
              }
          }

          我們可以編寫(xiě)以下Converter以實(shí)現(xiàn)對(duì)枚舉類型的更寬的容錯(cuò)性:

          public class LevinEnumSingleNameConverter extends EnumSingleValueConverter {
              
          private static final String CUSTOM_ENUM_NAME_METHOD = "getName";
              
          private static final String CUSTOM_ENUM_VALUE_OF_METHOD = "toEnum";
             
              
          private Class<? extends Enum<?>> enumType;
           
              
          public LevinEnumSingleNameConverter(Class<? extends Enum<?>> type) {
                  
          super(type);
                  
          this.enumType = type;
              }
           
              @Override
              
          public String toString(Object obj) {
                  Method method 
          = getCustomEnumNameMethod();
                  
          if(method == null) {
                      
          return super.toString(obj);
                  } 
          else {
                      
          try {
                          
          return (String)method.invoke(obj, (Object[])null);
                      } 
          catch(Exception ex) {
                          
          return super.toString(obj);
                      }
                  }
              }
           
              @Override
              
          public Object fromString(String str) {
                  Method method 
          = getCustomEnumStaticValueOfMethod();
                  
          if(method == null) {
                      
          return enhancedFromString(str);
                  }
                  
          try {
                      
          return method.invoke(null, str);
                  } 
          catch(Exception ex) {
                      
          return enhancedFromString(str);
                  }
              }
             
              
          private Method getCustomEnumNameMethod() {
                  
          try {
                      
          return enumType.getMethod(CUSTOM_ENUM_NAME_METHOD, (Class<?>[])null);
                  } 
          catch(Exception ex) {
                      
          return null;
                  }
              }
             
              
          private Method getCustomEnumStaticValueOfMethod() {
                  
          try {
                      Method method 
          = enumType.getMethod(CUSTOM_ENUM_VALUE_OF_METHOD, (Class<?>[])null);
                      
          if(method.getModifiers() == Modifier.STATIC) {
                          
          return method;
                      }
                      
          return null;
                  } 
          catch(Exception ex) {
                      
          return null;
                  }
              }
             
              
          private Object enhancedFromString(String str) {
                  
          try {
                      
          return super.fromString(str);
                  } 
          catch(Exception ex) {
                      
          for(Enum<?> item : enumType.getEnumConstants()) {
                          
          if(item.name().equalsIgnoreCase(str)) {
                              
          return item;
                          }
                      }
                      
          throw new IllegalStateException("Cannot converter <" + str + "> to enum <" + enumType + ">");
                  }
              }
          }

          如下方式使用即可:

          @XStreamAsAttribute
          @XStreamAlias(
          "time-period")
          @XStreamConverter(value
          =LevinEnumSingleNameConverter.class)
          private TimePeriod timePeriod;

          對(duì)double類型,貌似默認(rèn)的DoubleConverter實(shí)現(xiàn)依然不給力,它不支持自定義的格式,比如我們想在序列化的時(shí)候用一下格式: ###,##0.0########,此時(shí)又需要編寫(xiě)自己的Converter

          public class FormatableDoubleConverter extends DoubleConverter {
              
          private String pattern;
              
          private DecimalFormat formatter;
             
              
          public FormatableDoubleConverter(String pattern) {
                  
          this.pattern = pattern;
                  
          this.formatter = new DecimalFormat(pattern);
              }
             
              @Override
              
          public String toString(Object obj) {
                  
          if(formatter == null) {
                      
          return super.toString(obj);
                  } 
          else {
                      
          return formatter.format(obj);
                  }
              }
             
              @Override
              
          public Object fromString(String str) {
                  
          try {
                      
          return super.fromString(str);
                  } 
          catch(Exception ex) {
                      
          if(formatter != null) {
                          
          try {
                              
          return formatter.parse(str);
                          } 
          catch(Exception e) {
                              
          throw new IllegalArgumentException("Cannot parse <" + str + "> to double value", e);
                          }
                      }
                      
          throw new IllegalArgumentException("Cannot parse <" + str + "> to double value", ex);
                  }
              }
             
              
          public String getPattern() {
                  
          return pattern;
              }
          }

          使用方式和之前的Converter類似:

          @XStreamAsAttribute
          @XStreamConverter(value
          =FormatableDoubleConverter.class, strings={"###,##0.0########"})
          private double value;

          最后,還有兩個(gè)XStream沒(méi)法實(shí)現(xiàn)的,或者說(shuō)我沒(méi)有找到一個(gè)更好的實(shí)現(xiàn)方式的場(chǎng)景。第一種場(chǎng)景是XStream不能很好的處理對(duì)象組合問(wèn)題:

          在面向?qū)ο缶幊讨校话惚M量的傾向于抽取相同的數(shù)據(jù)成一個(gè)類,而通過(guò)組合的方式構(gòu)建整個(gè)數(shù)據(jù)結(jié)構(gòu)。比如Student類中有nameaddressAddress是一個(gè)類,它包含citycodestreet等信息,此時(shí)如果要對(duì)Student對(duì)象做如下格式序列化:

          <student name=”Levin”>
           
          <city>shanghai</city>
           
          <street>zhangjiang</street>
           
          <code>201203</code>
          </student>

          貌似我沒(méi)有找到可以實(shí)現(xiàn)的方式,XStream能做是在中間加一層address標(biāo)簽。對(duì)這種場(chǎng)景的解決方案,一種是將Address中的屬性平攤到Student類中,另一種是讓Student繼承自Address類。不過(guò)貌似這兩種都不是比較理想的辦法。

          第二種場(chǎng)景是XStream不能很好的處理多態(tài)問(wèn)題:

          比如我們有一個(gè)Trade類,它可能表示不同的產(chǎn)品:

          public class Trade {
              
          private String tradeId;
              private Product product;

          }
          abstract class Product {
              
          private String name;
              
          public Product(String name) {
                  
          this.name = name;
          }

          }
          class FX extends Product {
              
          private double ratio;
              
          public FX() {
                  
          super("fx");
              }
              
          }
          class Future extends Product {
              
          private double maturity;
              
          public Future() {
                  
          super("future");
              }
              
          }

          通過(guò)一些簡(jiǎn)單的設(shè)置,我們能得到如下XML格式:

          <trades>
           
          <trade trade-id="001">
              
          <product class="levin.xstream.blog.FX" name="fx" ratio="0.59"/>
           
          </trade>
           
          <trade trade-id="002">
              
          <product class="levin.xstream.blog.Future" name="future" maturity="2.123"/>
           
          </trade>
          </trades>

          作為數(shù)據(jù)文件,對(duì)Java類的定義顯然是不合理的,因而簡(jiǎn)單一些,我們可以編寫(xiě)自己的Converterclass屬性從product中去除:

          xstream.registerConverter(new ProductConverter(
                  xstream.getMapper(), xstream.getReflectionProvider()));
           
              
          public ProductConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
                  
          super(mapper, reflectionProvider);
              }
             
              @Override
              
          public boolean canConvert(@SuppressWarnings("rawtypes") Class type) {
                  
          return Product.class.isAssignableFrom(type);
              }
           
              @Override
              
          protected Object instantiateNewInstance(HierarchicalStreamReader reader, UnmarshallingContext context) {
                  Object currentObject 
          = context.currentObject();
                  
          if(currentObject != null) {
                      
          return currentObject;
                  }
                 
                  String name 
          = reader.getAttribute("name");
                  
          if("fx".equals(name)) {
                      
          return reflectionProvider.newInstance(FX.class);
                  } 
          else if("future".equals(name)) {
                      
          return reflectionProvider.newInstance(Future.class);
                  }
                  
          throw new IllegalStateException("Cannot convert <" + name + "> product");
              }
          }

          在所有Production上定義@XStreamAlias(“product”)注解。這時(shí)的XML輸出結(jié)果為:

          <trades>
           
          <trade trade-id="001">
              
          <product name="fx" ratio="0.59"/>
           
          </trade>
           
          <trade trade-id="002">
              
          <product name="future" maturity="2.123"/>
           
          </trade>
          </trades>

          然而如果有人希望XML的輸出結(jié)果如下呢?

          <trades>
           
          <trade trade-id="001">
              
          <fx ratio="0.59"/>
           
          </trade>
           
          <trade trade-id="002">
              
          <future maturity="2.123"/>
           
          </trade>
          </trades>

          大概找了一下,可能可以定義自己的Mapper來(lái)解決,不過(guò)XStream的源碼貌似比較復(fù)雜,沒(méi)有時(shí)間深究這個(gè)問(wèn)題,留著以后慢慢解決吧。

          補(bǔ)充:

          對(duì)Map類型數(shù)據(jù),XStream默認(rèn)使用以下格式顯示:

          <map class="linked-hash-map">
              
          <entry>
                
          <string>key1</string>
                
          <string>value1</string>
              
          </entry>
              
          <entry>
                
          <string>key2</string>
                
          <string>value2</string>
              
          </entry>
           
          </map>

           

          但是對(duì)一些簡(jiǎn)單的Map,我們希望如下顯示:

           <map>
              
          <entry key="key1" value="value1"/>
              
          <entry key="key2" value="value2"/>
           
          </map>

          對(duì)這種需求需要通過(guò)編寫(xiě)Converter解決,繼承自MapConverter,覆蓋以下函數(shù),這里的Map默認(rèn)keyvalue都是String類型,如果他們不是String類型,需要另外添加邏輯:

          @SuppressWarnings("rawtypes")
          @Override
          public void marshal(Object source, HierarchicalStreamWriter writer,
                  MarshallingContext context) {
              Map map 
          = (Map) source;
              
          for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
                  Entry entry 
          = (Entry) iterator.next();
                  ExtendedHierarchicalStreamWriterHelper.startNode(writer, mapper()
                          .serializedClass(Map.Entry.
          class), entry.getClass());
           
                  writer.addAttribute(
          "key", entry.getKey().toString());
                  writer.addAttribute(
          "value", entry.getValue().toString());
                  writer.endNode();
              }
          }
           
          @Override
          @SuppressWarnings({ 
          "unchecked""rawtypes" })
          protected void putCurrentEntryIntoMap(HierarchicalStreamReader reader,
                  UnmarshallingContext context, Map map, Map target) {
              Object key 
          = reader.getAttribute("key");
              Object value 
          = reader.getAttribute("value");
           
              target.put(key, value);
          }

          但是只是使用Converter,得到的結(jié)果多了一個(gè)class屬性:

           <map class="linked-hash-map">
              
          <entry key="key1" value="value1"/>
              
          <entry key="key2" value="value2"/>
           
          </map>

          XStream中,如果定義的字段是一個(gè)父類或接口,在序列化是會(huì)默認(rèn)加入class屬性以確定反序列化時(shí)用的類,為了去掉這個(gè)class屬性,可以定義默認(rèn)的實(shí)現(xiàn)類來(lái)解決(雖然感覺(jué)這種解決方案不太好,但是目前還沒(méi)有找到更好的解決方案)。

          xstream.addDefaultImplementation(LinkedHashMap.class, Map.class);

           

          posted on 2012-11-30 01:50 DLevin 閱讀(25042) 評(píng)論(3)  編輯  收藏 所屬分類: 經(jīng)驗(yàn)積累

          FeedBack:
          # re: 使用XStream序列化、反序列化XML數(shù)據(jù)時(shí)遇到的各種問(wèn)題
          2013-10-24 15:39 | Sy
          厲害。最后一部份寫(xiě)的,真是精彩。  回復(fù)  更多評(píng)論
            
          # re: 使用XStream序列化、反序列化XML數(shù)據(jù)時(shí)遇到的各種問(wèn)題
          2015-12-18 16:35 | laughing
          樓主你好,

          不知道繼承的那個(gè)問(wèn)題你有沒(méi)有解決掉?我現(xiàn)在也遇到這個(gè)問(wèn)題,如果你解決掉麻煩講解一下,謝謝  回復(fù)  更多評(píng)論
            
          # re: 使用XStream序列化、反序列化XML數(shù)據(jù)時(shí)遇到的各種問(wèn)題
          2016-05-19 18:50 | MR熊
          繼承上,子類對(duì)象 在toxml時(shí), XStream只將子類的屬性轉(zhuǎn)xml了, 父類的屬性沒(méi)轉(zhuǎn)換xml為何呢?  回復(fù)  更多評(píng)論
            
          主站蜘蛛池模板: 锦州市| 甘德县| 淮滨县| 新郑市| 宁都县| 万年县| 鄂托克旗| 抚宁县| 江达县| 山西省| 康马县| 丹凤县| 嘉峪关市| 沾益县| 宁明县| 湛江市| 台东市| 阳原县| 和平区| 兰坪| 石嘴山市| 开江县| 宁远县| 上饶市| 鄂托克旗| 龙南县| 伊宁县| 龙川县| 张家港市| 吐鲁番市| 卫辉市| 邯郸市| 桂林市| 秀山| 株洲县| 子洲县| 武陟县| 刚察县| 东丽区| 阜城县| 泰兴市|