引入:
上文中我们很详实的演示了如何通过MTOM,让客户端构造一个带附件的SOAP消息,然后服务器端解析处理SOAP消息,并且通过LogHandler来观察请求和返回的SOAP消息的内容。这里我们演示相反过程,就是如何从服务器端下载一个带附件的SOAP消息,然后客户端对这个SOAP消息进行处理,尤其是对附件的处理。
实践:
因为大体上都和上文类似,所以这里我就不做太多的讲解了。
我们设计某个需求,假设公司要面试,大家都把简历投到公司某个简历仓库中,所以我们需要通过web service,根据面试人的名字,从简历仓库获取其面试的职位和他的简历(是一个Microsoft Word文档)
服务端:
首先还是定义VO:
package com.charles.cxfstudy.server.vo; 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.XmlType; /** * 我们这里定义一个VO,关于面试人的信息,比如姓名,职位,简历(是一个word文档) * @author Administrator * */ @XmlType(name="candidateInfo") @XmlAccessorType(XmlAccessType.FIELD) public class CandidateInfo { private String name; //面试人姓名 private String job; //面试人要应聘的职位 @XmlMimeType("application/octet-stream") private DataHandler resume; //面试人的简历,这是一个word文档 public String getName() { return name; } public void setName(String name) { this.name = name; } public String getJob() { return job; } public void setJob(String job) { this.job = job; } public DataHandler getResume() { return resume; } public void setResume(DataHandler resume) { this.resume = resume; } }
然后我们定义一个可以监控请求/相应消息的LogHandler,和上文一样:
/** * SOAP Handler可以用来对SOAP消息进行访问。 * 这里演示的是第一种,它必须实现SOAPHandler<SOAPMessageContext>接口 */ package com.charles.cxfstudy.server.handlers; import java.util.Set; import javax.xml.namespace.QName; import javax.xml.soap.SOAPMessage; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.handler.soap.SOAPHandler; import javax.xml.ws.handler.soap.SOAPMessageContext; /** * 记录进/出Endpoint的消息到控制台的Handler * * @author charles.wang * */ public class LogHandler implements SOAPHandler<SOAPMessageContext> { /** * 如何去处理SOAP消息的逻辑。 这里会先判断消息的类型是入站还是出站消息,然后把消息写到标准输出流 */ public boolean handleMessage(SOAPMessageContext context) { // 先判断消息来源是入站还是出站的 // (入站表示是发送到web service站点的消息,出站表示是从web service站点返回的消息) boolean outbound = (Boolean) context .get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); // 如果是出站消息 if (outbound) { System.out.println("这是从Endpoint返回到客户端的SOAP消息"); } else { System.out.println("这是客户端的请求SOAP消息"); } SOAPMessage message = context.getMessage(); try { message.writeTo(System.out); System.out.println('\n'); } catch (Exception ex) { System.err.print("Exception occured when handling message"); } return true; } /** * 如何去处理错误的SOAP消息 这里会先打印当前调用的方法,然后从消息上下文中取出消息,然后写到标准输出流 */ public boolean handleFault(SOAPMessageContext context) { SOAPMessage message = context.getMessage(); try { message.writeTo(System.out); System.out.println(); } catch (Exception ex) { System.err.print("Exception occured when handling fault message"); } return true; } /** * 这里没有资源清理的需求,所以我们只打印动作到控制台 */ public void close(MessageContext context) { System.out.println("LogHandler->close(context) method invoked"); } public Set<QName> getHeaders() { return null; } }
下面对LogHandler配置,添加其到HandlerChain中:
<?xml version="1.0" encoding="UTF-8"?> <handler-chains xmlns="http://java.sun.com/xml/ns/javaee"> <handler-chain> <!-- 配置可以记录出/入Endpoint消息内容到控制台的Handler --> <handler> <handler-name>LogHandler</handler-name> <handler-class>com.charles.cxfstudy.server.handlers.LogHandler</handler-class> </handler> </handler-chain> </handler-chains>
然后我们开发SEI,它定义了下载面试人信息的业务方法:
/** * 这是一个web服务接口定义,定义了如何对于去人才库下载指定面试人的信息的处理 (内含作为附件的简历) */ package com.charles.cxfstudy.server.services; import javax.jws.WebParam; import javax.jws.WebService; import com.charles.cxfstudy.server.vo.CandidateInfo; import com.charles.cxfstudy.server.vo.Profile; /** * @author Administrator * */ @WebService public interface IDownloadCandidateInfoService { /** * 下载面试人的信息 */ CandidateInfo downloadCandidateInfo(@WebParam(name="name") String name); }
然后开发SIB,它实现了SEI,并且提供业务方法的实现:
package com.charles.cxfstudy.server.services; import java.io.File; import javax.activation.DataHandler; import javax.activation.FileDataSource; import javax.jws.HandlerChain; import javax.jws.WebService; import com.charles.cxfstudy.server.vo.CandidateInfo; /** * 服务实现类,提供下载面试人信息的类 * @author Administrator * */ @WebService(endpointInterface = "com.charles.cxfstudy.server.services.IDownloadCandidateInfoService") @HandlerChain(file="/handler_chains.xml") public class DownloadCandidateInfoServiceImpl implements IDownloadCandidateInfoService { private String resumeRepositoryPath="D:/tmp/resumeRep/"; /** * 去人才库去下载指定的面试人信息,然后返回给客户端 */ public CandidateInfo downloadCandidateInfo(String name) { CandidateInfo ci = new CandidateInfo(); if(name.trim().equals("Charles")){ ci.setName("Charles"); ci.setJob("System Architect"); ci.setResume(new DataHandler(new FileDataSource(new File(resumeRepositoryPath+"charles_cv.doc")))); } else{ ci.setName("Kevin"); ci.setJob("Senior Developer"); ci.setResume(new DataHandler(new FileDataSource(new File(resumeRepositoryPath+"kevin_cv.doc")))); } return ci; } }
然后在bean配置文件中声明我们的SIB,并且在服务器端启用对MTOM的支持:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"> <!-- 导入cxf中的spring的一些配置文件,他们都在cxf-<version>.jar文件中 --> <import resource="classpath:META-INF/cxf/cxf.xml" /> <import resource="classpath:META-INF/cxf/cxf-servlet.xml" /> ... <!-- 这里是第二个web service,它提供下载应聘人信息的功能(主要演示从服务器返回带附件的SOAP消息 --> <jaxws:endpoint id="downloadCandidateInfoService" implementor="com.charles.cxfstudy.server.services.DownloadCandidateInfoServiceImpl" address="/downloadCandidateInfo" > <!-- 下面这段注释在服务器端开启了MTOM,所以它可以正确的处理来自客户端的带附件的SOAP消息 --> <jaxws:properties> <entry key="mtom-enabled" value="true"/> </jaxws:properties> </jaxws:endpoint> </beans>
打包部署到容器,直到通过页面可以访问对应的wsdl。
客户端:
/** * 客户端测试代码 */ package com.charles.mtom.receiveattachedsoap; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; import javax.activation.DataHandler; import javax.activation.DataSource; import javax.activation.FileDataSource; import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; /** * @author charles.wang * */ public class MainTest { public static void main(String [] args) throws Exception{ JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean(); factory.setServiceClass(IDownloadCandidateInfoService.class); //下面三行代码:激活客户端能处理带附件的SOAP消息的功能: Map<String,Object> props = new HashMap<String,Object>(); props.put("mtom-enabled", Boolean.TRUE); factory.setProperties(props); factory.setAddress("http://localhost:8080/cxf_mtom_service/services/downloadCandidateInfo"); //调用业务方法 IDownloadCandidateInfoService service = (IDownloadCandidateInfoService) factory.create(); //调用业务方法,发送soap请求,获取带附件的soap消息 CandidateInfo candidateInfo = service.downloadCandidateInfo("Charles"); String candidateName = candidateInfo.getName(); String candidateJob = candidateInfo.getJob(); InputStream is =candidateInfo.getResume().getInputStream(); File candidateResume = new File("D:/tmp/download"+candidateName+".doc"); OutputStream os = new FileOutputStream(candidateResume); //把返回的附件复制到我们指定的文件中 byte[] b = new byte[100000]; int bytesRead = 0; while ((bytesRead = is.read(b)) != -1) { os.write(b, 0, bytesRead); } os.close(); is.close(); System.out.println("面试候选人名字为:"+candidateName); System.out.println("面试候选人申请职位为:"+candidateJob); System.out.println("面试候选人简历在:"+candidateResume.getAbsolutePath()+","+"文件大小为:"+candidateResume.length()+"字节"); } }
我们运行这个例子,果然,我们传递的面试者的名字对应的附件被服务器塞到SOAP消息中,然后传递到客户端,客户端对其解析还原。
从服务器控制台,我们也看到了LogHandler记录下的真实的从客户端发送以及从服务端返回的SOAP消息:
最后,我们看下实际截图:
显然原始档案库在D:/tmp/resumeRep 目录,我们传递了Charles作为面试者姓名,服务器端从档案库中获取了charles_cv.doc,放到SOAP消息的附件中,然后客户端从附件拿到了文档,并且重命名然后保存到D:/tmp/Charles.doc中,一切顺利。