jaxb解析xml在WebService中的使用,以及Date類型的解決方案

1.前言


  以前在學校學習的時候,沒有接觸過WebService。如今開始實習之後,由於項目需要,要對接別人程序提供出來的接口,要用到WebService。而在使用WebService的時候,我這裏入參和出參都需要是xml。而爲了封裝信息和以後維護方便等理由,這裏很自然而然的,就需要用到jaxb技術,將一個對象轉換爲xml以及將xml轉換爲一個對象。(其實說白了,就像是使用gson解析json數據一樣,只不過gson使用起來比較方便)。這裏只介紹一下,在實際應用中如何實現對象與xml之間的相互轉換,並不述說WebService。

2.入參


  首先,當我調用WebService發起請求時,入參要求我傳入的是xml格式的,現在,先讓我們看一個入參的例子:

<Request>
    <PatientId>3869622</PatientId>
    <VisitId>12097107</VisitId>
</Request>

  xml的格式很簡單,相信稍微學過一下xml的,一看就懂了。說老實話,如果要我們自己用字符串拼接這麼一串xml格式的入參,其實也是不難的。

String inXml = "<Request><PatientId>" + PatientId + "</PatientId><VisitId>" + VisitId + "</VisitId></Request>";
但是,不難發現,這樣的話,當這個入參內容很多的時候,這個字符串就會變得很長,而且還需要有很多變量去組拼字符串,可維護性是非常低的。因爲,我們需要有一個類去封裝數據,然後再真正使用的時候,將這個類的對象,通過jaxb轉換爲xml。

下面讓我們看看這個類的樣子:

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "Request")
public class GetBillRateRequest {

	private String patientId;
	private String visitId;

	@XmlElement(name = "PatientId")
	public String getPatientId() {
		return patientId;
	}

	public void setPatientId(String patientId) {
		this.patientId = patientId;
	}

	@XmlElement(name = "VisitId")
	public String getVisitId() {
		return visitId;
	}

	public void setVisitId(String visitId) {
		this.visitId = visitId;
	}

}

這個類中只有兩個成員變量,剛好對應上面的字符串。

@XmlRootElement(name = "Request")定義最外層的節點,也就是根節點的名稱。

<Request>
    ……
</Request>

如果對名字沒有要求的話,直接@XmlRootElement()這樣寫就可以了。

<getBillRateRequest>
……
</getBillRateRequest>

但是,因爲項目的要求,我這邊每一個WebService的請求,都必須是用<Request>做最外層的節點,包括裏面的內容的,這麼多種用於封裝信息的類,不可能每一個類的類名都叫Request吧?而且你仔細觀察的話,GetBillRateRequest這樣的類名,轉換後會變成<getBillRateRequest>,也就是說哪怕類名是Request也是不符合要求的。所以,這裏我採用了@XmlRootElement(name = "Request")。

這裏複製一下官方文檔的內容,羅列一下這個註解的其他屬性

Modifier and Type Optional Element and Description
String name
local name of the XML element.
String namespace
namespace name of the XML element.

@XmlElement(name = "PatientId")定義每一個屬性轉換爲xml節點時的名稱。跟上面的那個差不多,這裏不再累述。

xml的入參要求節點是<PatientId>,但是這樣的屬性命名規則,明顯不符合java通常的屬性名字規則,按照我們的習慣,屬性的名字應該是這樣寫的:patientId,所以我們這裏就要用到name的這個屬性,來修改一下節點的名稱。毫無疑問的,如果你有需求的話,完全可以在實際應用中,根據實際情況來這樣寫:@XmlElement(name = "aabbcc")

Optional Element Summary
 String defaultValue 
          Default value of this element.
 String name 
          Name of the XML Schema element.
 String namespace 
          XML target namespace of the XML Schema element.
 boolean nillable 
          Customize the element declaration to be nillable.
 boolean required 
          Customize the element declaration to be required.
 Class type 
          The Java class being referenced.

其餘屬性同上,相信有一點xml基礎和英文基礎的人,都能看懂。再不行的話,寫幾次demo就能搞懂了。這裏我就不翻譯和一一不述說了,怕描述有偏頗。


3.出參

