Apache CXF 学习-使用MTOM来让客户端接收从服务端发过来的带附件的SOAP消息

引入:

上文中我们很详实的演示了如何通过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消息中,然后传递到客户端,客户端对其解析还原。

wKioL1MJscSy9baoAAIO_YCN3sw505.jpg


从服务器控制台,我们也看到了LogHandler记录下的真实的从客户端发送以及从服务端返回的SOAP消息:

wKiom1MJsVSi8ZJdAALAqLVoF2M285.jpg

最后,我们看下实际截图:

wKiom1MJsiziCtcXAANiuoFtiDQ596.jpg

显然原始档案库在D:/tmp/resumeRep 目录,我们传递了Charles作为面试者姓名,服务器端从档案库中获取了charles_cv.doc,放到SOAP消息的附件中,然后客户端从附件拿到了文档,并且重命名然后保存到D:/tmp/Charles.doc中,一切顺利。

你可能感兴趣的:(apache,mtom,SOAP消息)