引入:
上文中我们很详实的演示了如何通过MTOM,让客户端构造一个带附件的SOAP消息,然后服务器端解析处理SOAP消息,并且通过LogHandler来观察请求和返回的SOAP消息的内容。这里我们演示相反过程,就是如何从服务器端下载一个带附件的SOAP消息,然后客户端对这个SOAP消息进行处理,尤其是对附件的处理。
实践:
因为大体上都和上文类似,所以这里我就不做太多的讲解了。
我们设计某个需求,假设公司要面试,大家都把简历投到公司某个简历仓库中,所以我们需要通过web service,根据面试人的名字,从简历仓库获取其面试的职位和他的简历(是一个Microsoft Word文档)
服务端:
首先还是定义VO:
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
|
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,和上文一样:
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
;
}
}
|
下面对LogHandler配置,添加其到HandlerChain中:
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
>
|
然后我们开发SEI,它定义了下载面试人信息的业务方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/**
* 这是一个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,并且提供业务方法的实现:
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
|
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的支持:
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,它提供下载应聘人信息的功能(主要演示从服务器返回带附件的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。
客户端:
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
|
/**
* 客户端测试代码
*/
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中,一切顺利。