<Response>
    <ResultCode>0</ResultCode>
    <ErrorMsg>獲取成功</ErrorMsg>
    <vpatientid>3869622</vpatientid>
    <nvisitid>12097107</nvisitid>
    <BillRateList>
        <BillRateListInfo>
            <feeclassname>aaaaaa</feeclassname>
            <costs>71.55</costs>
            <rate>0.07%</rate>
        </BillRateListInfo>
        <BillRateListInfo>
            <feeclassname>bbbbbbb</feeclassname>
            <costs>650</costs>
            <rate>0.61%</rate>
        </BillRateListInfo>
        <BillRateListInfo>
            <feeclassname>cccccccc</feeclassname>
            <costs>21274</costs>
            <rate>20.09%</rate>
        </BillRateListInfo>
    </BillRateList>
</Response>


仔細觀察,會發現出參中包含一個集合,而因爲集合的關係,需要兩個用於封住數據的類。

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "Response")
public class GetBillRateResponse {
	private String patientId;
	private String visitId;
	private Integer resultCode;
	private String errorMsg;

	private List<BillRateListInfoResponse> billRateListInfos = new ArrayList<BillRateListInfoResponse>();

	@XmlElementWrapper(name = "BillRateList")
	@XmlElement(name = "BillRateListInfo")
	public List<BillRateListInfoResponse> getBillRateListInfos() {
		return billRateListInfos;
	}

	public void setBillRateListInfos(
			List<BillRateListInfoResponse> billRateListInfos) {
		this.billRateListInfos = billRateListInfos;
	}

	@XmlElement(name = "vpatientid")
	public String getPatientId() {
		return patientId;
	}

	public void setPatientId(String patientId) {
		this.patientId = patientId;
	}

	@XmlElement(name = "nvisitid")
	public String getVisitId() {
		return visitId;
	}

	public void setVisitId(String visitId) {
		this.visitId = visitId;
	}

	public Integer getResultCode() {
		return resultCode;
	}

	public void setResultCode(Integer resultCode) {
		this.resultCode = resultCode;
	}

	public String getErrorMsg() {
		return errorMsg;
	}

	public void setErrorMsg(String errorMsg) {
		this.errorMsg = errorMsg;
	}
}


集合中,每一個item對應的類BillRateListInfoResponse:

因爲是實際使用的一個類,所以可能會有一些特別的註釋,只要沒有泄露太多信息的註釋,我這裏就不去掉了。其中setRate()方法算是一個比較特別的存在吧。

import javax.xml.bind.annotation.XmlElement;

public class BillRateListInfoResponse {

	private String fee_class_name;
	private Float costs;
	private String rate;

	@XmlElement(name = "feeclassname")
	public String getFee_class_name() {
		return fee_class_name;
	}

	public void setFee_class_name(String fee_class_name) {
		this.fee_class_name = fee_class_name;
	}

	@XmlElement(name = "costs")
	public Float getCosts() {
		return costs;
	}

	public void setCosts(Float costs) {
		this.costs = costs;
	}

	@XmlElement(name = "rate")
	public String getRate() {
		return rate;
	}

	public void setRate(String rate) {
		// 因爲對方返回的,是3.67%,但是app那邊要的,是float而且總和是1
		// 所以要去掉%,並且在除以100,變成0.0367
		rate = rate.substring(0, rate.length() - 1);
		Float temp = Float.parseFloat(rate);
		temp /= 100;
		rate = String.format("%.4f", temp);
		this.rate = rate;
	}
}


稍微看過兩個類的代碼後,這裏需要重點的關注的,是集合上面的兩個註解:

@XmlElementWrapper(name = "BillRateList")
@XmlElement(name = "BillRateListInfo")

我們觀察一下xml的格式:

 <BillRateList>
        <BillRateListInfo>
         ……
        </BillRateListInfo>
       ……
    </BillRateList>

不難發現,@XmlElementWrapper(name = "BillRateList")註解的作用,就是表明這個集合在xml中的節點名字,而@XmlElement(name = "BillRateListInfo")就是表明集合中每一個item的節點名稱。這裏需要特別注意的是,就是BillRateListInfoResponse,這個用於封裝每一個item的類,它是不需要寫@XmlRootElement()這個註解的,因爲它轉換成xml的時候,它的節點名稱,已經在前面指定了。


4.轉換工具類

  好了,類都寫好了,要怎麼方便的將一個對象轉換爲xml,或者將xml轉換爲一個對象呢,這裏給出一個工具類(網上百度一大把0.0)


