SpringBoot學習筆記之CXF整合(實現使用者驗證)
阿新 • • 發佈:2019-01-27
Springboot整合CXF
說起web service最近幾年restful大行其道,大有取代傳統soap web service的趨勢,但是一些特有或相對老舊的系統依然使用了傳統的soap web service,例如銀行、航空公司的機票查詢介面等。目前就遇到了這種情況,需要在系統中查詢第三方提供的soap web service介面,也就是說要將它整合進現有的系統當中。Spring整合CXF本來十分簡單,但是因為使用了Spring
boot,不想用以前xml一堆配置的方式,那麼能否按照Spring boot的風格優雅的進行整合呢?答案當然是肯定的,但是遍查網上幾乎沒有這方面的資料,折騰過後覺得還是有必要記錄一下,雖然它顯得非常的簡單。
1)、引入cxf 依賴包
<!-- CXF webservice --><dependency><groupId>org.apache.cxf</groupId><artifactId>cxf-spring-boot-starter-jaxws</artifactId><version>3.1.7</version></dependency><!-- CXF webservice -->
2、開發webservice介面類
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
@WebService(name="ITestWebService",//暴露服務名稱
targetNamespace="http://webservice.liyj.vk.com")//名稱空間,一般是介面的包名倒序
public interface ITestWebService {
@WebMethod
@WebResult(name="String",targetNamespace="")
public String sayHello(@WebParam String name);
}
3、開發介面實現類
import javax.jws.WebService;
import org.springframework.stereotype.Component;
@WebService(serviceName="ITestWebService",
targetNamespace="http://webservice.liyj.vk.com",
endpointInterface="com.vk.liyj.webservice.ITestWebService")
@Component
public class TestWebSericeImpl implements ITestWebService {
@Override
public String sayHello(String name) {
return "hello "+name;
}
}
4、定義攔截器用於使用者驗證
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.saaj.SAAJInInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.NodeList;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
public class AuthInterceptor extends AbstractPhaseInterceptor<SoapMessage>{
private static final Logger logger = LoggerFactory.getLogger(AuthInterceptor.class);
private SAAJInInterceptor saa = new SAAJInInterceptor();
private static final String USER_NAME = "admin";
private static final String USER_PASSWORD = "pass";
public AuthInterceptor() {
super(Phase.PRE_PROTOCOL);
getAfter().add(SAAJInInterceptor.class.getName());
}
@Override
public void handleMessage(SoapMessage message) throws Fault {
SOAPMessage mess = message.getContent(SOAPMessage.class);
if (mess == null) {
saa.handleMessage(message);
mess = message.getContent(SOAPMessage.class);
}
SOAPHeader head = null;
try {
head = mess.getSOAPHeader();
} catch (Exception e) {
logger.error("getSOAPHeader error: {}",e.getMessage(),e);
}
if (head == null) {
throw new Fault(new IllegalArgumentException("找不到Header,無法驗證使用者資訊"));
}
NodeList users = head.getElementsByTagName("username");
NodeList passwords = head.getElementsByTagName("password");
if (users.getLength() < 1) {
throw new Fault(new IllegalArgumentException("找不到使用者資訊"));
}
if (passwords.getLength() < 1) {
throw new Fault(new IllegalArgumentException("找不到密碼資訊"));
}
String userName = users.item(0).getTextContent().trim();
String password = passwords.item(0).getTextContent().trim();
if(USER_NAME.equals(userName) && USER_PASSWORD.equals(password)){
logger.debug("admin auth success");
} else {
SOAPException soapExc = new SOAPException("認證錯誤");
logger.debug("admin auth failed");
throw new Fault(soapExc);
}
}
}
Interceptor是CXF架構中一個很有特色的模式。你可以在不對核心模組進行修改的情況下,動態新增很多功能。這對於CXF這個以處理訊息為中心的服務框架來說是非常有用的,CXF通過在Interceptor中對訊息進行特殊處理,實現了很多重要功能模組。這裡就是採用攔截器進行使用者驗證。
5、客戶端登入攔截器 ClientLoginInterceptor
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.helpers.DOMUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.xml.namespace.QName;
import java.util.List;
public class ClientLoginInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
private String username;
private String password;
public ClientLoginInterceptor(String username, String password) {
super(Phase.PREPARE_SEND);
this.username = username;
this.password = password;
}
@Override
public void handleMessage(SoapMessage soap) throws Fault {
List<Header> headers = soap.getHeaders();
Document doc = DOMUtils.createDocument();
Element auth = doc.createElement("authrity");
Element username = doc.createElement("username");
Element password = doc.createElement("password");
username.setTextContent(this.username);
password.setTextContent(this.password);
auth.appendChild(username);
auth.appendChild(password);
headers.add(0, new Header(new QName("tiamaes"),auth));
}
}
6、開發cxf配置類釋出服務
import javax.xml.ws.Endpoint;
import org.apache.cxf.Bus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.transport.servlet.CXFServlet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CxfConfig {
@Autowired
private Bus bus;
@Autowired
private ITestWebService service;
//必須要有
@Bean
public ServletRegistrationBean cxfServlet(){
return new ServletRegistrationBean(new CXFServlet(),"/services/*");
}
@Bean
public Endpoint endpoint(){
EndpointImpl endpoint=new EndpointImpl(bus,service);
endpoint.publish("/testWebService");
//列印報文日誌攔截器
endpoint.getInInterceptors().add(new LoggingInInterceptor());
endpoint.getInInterceptors().add(new LoggingOutInterceptor());
//通過攔截器校驗使用者名稱與密碼
endpoint.getInInterceptors().add(new AuthInterceptor());
return endpoint;
}
}
說明:
i、上類中 cxfServlet()方法相當於傳統web.xml中的下列程式碼
<servlet>
<servlet-name>CXFServlet</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/server/*</url-pattern>
</servlet-mapping>
ii、上類中的endpoint()方法相當於傳統xml配置檔案中的下列程式碼,LoggingInInterceptor,LoggingOutInterceptor用於列印webservice呼叫日誌。
<jaxws:endpoint id="meetingService"
implementor="#meetingWsEndpoint" address="/meetingService" >
<jaxws:inInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor" />
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
</jaxws:outInterceptors>
</jaxws:endpoint>
通過對比我們可以看到Spring boot和cxf的整合比以前xml的方式更加的簡潔。
webservice服務已經發布了,那麼我們怎麼呼叫已經發布的服務呢?有兩種比較通過的呼叫方法,非使用wsdl文件生成java類。本人喜歡傳入方法名呼叫的方式,顯得清爽而簡潔。兩種呼叫服務的程式碼如下:
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
public class TestWebservice {
public static void main(String[] args) throws Exception {
cl2();
}
/**
* 方式1.代理類工廠的方式,需要拿到對方的介面
*/
public static void cl1() {
try {
// 介面地址
String address = "http://localhost:8080/services/CommonService?wsdl";
// 代理工廠
JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
// 設定代理地址
jaxWsProxyFactoryBean.setAddress(address);
// 設定介面型別
jaxWsProxyFactoryBean.setServiceClass(ITestWebService.class);
// 建立一個代理介面實現
ITestWebService cs = (ITestWebService) jaxWsProxyFactoryBean.create();
// 資料準備
String userName = "liyj";
// 呼叫代理介面的方法呼叫並返回結果
String result = cs.sayHello(userName);
System.out.println("返回結果:" + result);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 動態呼叫方式
*/
public static void cl2() {
// 建立動態客戶端
JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
Client client = dcf.createClient("http://localhost:8080/web/services/testWebService?wsdl");
// 需要密碼的情況需要加上使用者名稱和密碼
// client.getOutInterceptors().add(new ClientLoginInterceptor(USER_NAME,
// PASS_WORD));
Object[] objects = new Object[0];
try {
// invoke("方法名",引數1,引數2,引數3....);
objects = client.invoke("sayHello", "liyj");
System.out.println("返回資料:" + objects[0]);
} catch (java.lang.Exception e) {
e.printStackTrace();
}
}
}
使用動態呼叫方式要注意的就是,如果呼叫的服務介面返回的是一個自定義物件,那麼結果Object[]中的資料型別就成了這個自定義物件(元件幫你自動生成了這個物件),但是你本地可能並沒有這個類,所以需要自行轉換處理,最簡單的是新建一個跟返回結果一模一樣的類進行強轉,當然更好的方式是封裝一個通用的。