您的位置:首页 > 编程语言 > Java开发

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

2016-08-14 22:54 447 查看
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 TypeOptional 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除了能做日期格式的转换之外,它还有很多其他的作用。可以作为一个中间层,在数据封装进对象之前,对数据进行进一步封装之类的,这个就让聪明的各位,去开拓探索吧~

谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  jaxb java xml
相关文章推荐