Apache CXF 學習-使用MTOM來從客戶端發送帶附件的SOAP消息到服務端

引入:java

一般意義上說,SOAP消息就是一個帶字符串的內容包,其實CXF還能夠發送/接收帶附件的SOAP消息,這樣SOAP的消息看起來就以下所示:web

wKioL1MJpC3BbshFAAD12r-8yLU052.jpg

咱們這篇文章就着重講解如何來從客戶端發送帶附件的SOAP消息到服務器Endpoint,而且給出詳細的例子,下篇文章會講解相反過程,如何客戶端接收而且處理來自Endpoint的帶附件的SOAP消息。spring


實踐:apache

咱們假想有這樣一個需求,假設咱們如今有個ProfileManagement系統,用戶須要在客戶端吧本身的Profile上傳到ProfileManagement系統,而且這個Profile中除了包含姓名,年齡外還包含本身的頭像圖片(portrait),因此若是用CXF來作的話,就是要上傳一個帶附件的SOAP消息。服務器


服務端:app

首先,咱們要定義一個VO來表示Profile信息:框架

/**
 * 這個一個VO,咱們定義了一個Profile類型,它會帶有圖片的附件信息。接下來,咱們會用JAXB框架將其映射爲wsdl文件中的類型定義
 */
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.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
 * 咱們定義一個有姓名(name),年齡(age),圖片(portrait)的Profile類
 * @author charles.wang
 *
 */
@XmlType(name="profile")
//注意,這裏必須用下面這行指定XmlAccessType.FIELD,來標註利用JAXB框架在JAVA和XML之間轉換隻關注字段,
//而不會關注getter(),不然默認2個都會關注則會發生如下錯誤:
//com.sun.xml.bind.v2.runtime.IllegalAnnotationException
//Class has two properties of the same name "portrait",我調試了很久才解決這個問題的
@XmlAccessorType(XmlAccessType.FIELD)
public class Profile {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
    private String name;
    private int age;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
    //這個字段是一個圖片資源
    @XmlMimeType("application/octet-stream")
    private DataHandler portrait;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
    private String p_w_picpathFileExtension;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public DataHandler getPortrait() {
        return portrait;
    }
    public void setPortrait(DataHandler portrait) {
        this.portrait = portrait;
    }
    public String getImageFileExtension() {
        return p_w_picpathFileExtension;
    }
    public void setImageFileExtension(String p_w_picpathFileExtension) {
        this.p_w_picpathFileExtension = p_w_picpathFileExtension;
    }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
}

和通常狀況同樣,咱們這裏用了JAXB的註釋,這樣JAXB框架會自動的把咱們的基於JAX-WS的請求/響應參數轉爲XML格式的消息。特別注意有2點:ide

a.對於附件資源,咱們這裏是用了@XmlMimeType("application/octet-stream")來標示,而且其字段類型爲DataHandler,這樣的目的是爲了能讓JAXB框架正確的處理附件資源,其實這裏MIME類型也能夠設爲具體類型,好比 p_w_picpath/jpeg, 可是這種用法更通用。測試

b.須要加@XmlAccessorType(XmlAccessType.FIELD),這樣可使得JAXB只處理字段類型到附件的映射,而不會處理getter()方法,不然會報錯this

com.sun.xml.bind.v2.runtime.IllegalAnnotationException  
//Class has two properties of the same name "portrait"


