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中各個元素的名字。
呼~ 終於走完了