java的webservice實現有多種方式,可用的工具也有一些。之前對這塊的只是比較缺乏,以至於一上來就一直看spring webservice.花費了幾天后發現和要用的功能不符,就···
當前學習的需求是webservice client。因此整篇文章用來說明java webserviceclient的創建過程。
首先使用java自帶的soapconnection實現。那首先具體的client訪問流程為
SOAPConnection connection = null;
try {
SOAPConnectionFactory sfc = SOAPConnectionFactory.newInstance();
connection = sfc.createConnection();
SOAPMessage soapMessage = ObjectToSoapXml(object, nsMethod, nsName);
URL endpoint = new URL(new URL(url),
"",
new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL url) throws IOException {
URL target = new URL(url.toString());
URLConnection connection = target.openConnection();
// Connection settings
connection.setConnectTimeout(120000); // 2 min
connection.setReadTimeout(60000); // 1 min
return(connection);
}
});
SOAPMessage response = connection.call(soapMessage, endpoint);
} catch (Exception e) {
}
這其中首先創建soapconnection調用call方法向server端發送請求,call的兩個參數一個是發送的消息soapmessage,一個是服務器端地址。
那這里的關鍵是soapmessage的封裝,那在java中,信息一般采用對象的形式存儲。問題就是怎樣把含有信息的對象封裝成soapMessage.我采用的方法是
private static<T> SOAPMessage ObjectToSoapXml(T object, String nsMethod, String nsName) {
SOAPMessage soapMessage = null;
try {
MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL);
soapMessage = messageFactory.createMessage();
SOAPPart soapPart = soapMessage.getSOAPPart();
// SOAP Envelope
SOAPEnvelope envelope = soapPart.getEnvelope();
envelope.setPrefix("SOAP-ENV");
envelope.addNamespaceDeclaration("ns1", nsMethod);
// SOAP Body
SOAPBody soapBody = envelope.getBody();
soapBody.setPrefix("SOAP-ENV");
soapBody.addDocument(jaxbObjectToXML(object, nsMethod, nsName));//將body中的類通過document的形式寫入
soapMessage.saveChanges();
} catch (SOAPException e) {
e.printStackTrace();
}
return soapMessage;
}
使用messagefactory創建soapmessage,這里有一點要注意SOAPConstants.SOAP_1_1_PROTOCOL,使用這個參數的原因是要指定soapmessage的content-type為text/xml,charset=utf-8,那其他的可再參考其他常量。那創建的soapmessage就包含soapenvelop了,可添加前綴和命名空間。接下來就是在soapbody中添加要傳遞的信息對象。一開始看到的各種例子都是一個元素一個元素的添加。可擴展性太差了。一直考慮將整個對象添加進去。采用方式是soapbody adddocument的方式。那就要把信息對象轉換成org.w3c.dom.Document對象。接下來是轉換方式
private static<T> Document jaxbObjectToXML(T emp, String nsMethod, String nsName) {
try {
JAXBContext context = JAXBContext.newInstance(emp.getClass());
// Create the Document
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.newDocument();
// Marshal the Object to a Document
Marshaller marshaller = context.createMarshaller();
marshaller.marshal(emp, document);
if(null != document) {
document.renameNode(document.getFirstChild(), nsMethod, nsName);
}
return document;
} catch (Exception e) {
logger.error(e.toString(), e);
}
return null;
}
使用jaxb將object轉成xml,marshal方法可直接實現對象到document。我在這里遇到的一個問題是,對象到xml的時候,我的xml要求根元素有前綴。沒知識實在不好添加。最終找到實現方式是轉換成的document獲取根元素,通過getfirstchild的方式,然后對根元素重命名 renameNode,這里的問題是這個方法的后兩個參數一個是命名空間,一個是重命名后節點名稱。我要使用的是含有前綴,無命名空間。其實這樣說就是沒知識了。前綴和命名空間應該是對應的,命名空間和前綴應一起設置。只是命名空間並不顯示。若只設置前綴,無命名空間則會報錯。那這里問題就愉快的解決了,此時是完成了對象封裝成soapmessage,就可以通過soapconnection向服務端發消息了。
那消息發送出去服務端返回結果是不是還要處理一下呢?當然可以通過元素逐級獲取的方式獲取你要的元素。同樣擴展性太差。我采用的方式同樣是把soapbody中的內容實現到對象的對應。
public static<T> T parseSoapMessage(SOAPMessage reqMsg, T object, String name) {
try {
reqMsg = removeUTFBOM(reqMsg);
SOAPBody soapBody = reqMsg.getSOAPBody();
Document document = soapBody.extractContentAsDocument();//獲取返回信息中的消息體
document.renameNode(document.getFirstChild(), null, name);//根節點去掉前綴
JAXBContext jc = JAXBContext.newInstance(object.getClass());
Unmarshaller unmarshaller = jc.createUnmarshaller();
object = (T)unmarshaller.unmarshal(document);
}catch(Exception e) {
logger.error(e.toString(), e);
}
return object;
}
大問題來了,調用soapconnection返回的soapmessage,直接調用getsoapbody報錯了。幾番查看,是返回的結果是utf-8 BOM滴,額,這個處理沒有找到好的方式。最終也只是將soapmessage轉成string將BOM去掉之后再轉換回來。因之前對象到soapmessage轉換時使用document,那現在也考慮這種方式並且可行了。那注意抽取出來的document呢我這邊還是含有前綴的,所以使用renameNode做了一下去除前綴的處理,然后使用unmarshal將document嗨皮的轉成對象了。終於完成了。
去BOM的方式
private static SOAPMessage removeUTFBOM(SOAPMessage soapMessage) {
ByteArrayOutputStream baos = null;
try
{
baos = new ByteArrayOutputStream();
soapMessage.writeTo(baos);
String soapString = baos.toString();
if (baos.toString().startsWith("\uFEFF")) {
soapString = soapString.substring(1);
InputStream is = new ByteArrayInputStream(soapString.getBytes());
soapMessage = MessageFactory.newInstance().createMessage(null, is);
}
} catch (SOAPException e) {
logger.error(e.toString(), e);
} catch (IOException e) {
logger.error(e.toString(), e);
}
return soapMessage;
}
最后還有一點就是xml對應bean的定義
我采取的方式是在類上注解
@XmlRootElement(name = "name") //聲明為根元素,根元素名字
@XmlAccessorType(XmlAccessType.FIELD)
然后在各個元素上
@XmlElement(name = "elementname", nillable = true)
指定該屬性對應xml中元素的名字,使用nillable屬性是因為,若此屬性為空的話,相應的xml元素便不存在,指定此屬性為true,則為空的屬性也會顯示。
再就是為根元素的類中含有其他對象時,其他對象的聲明方式
首先在類上聲明 @XmlAccessorType(XmlAccessType.FIELD)
相應屬性上聲明 @XmlElement(name = "elementname", nillable = true)
再就是有些屬性為list
@XmlElementWrapper(name="ListName")
@XmlElement(name="ListelementName", nillable = true)
wrapper指定list的名字,接下來指定list中各個元素的名字。
呼~ 終於走完了
