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;
}
}
因爲是實際使用的一個類,所以可能會有一些特別的註釋,只要沒有泄露太多信息的註釋,我這裏就不去掉了。其中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除了能做日期格式的轉換之外,它還有很多其他的作用。可以作爲一箇中間層,在數據封裝進對象之前,對數據進行進一步封裝之類的,這個就讓聰明的各位,去開拓探索吧~
謝謝!