而後,由於咱們用的是JAX-WS的調用方式,因此咱們可能從代碼中看不到真正SOAP消息長什麼樣子,因此咱們這裏定義了一個LogHandler(具體參見http://supercharles888.blog.51cto.com/609344/1361866  ),它能夠把入Endpoint和出Endpoint的SOAP消息進行區分,而且把消息內容打印到服務器的控制檯上:

/**
 * 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;
    }
}

咱們定義一個HandlerChain文件,把LogHandler加進去:

<?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>


如今咱們就開始寫服務器端的處理邏輯了,咱們可讓其從客戶端發過來的帶附件的SOAP消息中提取通常字段和附件,對於附件的圖片(頭像文件),咱們複製到指定位置。

首先,咱們定義SEI,它是個服務接口:

/**
 * 這是一個web服務接口定義,定義瞭如何對於上傳的Profile進行處理
 */
package com.charles.cxfstudy.server.services;
import javax.jws.WebParam;
import javax.jws.WebService;
import com.charles.cxfstudy.server.vo.Profile;
/**
 * @author Administrator
 *
 */
@WebService
public interface IUploadProfileService {
                                                                                                                                                                                                                                                                                                                                                                                                                                                     
    /**
     * 上傳Profile,而且對於Profile進行處理
     */
    void uploadProfile(@WebParam(name="profile") Profile profile);
}


而後咱們定義SIB,它實現了SEI:

/**
 * 服務實現類,提供上傳Profile的服務
 */
package com.charles.cxfstudy.server.services;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.activation.DataHandler;
import javax.jws.HandlerChain;
import javax.jws.WebParam;
import javax.jws.WebService;
import com.charles.cxfstudy.server.vo.Profile;
/**
 * @author charles.wang
 *
 */
@WebService(endpointInterface = "com.charles.cxfstudy.server.services.IUploadProfileService")
@HandlerChain(file="/handler_chains.xml")
public class UploadProfileServiceImpl implements IUploadProfileService {
    /**
     * 上傳Profile,而且對於Profile進行處理
     */
    public void uploadProfile(@WebParam(name="profile") Profile profile){
                                                                                                                                                                                                                                                                                                                                                                                                                                           
        //從參數中得到相關信息
        String name = profile.getName();                  //姓名
        int    age  = profile.getAge();                   //年齡
                                                                                                                                                                                                                                                                                                                                                                                                                                           
        DataHandler portrait = profile.getPortrait();     //肖像圖片
        String p_w_picpathFileExtension = profile.getImageFileExtension(); //肖像圖片的擴展名
                                                                                                                                                                                                                                                                                                                                                                                                                                           
        try{
            //獲取輸入流,來獲取圖片資源
            InputStream is = portrait.getInputStream();
            //打開一個輸出流,來保存得到的圖片
            OutputStream os = new FileOutputStream("d:/tmp/uploadTo/"+name+"."+p_w_picpathFileExtension);
            //進行復制,把得到的肖像圖片複製到
            byte[] b = new byte[10000];
            int byteRead=0;
            while ( (byteRead=is.read(b))!= -1){
                os.write(b,0,byteRead);
            }
            os.flush();
            os.close();
            is.close();
        }catch(IOException ex){
            ex.printStackTrace();
        }
                                                                                                                                                                                                                                                                                                                                                                                                                                               
    }
                                                                                                                                                                                                                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                                                                                                                                                                                                       
}

大致上代碼很容易讀懂(呵呵,我自信我代碼的可讀性),和慣例同樣,咱們用@HandlerChain註解來使得LogHandler能夠做用到當前的服務類上而且打印進出當前服務器類的SOAP消息。


爲了讓MTOM生效(也就是讓服務器端支持對帶附件的SOAP消息處理),咱們必須在當前SIB的bean定義文件中打開MTOM開關,以下的20-22行(在beans.xml中)

<?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,它提供上傳用戶Profile的功能(主要演示發送帶附件的SOAP消息到服務器) -->
<jaxws:endpoint
id="uploadProfileService"
implementor="com.charles.cxfstudy.server.services.UploadProfileServiceImpl"
address="/uploadProfile" >
  <!-- 下面這段註釋在服務器端開啓了MTOM,因此它能夠正確的處理來自客戶端的帶附件的SOAP消息 -->
   <jaxws:properties>
       <entry key="mtom-enabled" value="true"/>
   </jaxws:properties>
</jaxws:endpoint>
...
</beans>


如今打包完部署應用到服務器上,運行就能夠了。


客戶端:

如今咱們來編寫客戶端,其實很簡單,主要就是封裝一個Profile對象(帶附件),而後調用業務方法進行上傳操做。

/**
 * 客戶端測試代碼
 */
package com.charles.mtom.sendattachedsoap;
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){
                                                                                                                                                                                                                                                                             
    JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
    factory.setServiceClass(IUploadProfileService.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/uploadProfile");
                                                                                                                                                                                                                                                                             
    //調用業務方法
    IUploadProfileService service = (IUploadProfileService) factory.create();
                                                                                                                                                                                                                                                                             
    //建立 一個要上傳的Profile對象
    Profile profile = new Profile();
    profile.setName("Charles");
    profile.setAge(28);
                                                                                                                                                                                                                                                                             
    //下面兩行特別注意如何去吧一個附件附加到請求中的
    DataSource source = new FileDataSource("F:/p_w_picpaths/myprofileImage.jpg");
    profile.setPortrait(new DataHandler(source));
    profile.setImageFileExtension("jpg");
                                                                                                                                                                                                                                                                             
    //調用業務方法,發送soap請求
    service.uploadProfile(profile);
                                                                                                                                                                                                                                                                             
    }
}

條理也很清楚,這裏特別注意是第23-25行激活了客戶端對MTOM的支持。第36到39行演示瞭如何把一個附件(好比圖片文件)附加到SOAP消息上。


咱們運行例子。

能夠清楚的看到從客戶端發送的SOAP消息和從服務器端返回的SOAP消息,顯然,發送的消息是以p_w_upload的形式附加在SOAP消息上的,符合咱們文章開始的結構示意圖。

wKiom1MJqsvj4wBgAAwYynEXQAo116.jpg

咱們去文件系統檢查,果真,客戶端經過調用,把圖片文件從F:/Images/myProfileImage.jpg傳遞給了webservice,而後webservice從SOAP消息中拿到附件,而且更名爲Charles.jpg,而後存儲到了D:/tmp/uploadTo目錄

wKiom1MJq1nDrrw4AAI7L0l9pbU118.jpg


額外話題

注意,對於客戶端代碼的開啓MTOM的支持是必不可少的:

//下面三行代碼:激活客戶端能處理帶附件的SOAP消息的功能:
Map<String,Object> props = new HashMap<String,Object>();
props.put("mtom-enabled", Boolean.TRUE);
factory.setProperties(props);


若是沒有這3行的話,咱們發送的SOAP消息就是一個不帶附件的消息,而是把咱們的資源文件(好比圖片)轉爲Base64,而後把編碼後的內容和普通字段同樣放在SOAP消息中。


好比,當咱們註釋掉上面幾行,再發送請求的時候,其請求的SOAP消息就以下:

wKiom1MJrT_BKTouAA604U_Tol0837.jpg

這顯然就不是一個帶附件的SOAP消息了,由於Base64編碼很低效,要大量運算,因此若是附件文件很大,那麼轉爲Base64就會花費不少時間,這不是一個很好的選擇。

相關文章
相關標籤/搜索