java创建webservice client
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中各个元素的名字。
呼~ 终于走完了