??? 前面写了个JAX-WS的小例子,看到用JAVA6开发WebService确实很简单,也很方便,不过前面也说了,JAVA有三种WebService规范,JAX-WS是其中一种,现在来看看JAXM&SAAJ。
?
??? 最近在做一个接口平台的项目,接口嘛,当然得涉及到对WebService的接口了,我们计划做成一个通用的平台,通过配置文件进行配置后就可以动态对某一个接口进行调用,但像前面的例子那样,每次都要生成一堆客户端代码,这可受不了。如果调用的接口唯一,生成一次客户端代码当然没问题,但如果要调用的接口是动态的,这就不好办了。因此,我需要了解SOAP更多底层的细节,由我自己来组织SOAP中的内容而不是完全由代码生成器生成。
?
??? 仍使用前面例子中的服务器端:
接口:
package com.why.server; import javax.jws.WebParam; import javax.jws.WebService; import javax.jws.soap.SOAPBinding; import javax.xml.ws.soap.MTOM; /** * * @author why * */ @WebService(name="Hello") @SOAPBinding(style = SOAPBinding.Style.RPC) public interface Hello { public void printContext(); public Customer selectCustomerByName(@WebParam(name = "c",header=true)Customer customer); public Customer selectMaxAgeCustomer(Customer c1, Customer c2); }
实现类:
package com.why.server; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Set; import javax.activation.DataHandler; import javax.activation.FileDataSource; import javax.annotation.Resource; import javax.jws.WebService; import javax.xml.ws.WebServiceContext; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.soap.MTOM; /** * * 通过@MTOM注解启动MTOM传输方式,使用CXF实现时,这个注解放在接口或者实现类上都可以,使用JDK1.6自带实现时,需标注在实现类上 * @author why * */ @WebService(serviceName="HelloService",portName="HelloServicePort",targetNamespace="http://service.why.com/",endpointInterface="com.why.server.Hello") @MTOM public class HelloImpl implements Hello { @Resource private WebServiceContext context; @Override public void printContext(){ MessageContext ctx = context.getMessageContext(); Set<String> set = ctx.keySet(); for (String key : set) { System.out.println("{" + key + "," + ctx.get(key) +"}"); try { System.out.println("key.scope=" + ctx.getScope(key)); } catch (Exception e) { System.out.println(key + " is not exits"); } } } @Override public Customer selectCustomerByName(Customer customer) { if("why".equals(customer.getName())){ customer.setId(1); try { customer.setBirthday(new SimpleDateFormat("yyyy-MM-dd").parse("1985-10-07")); } catch (ParseException e) { e.printStackTrace(); } customer.setImageData(new DataHandler(new FileDataSource(new File("c:"+ File.separator + "why.jpg")))); }else{ customer.setId(2); customer.setBirthday(new Date()); customer.setImageData(new DataHandler(new FileDataSource(new File("c:"+ File.separator + "origin.jpg")))); } return customer; } @Override public Customer selectMaxAgeCustomer(Customer c1, Customer c2) { try { // 输出接收到的附件 System.out.println("c1.getImageData().getContentType()=" + c1.getImageData().getContentType()); InputStream is = c1.getImageData().getInputStream(); OutputStream os = new FileOutputStream("c:\\temp1.jpg"); byte[] bytes = new byte[1024]; int c; while ((c = is.read(bytes)) != -1) { os.write(bytes, 0, c); } os.close(); System.out.println("c2.getImageData().getContentType()=" + c2.getImageData().getContentType()); is = c2.getImageData().getInputStream(); os = new FileOutputStream("c:\\temp2.jpg"); bytes = new byte[1024]; while ((c = is.read(bytes)) != -1) { os.write(bytes, 0, c); } os.close(); } catch (IOException e) { e.printStackTrace(); } if (c1.getBirthday().getTime() > c2.getBirthday().getTime()){ return c2; } else{ return c1; } } }
Customer类:
package com.why.server; import java.util.Date; import javax.activation.DataHandler; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlMimeType; import javax.xml.bind.annotation.XmlRootElement; /** * * @author why * */ @XmlRootElement(name = "Customer") @XmlAccessorType(XmlAccessType.FIELD) public class Customer { private long id; private String name; private Date birthday; @XmlMimeType("application/octet-stream") private DataHandler imageData; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public DataHandler getImageData() { return imageData; } public void setImageData(DataHandler imageData) { this.imageData = imageData; } }
发布:
package com.why.server; import javax.xml.ws.Endpoint; /** * * @author why * */ public class SoapServer { public static void main(String[] args) { Endpoint.publish("http://localhost:8080/helloService",new HelloImpl()); } }?
??? 这次不生成客户端类,而是通过自己组织SOAP消息,向服务器发送请求。首先,我们需要一个到WebService服务的连接(就像Connection之于JDBC),通过javax.xml.soap.SOAPConnectionFactory的createConnection()可以获得一个WebService连接。获得连接之后,我们就可以组织我们的SOAP消息了。通过javax.xml.soap.MessageFactory的createMessage()方法,获得一个javax.xml.soap.SOAPMessage,SOAPMessage就是我们SOAP消息的入口。我们知道,SOAP其实就是一个XML,有了SOAPMessage这个入口,剩下的就是对XML的组织和解析了。对于SOAP消息的各个部分,SOAPMessage都有对应的接口:
// 获取SOAP连接工厂 SOAPConnectionFactory factory = SOAPConnectionFactory.newInstance(); // 从SOAP连接工厂创建SOAP连接对象 SOAPConnection connection = factory.createConnection(); // 获取消息工厂 MessageFactory mFactory = MessageFactory.newInstance(); // 从消息工厂创建SOAP消息对象 SOAPMessage message = mFactory.createMessage(); // 创建SOAPPart对象 SOAPPart part = message.getSOAPPart(); // 创建SOAP信封对象 SOAPEnvelope envelope = part.getEnvelope(); // 创建SOAPHeader对象 SOAPHeader header = message.getSOAPHeader(); // 创建SOAPBody对 SOAPBody body = envelope.getBody();?
??? 把我们需要传递的参数组织好,通过connection.call方法进行对WebService的调用,他仍然会给我们返回一个SOAPMessage对象,对应服务器端的三个函数,我分别写了对应的三个方法对其进行调用,以下是我的客户端类:
?
package com.why.client; import java.io.ByteArrayOutputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.util.Iterator; import java.util.UUID; import javax.activation.DataHandler; import javax.activation.FileDataSource; import javax.xml.namespace.QName; import javax.xml.soap.AttachmentPart; import javax.xml.soap.MessageFactory; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPBodyElement; import javax.xml.soap.SOAPConnection; import javax.xml.soap.SOAPConnectionFactory; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPHeaderElement; import javax.xml.soap.SOAPMessage; import javax.xml.soap.SOAPPart; /** * * @author why * */ public class SoapClient { public static void main(String[] args) throws Exception{ printContext(); selectCustomerByName(); selectMaxAgeCustomer(); } /** * 调用一个无参函数 * @throws Exception */ public static void printContext() throws Exception{ // 获取SOAP连接工厂 SOAPConnectionFactory factory = SOAPConnectionFactory.newInstance(); // 从SOAP连接工厂创建SOAP连接对象 SOAPConnection connection = factory.createConnection(); // 获取消息工厂 MessageFactory mFactory = MessageFactory.newInstance(); // 从消息工厂创建SOAP消息对象 SOAPMessage message = mFactory.createMessage(); // 创建SOAPPart对象 SOAPPart part = message.getSOAPPart(); // 创建SOAP信封对象 SOAPEnvelope envelope = part.getEnvelope(); // 创建SOAPHeader对象 SOAPHeader header = message.getSOAPHeader(); // 创建SOAPBody对象 SOAPBody body = envelope.getBody(); // 创建XML的根元素 SOAPBodyElement bodyElementRoot = body.addBodyElement(new QName("http://server.why.com/", "printContext", "ns1")); // 访问Web服务地址 SOAPMessage reMessage = connection.call(message, new URL("http://127.0.0.1:8080/helloService")); // 控制台输出返回的SOAP消息 OutputStream os = System.out; reMessage.writeTo(os); connection.close(); } /** * 调用一个在soap:HEADER中传递参数的函数 * @throws Exception */ public static void selectCustomerByName() throws Exception{ // 获取SOAP连接工厂 SOAPConnectionFactory factory = SOAPConnectionFactory.newInstance(); // 从SOAP连接工厂创建SOAP连接对象 SOAPConnection connection = factory.createConnection(); // 获取消息工厂 MessageFactory mFactory = MessageFactory.newInstance(); // 从消息工厂创建SOAP消息对象 SOAPMessage message = mFactory.createMessage(); // 创建SOAPPart对象 SOAPPart part = message.getSOAPPart(); // 创建SOAP信封对象 SOAPEnvelope envelope = part.getEnvelope(); // 创建SOAPHeader对象 SOAPHeader header = message.getSOAPHeader(); // 创建SOAPBody对象 SOAPBody body = envelope.getBody(); // 创建XML的根元素 SOAPHeaderElement headerElementRoot = header.addHeaderElement(new QName("http://server.why.com/", "c", "ns1")); SOAPBodyElement bodyElementRoot = body.addBodyElement(new QName("http://server.why.com/", "selectCustomerByName", "ns1")); headerElementRoot.addChildElement(new QName("name")).addTextNode("why"); // 访问Web服务地址 SOAPMessage reMessage = connection.call(message, new URL("http://127.0.0.1:8080/helloService")); // 控制台输出返回的SOAP消息 OutputStream os = System.out; reMessage.writeTo(os); // 输出SOAP消息中的附件 Iterator<AttachmentPart> it = reMessage.getAttachments(); while (it.hasNext()) { InputStream ins = it.next().getDataHandler().getInputStream(); byte[] b = new byte[ins.available()]; OutputStream ous = new FileOutputStream("c:\\aaa.jpg"); while (ins.read(b) != -1) { ous.write(b); } ous.close(); } connection.close(); } /** * 调用一个在soap:Body中传递参数的函数 * @throws Exception */ public static void selectMaxAgeCustomer() throws Exception{ // 获取SOAP连接工厂 SOAPConnectionFactory factory = SOAPConnectionFactory.newInstance(); // 从SOAP连接工厂创建SOAP连接对象 SOAPConnection connection = factory.createConnection(); // 获取消息工厂 MessageFactory mFactory = MessageFactory.newInstance(); // 从消息工厂创建SOAP消息对象 SOAPMessage message = mFactory.createMessage(); // 创建SOAPPart对象 SOAPPart part = message.getSOAPPart(); // 创建SOAP信封对象 SOAPEnvelope envelope = part.getEnvelope(); // 创建SOAPHeader对象 SOAPHeader header = message.getSOAPHeader(); // 创建SOAPBody对象 SOAPBody body = envelope.getBody(); // 设置Content-Type MimeHeaders hd = message.getMimeHeaders(); hd.setHeader("Content-Type", "application/xop+xml; charset=utf-8; type=\"text/xml\""); // 创建XML的根元素 SOAPBodyElement bodyElementRoot = body.addBodyElement(new QName("http://server.why.com/", "selectMaxAgeCustomer", "ns1")); // 创建Customer实例1 SOAPElement elementC1 = bodyElementRoot.addChildElement(new QName("arg0")); elementC1.addChildElement(new QName("id")).addTextNode("1"); elementC1.addChildElement(new QName("name")).addTextNode("A"); elementC1.addChildElement(new QName("birthday")).addTextNode("1989-01-28T00:00:00.000+08:00"); // 创建附件对象 AttachmentPart attachment = message.createAttachmentPart(new DataHandler(new FileDataSource("c:\\c1.jpg"))); // 设置Content-ID attachment.setContentId("<" + UUID.randomUUID().toString() + ">"); attachment.setMimeHeader("Content-Transfer-Encoding", "binary"); message.addAttachmentPart(attachment); SOAPElement elementData = elementC1.addChildElement(new QName("imageData")); // 添加XOP支持 elementData.addChildElement( new QName("http://www.w3.org/2004/08/xop/include", "Include","xop")) .addAttribute(new QName("href"),"cid:" + attachment.getContentId().replaceAll("<", "").replaceAll(">", "")); // 创建Customer实例2 SOAPElement elementC2 = bodyElementRoot.addChildElement(new QName("arg1")); elementC2.addChildElement(new QName("id")).addTextNode("2"); elementC2.addChildElement(new QName("name")).addTextNode("B"); elementC2.addChildElement(new QName("birthday")).addTextNode("1990-01-28T00:00:00.000+08:00"); AttachmentPart attachment2 = message.createAttachmentPart(new DataHandler(new FileDataSource("c:\\c2.jpg"))); attachment2.setContentId("<" + UUID.randomUUID().toString() + ">"); message.addAttachmentPart(attachment2); SOAPElement elementData2 = elementC2.addChildElement(new QName("imageData")); elementData2.addChildElement( new QName("http://www.w3.org/2004/08/xop/include", "Include","xop")) .addAttribute(new QName("href"),"cid:" + attachment2.getContentId().replaceAll("<", "").replaceAll(">", "")); // 控制台输出发送的SOAP消息 OutputStream os = new ByteArrayOutputStream(); message.writeTo(os); String soapStr = os.toString(); System.out.println("\n@@@@@@@@@@@@@@@@@@\n"+soapStr+"\n@@@@@@@@@@@@@@@@@@"); // 访问Web服务地址 SOAPMessage reMessage = connection.call(message, new URL("http://127.0.0.1:8080/helloService")); // 控制台输出返回的SOAP消息 OutputStream baos = new ByteArrayOutputStream(); reMessage.writeTo(baos); String soapStr2 = baos.toString(); System.out.println("\n#############\n"+soapStr2+"\n################"); // // 输出SOAP消息中的第一个子元素的元素名称 System.out.println("\n<<<<<<<<<<<<<<<<<<<" + reMessage.getSOAPBody().getFirstChild().getLocalName()); // 输出SOAP消息中的附件 Iterator<AttachmentPart> it = reMessage.getAttachments(); while (it.hasNext()) { InputStream ins = it.next().getDataHandler().getInputStream(); byte[] b = new byte[ins.available()]; OutputStream ous = new FileOutputStream("c:\\bbb.jpg"); while (ins.read(b) != -1) { ous.write(b); } ous.close(); } connection.close(); } }
?
??? 使用SAAJ创建附件时,需设置Content-Type=application/xop+xml; charset=utf-8; type="text/xml",否则服务器端获取不到这个附件,查看发送给服务器端的SOAP消息可以看到,默认Content-Type被置为text/xml; charset=utf-8,因此,需在代码中加入:
MimeHeaders hd = message.getMimeHeaders(); hd.setHeader("Content-Type", "application/xop+xml; charset=utf-8; type=\"text/xml\"");?
??? SOAPMessage有一个writeTo(OutputStream os)方法,可以将整个SOAP消息的内容写入一个输出流中,我们可以截获这个输出流的内容进行分析或再次整理。
?
附件是我的工程(2010-11-15更新)
?
eg:服务端:public void xxx(Customer[] c);
或是我另写一个Customer的Javabean类:Customers{
List<customer> custm;
...
}
服务端方法:public void xxx(Customers cs);
这个时候参数中的对象包含有List类型的参数,消息文件又该如何编写!?
望指教!~~~
eg:服务端:public void xxx(Customer[] c);
或是我另写一个Customer的Javabean类:Customers{
List<customer> custm;
...
}
服务端方法:public void xxx(Customers cs);
这个时候参数中的对象包含有List类型的参数,消息文件又该如何编写!?
望指教!~~~
对于数组形式,查看wsdl文件可以看到,会被封装成类似这种方式:
<xs:complexType name="customerArray" final="#all">
<xs:sequence>
<xs:element name="item" type="tns:customer" minOccurs="0" maxOccurs="unbounded" nillable="true"/>
</xs:sequence>
</xs:complexType>
所以,在封装的时候,只要将其封装为
<arg0>
<item>
<Customer>...</Customer>
</item>
<item>
<Customer>...</Customer>
</item>
...
</arg0>
这样的层次结构就可以了。
对于集合,使用JDK自带的实现时,貌似不能直接传递,但可以变通一下:自定义一个bean,将要传递的list作为这个bean的属性就可以传递了,也就是说,对象内的list是可以传递的。
public class CustomerList{
private ArrayList list;
private HashMap map;
//get and set method
}
SOAP封装List或Map时类似这样的方式:
<list>aaa</list>
<list>bbb</list>
<map>
<entry>
<key>1</key>
<value>111</value>
</entry>
<entry>
<key>2</key>
<value>222</value>
</entry>
</map>
不过我觉得应该尽量避免使用这种特定语言的内置对象。
若使用其他实现,如CXF,可以直接传递,SOAP消息中封装方式与数组类似,不知道是JDK的BUG还是人家压根没考虑实现这种方式。
如果说的不对,还望大侠们指教啊!