Spring boot如何開發CXF 框架的Webservice服務,參考上篇《Springboot開發WebService服務端和客戶端》
做這個webService服務是因為甲方項目是集成平台的,要求我們開發webService服務端接收他們統一推送的信息進行同步數據,現在的情況是,集成平台要求服務端的請求報文和響應報文必須按照他們的格式來。
修改請求報文
這是我的請求報文:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://server.dandelion.com">
<soapenv:Header/>
<soapenv:Body>
<ser:getData>
<!--Optional:-->
<action>同步用戶信息</action>
<!--Optional:-->
<msg>
<![CDATA[
<user>
<id>100123322</id>
<name>蒲公英不是夢</name>
</user>
]]>
</msg>
</ser:getData>
</soapenv:Body>
</soapenv:Envelope>
這是他們要求的請求報文:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body>
<esb:getData xmlns:esb="mdm.standard.com">
<!--Optional:-->
<action>同步用戶信息</action>
<!--Optional:-->
<msg>
<![CDATA[
<user>
<id>100123322</id>
<name>蒲公英不是夢</name>
</user>
]]>
</msg>
</esb:getData>
</soapenv:Body>
</soapenv:Envelope>
區別在於:
- 去掉了根節點的命名空間信息
- 方法名增加了命名空間並修改前綴ser為esb
- 命名空間由“http://server.dandelion.com”修改為“mdm.standard.com”
其中修改命名空間簡單,在webService接口那里修改注解信息即可,問題在於如何把他從根節點取消掉,然后顯示在方法名上,並且修改前綴。
在網上找了一些答案,大部分是客戶端自定義請求報文,或者內容不全,或者webservice服務端是spring框架開發的,通過修改自定義的xml文件來實現。
我嘗試了幾種方法最終都不能修改到請求的模版格式,本着情況急任務緊的事實,我就利用webservice的攔截器在請求未執行之前獲取請求的消息體,暴力修改內容達到目的。
思路是這樣的:
通過SoapUI看到的客戶端報文,仍然是原來的請求報文,但是允許集成平台按照他們的格式修改請求報文。發送請求時,攔截器進行攔截,將請求報文修改為webService服務端原來的請求報文,在執行任務。
CXF框架提供了AbstractPhaseInterceptor抽象類,通過定義phase(階段)來攔截不同階段的請求。
請求攔截器
import org.apache.commons.lang3.StringUtils;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
public class WsInInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
public WsInInterceptor(String phase) {
super(phase);
}
@Override
public void handleMessage(SoapMessage message) throws Fault {
try {
// 從流中獲取請求消息體並以字符串形式輸出,注意IOUtils是cxf的包;
String input = IOUtils.toString(message.getContent(InputStream.class), "UTF-8");
// 如果內容不為空(第一次連接也會被攔截,此時input為空)
if (StringUtils.isNotBlank(input)){
// 修改請求消息體為webservice服務要求的格式
input = input.replace("<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">","<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:ser=\"http://server.dandelion.com\">")
.replace("<esb:getData xmlns:esb=\"mdm.stardand.com\">","<ser:getData>")
.replace("</esb:getData>", "</ser:getData>");
}
// 重新寫入
message.setContent(InputStream.class, new ByteArrayInputStream(input.getBytes()));
} catch (Exception e) {
System.out.println(String.format("解析報文異常: %s", e.getMessage()));
}
}
}
創建WsInInterceptor類繼承AbstractPhaseInterceptor抽象類,需要創建構造方法傳入phase參數並重寫handleMessage方法。
首先通過message.getContent(InputStream.class)從流中獲取請求報文。
我這邊構造參數傳入的是Phase.RECEIVE,所以通過SoapUI連接webService服務端時也會攔截請求,只不過請求報文為空字符串,所以需要進行判空。
將根節點的命名空間去掉
將方法名的命名空間去掉並修改前綴為原來的
然后重新寫入
注入攔截器
在發布接口的配置文件中注入攔截器,並設置phase為receive階段。
通過SoapUI測試效果:
打印效果:
修改響應報文
這是我的響應報文:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:getDataResponse xmlns:ns2="http://server.dandelion.com">
<ns2:return>
<![CDATA[
<root>
<code>1</code>
<msg>同步成功</msg>
</root>
]]>
</ns2:return>
</ns2:getDataResponse>
</soap:Body>
</soap:Envelope>
這是他們要求的響應報文:
<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope" xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soapenv:Body>
<return>
<![CDATA[
<root>
<code>1</code>
<msg>同步成功</msg>
</root>
]]>
</return>
</soapenv:Body>
</soapenv:Envelope>
同樣是利用webservice的攔截器在結果未返回之前進行攔截,暴力修改響應報文達到目的。
響應攔截器
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class WsOutInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
public WsOutInterceptor(String phase) {
super(phase);
}
@Override
public void handleMessage(SoapMessage message) throws Fault {
try {
// 從流中獲取返回內容
OutputStream os = message.getContent(OutputStream.class);
CachedStream cs = new CachedStream();
message.setContent(OutputStream.class, cs);
message.getInterceptorChain().doIntercept(message);
CachedOutputStream cachedOutputStream = (CachedOutputStream) message.getContent(OutputStream.class);
InputStream in = cachedOutputStream.getInputStream();
String output = IOUtils.toString(in, "UTF-8");
// 修改內容為集成平台要求的格式
output = output.replace("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">","<soapenv:Envelope xmlns:soapenv=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\">")
.replace("</soap:Envelope>", "</soapenv:Envelope>")
.replace("<soap:Body>", "<soapenv:Body>")
.replace("</soap:Body>", "</soapenv:Body>")
.replace("<ns2:getDataResponse xmlns:ns2=\"http://server.dandelion.com\">", "")
.replace("</ns2:getDataResponse>", "");
// 處理完后寫回流中
IOUtils.copy(new ByteArrayInputStream(output.getBytes()), os);
cs.close();
os.flush();
message.setContent(OutputStream.class, os);
} catch (Exception e) {
System.out.println(String.format("解析報文異常: %s", e.getMessage()));
}
}
private static class CachedStream extends CachedOutputStream {
public CachedStream() {
super();
}
@Override
protected void doFlush() throws IOException {
currentStream.flush();
}
@Override
protected void doClose() throws IOException {
}
@Override
protected void onWrite() throws IOException {
}
}
}
注入攔截器
在發布接口的配置文件中注入攔截器,並設置phase為pre_stream階段。
通過SoapUI測試效果:
如果是按照響應結果中轉義符轉義失敗,可能是接口的注解問題,化繁為簡即可:
webservice接口:
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
@WebService(targetNamespace = "http://server.dandelion.com")
public interface TestService {
@WebMethod(operationName="getData")
String execute(@WebParam(name = "action") String action, @WebParam(name = "msg") String msg);
}
wbservice接口實現:
import javax.jws.WebService;
@WebService(targetNamespace = "http://server.dandelion.com",
endpointInterface = "com.dandelion.server.TestService")
public class TestServiceImpl implements TestService{
@Override
public String execute(String action, String msg) {
System.out.println(String.format("action: %s", action));
System.out.println(String.format("msg: %s", msg));
return "<root><code>1</code><msg>同步成功</msg></root>";
}
}
如果你有更好的解決方案,希望不吝賜教!!!
【參考博客】
CXF支持的Phase(階段/工作時機):spring boot-實現WebService(CXF實現)的攔截器(Interceptor)
攔截去修改請求報文:CXF服務端改寫請求報文