import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class JaxbUtil {

	public static String toXML(Object obj) {
		try {
			JAXBContext context = JAXBContext.newInstance(obj.getClass());

			Marshaller marshaller = context.createMarshaller();
			marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");// //編碼格式
			marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);// 是否格式化生成的xml串
			marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);// 是否省略xm頭聲明信息
			StringWriter writer = new StringWriter();
			marshaller.marshal(obj, writer);
			return writer.toString();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@SuppressWarnings("unchecked")
	public static <T> T fromXML(String xml, Class<T> valueType) {
		try {
			JAXBContext context = JAXBContext.newInstance(valueType);
			Unmarshaller unmarshaller = context.createUnmarshaller();
			return (T) unmarshaller.unmarshal(new StringReader(xml));
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage());
		}
	}
}

結合我的實際環境,我在調用WebService時是這樣使用它的:

public GetMrFileIndexResponse getMrFileIndex(GetMrFileIndexRequest request) {
		return commonProcess(request, "GetMrFileIndex",
				GetMrFileIndexResponse.class);
	}

public <T> T commonProcess(Object toXmlBean, String functionName,
			Class<T> resultBeanClass) {
		String inXml = JaxbUtil.toXML(toXmlBean);
		try {<span style="white-space:pre">	</span>//可以忽略這一句代碼,這一句代碼,其實就是做一個WebService請求並接受WebService返回的結果,WebService的內容不再這裏述說。
			String resultXml = appDataWsSoap.test(functionName, inXml);

			if (resultXml == null || resultXml.isEmpty()) {
				return null;
			}
			
			T t = JaxbUtil.fromXML(resultXml, resultBeanClass);
			return t;
		} catch (Exception e) {
			System.out.println(functionName + "超時了……");
			return null;
		}

	}


5.Date類型的轉換(解決方案)

  在實際開發中,我們會遇到日期類型的話,如xml是這樣的<transactdate>2016-08-15</transactdate>,類中的屬性是這樣的:private Date transact_date;

如果我們不作爲,僅僅只是像前面那樣寫:

@XmlElement(name = "transactdate")
public Date getTransact_date() {
return transact_date;
}

這樣的話,如果日期的格式是yyyy-MM-dd的話,OK沒問題,我測試過,是可以的……(好坑,居然可以0.0)

但是,如果日期格式是:<ScheduledDateTime>2015-04-01 09:01</ScheduledDateTime>這種的,或者<LastModifyDate>2016-08-15 11:49:27</LastModifyDate>這樣的話,就不行了。這個時候,就必須要加入下面的註解了:

對於<ScheduledDateTime>2015-04-01 09:01</ScheduledDateTime>這種情況,我的代碼是這樣寫的:

@XmlElement(name = "ScheduledDateTime")
	@XmlJavaTypeAdapter(XmlyyyyMMddHHmmDateAdapter.class)
	public Date getScheduled_date_time() {
		return scheduled_date_time;
	}

重點在於:@XmlJavaTypeAdapter(XmlyyyyMMddHHmmDateAdapter.class),這裏指定了類,就是這個類對日期進行了處理:

類XmlyyyyMMddHHmmDateAdapter:

import java.text.SimpleDateFormat;
import java.util.Date;

import javax.xml.bind.annotation.adapters.XmlAdapter;

public class XmlyyyyMMddHHmmDateAdapter extends XmlAdapter<String, Date> {
	private SimpleDateFormat yyyyMMddHHmm = new SimpleDateFormat(
			"yyyy-MM-dd HH:mm");

	@Override
	public Date unmarshal(String v) throws Exception {
		if ("無".equals(v)) {
			return null;
		}
		return yyyyMMddHHmm.parse(v);
	}

	@Override
	public String marshal(Date v) throws Exception {
		return yyyyMMddHHmm.format(v);
	}
}


代碼很簡單,就幾句代碼,相信將這個看懂之後,<LastModifyDate>2016-08-15 11:49:27</LastModifyDate>這種格式的處理,也難不倒各位了吧~

當然,其實這個XmlAdapter除了能做日期格式的轉換之外,它還有很多其他的作用。可以作爲一箇中間層,在數據封裝進對象之前,對數據進行進一步封裝之類的,這個就讓聰明的各位,去開拓探索吧~


謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章