Apache CXF 学习-使用MTOM来从客户端发送带附件的SOAP消息到服务端

简介:

引入:

通常意义上说,SOAP消息就是一个带字符串的内容包,其实CXF还可以发送/接收带附件的SOAP消息,这样SOAP的消息看起来就如下所示:

wKioL1MJpC3BbshFAAD12r-8yLU052.jpg

我们这篇文章就着重讲解如何来从客户端发送带附件的SOAP消息到服务器Endpoint,并且给出详细的例子,下篇文章会讲解相反过程,如何客户端接收并且处理来自Endpoint的带附件的SOAP消息。


实践:

我们假想有这样一个需求,假设我们现在有个ProfileManagement系统,用户需要在客户端吧自己的Profile上传到ProfileManagement系统,并且这个Profile中除了包含姓名,年龄外还包含自己的头像图片(portrait),所以如果用CXF来做的话,就是要上传一个带附件的SOAP消息。


服务端:

首先,我们要定义一个VO来表示Profile信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
  * 这个一个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 imageFileExtension;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
     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  imageFileExtension;
     }
     public  void  setImageFileExtension(String imageFileExtension) {
         this .imageFileExtension = imageFileExtension;
     }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
}

和一般情况一样,我们这里用了JAXB的注释,这样JAXB框架会自动的把我们的基于JAX-WS的请求/响应参数转为XML格式的消息。特别注意有2点:

a.对于附件资源,我们这里是用了@XmlMimeType("application/octet-stream")来标示,并且其字段类型为DataHandler,这样的目的是为了能让JAXB框架正确的处理附件资源,其实这里MIME类型也可以设为具体类型,比如 image/jpeg, 但是这种用法更通用。

b.需要加@XmlAccessorType(XmlAccessType.FIELD),这样可以使得JAXB只处理字段类型到附件的映射,而不会处理getter()方法,否则会报错

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消息进行区分,并且把消息内容打印到服务器的控制台上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
  * 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加进去:

1
2
3
4
5
6
7
8
9
10
<? 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,它是个服务接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
  * 这是一个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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
  * 服务实现类,提供上传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 imageFileExtension = profile.getImageFileExtension();  //肖像图片的扩展名
                                                                                                                                                                                                                                                                                                                                                                                                                                            
         try {
             //获取输入流,来获取图片资源
             InputStream is = portrait.getInputStream();
             //打开一个输出流,来保存获得的图片
             OutputStream os =  new  FileOutputStream( "d:/tmp/uploadTo/" +name+ "." +imageFileExtension);
             //进行复制,把获得的肖像图片复制到
             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中)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<? 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对象(带附件),然后调用业务方法进行上传操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
  * 客户端测试代码
  */
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:/images/myprofileImage.jpg" );
     profile.setPortrait( new  DataHandler(source));
     profile.setImageFileExtension( "jpg" );
                                                                                                                                                                                                                                                                              
     //调用业务方法,发送soap请求
     service.uploadProfile(profile);
                                                                                                                                                                                                                                                                              
     }
}

条理也很清楚,这里特别注意是第23-25行激活了客户端对MTOM的支持。第36到39行演示了如何把一个附件(比如图片文件)附加到SOAP消息上。


我们运行例子。

可以清楚的看到从客户端发送的SOAP消息和从服务器端返回的SOAP消息,显然,发送的消息是以attachment的形式附加在SOAP消息上的,符合我们文章开始的结构示意图。

wKiom1MJqsvj4wBgAAwYynEXQAo116.jpg

我们去文件系统检查,果然,客户端通过调用,把图片文件从F:/Images/myProfileImage.jpg传递给了webservice,然后webservice从SOAP消息中拿到附件,并且改名为Charles.jpg,然后存储到了D:/tmp/uploadTo目录

wKiom1MJq1nDrrw4AAI7L0l9pbU118.jpg


额外话题

注意,对于客户端代码的开启MTOM的支持是必不可少的:

1
2
3
4
//下面三行代码:激活客户端能处理带附件的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就会花费很多时间,这不是一个很好的选择。





本文转自 charles_wang888 51CTO博客,原文链接:http://blog.51cto.com/supercharles888/1362358,如需转载请自行联系原作者
目录
相关文章
|
15天前
|
Java Apache C++
别再手写RPC了,Apache Thrift帮你自动生成RPC客户端及服务端代码
Thrift 是一个轻量级、跨语言的远程服务调用框架,由 Facebook 开发并贡献给 Apache。它通过 IDL 生成多种语言的 RPC 服务端和客户端代码,支持 C++、Java、Python 等。Thrift 的主要特点包括开发速度快、接口维护简单、学习成本低和多语言支持。广泛应用于 Cassandra、Hadoop 等开源项目及 Facebook、百度等公司。
别再手写RPC了,Apache Thrift帮你自动生成RPC客户端及服务端代码
|
6月前
|
分布式计算 Java 大数据
IO流【Java对象的序列化和反序列化、File类在IO中的作用、装饰器模式构建IO流体系、Apache commons-io工具包的使用】(四)-全面详解(学习总结---从入门到深化)
IO流【Java对象的序列化和反序列化、File类在IO中的作用、装饰器模式构建IO流体系、Apache commons-io工具包的使用】(四)-全面详解(学习总结---从入门到深化)
98 0
|
6月前
|
Java API Apache
ZooKeeper【基础 03】Java 客户端 Apache Curator 基础 API 使用举例(含源代码)
【4月更文挑战第11天】ZooKeeper【基础 03】Java 客户端 Apache Curator 基础 API 使用举例(含源代码)
74 11
|
6月前
|
存储 NoSQL 数据处理
Apache Paimon流式湖仓学习交流群成立
Apache Paimon流式湖仓学习交流群成立
491 59
|
6月前
|
Java API Apache
Apache CXF生成WebService的客户端
Apache CXF生成WebService的客户端
227 0
|
6月前
|
Apache
Apache ZooKeeper - 构建ZooKeeper源码环境及StandAlone模式下的服务端和客户端启动
Apache ZooKeeper - 构建ZooKeeper源码环境及StandAlone模式下的服务端和客户端启动
126 2
|
3月前
|
存储 消息中间件 Java
Apache Flink 实践问题之原生TM UI日志问题如何解决
Apache Flink 实践问题之原生TM UI日志问题如何解决
46 1
|
30天前
|
SQL Java API
Apache Flink 2.0-preview released
Apache Flink 社区正积极筹备 Flink 2.0 的发布,这是自 Flink 1.0 发布以来的首个重大更新。Flink 2.0 将引入多项激动人心的功能和改进,包括存算分离状态管理、物化表、批作业自适应执行等,同时也包含了一些不兼容的变更。目前提供的预览版旨在让用户提前尝试新功能并收集反馈,但不建议在生产环境中使用。
588 13
Apache Flink 2.0-preview released
|
1月前
|
存储 缓存 算法
分布式锁服务深度解析:以Apache Flink的Checkpointing机制为例
【10月更文挑战第7天】在分布式系统中,多个进程或节点可能需要同时访问和操作共享资源。为了确保数据的一致性和系统的稳定性,我们需要一种机制来协调这些进程或节点的访问,避免并发冲突和竞态条件。分布式锁服务正是为此而生的一种解决方案。它通过在网络环境中实现锁机制,确保同一时间只有一个进程或节点能够访问和操作共享资源。
68 3
|
2月前
|
SQL 消息中间件 关系型数据库
Apache Doris Flink Connector 24.0.0 版本正式发布
该版本新增了对 Flink 1.20 的支持,并支持通过 Arrow Flight SQL 高速读取 Doris 中数据。

推荐镜像

更多