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