最近在做接口時,需求是提供restful api和webService兩種方式共享數據。對於webService方式,大多數同學還是很陌生的,我在做的過程中也踩了一些坑,現記錄下來,分享給大家。
pom.xml
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-spring-boot-starter-jaxws</artifactId> <version>3.2.9</version> </dependency>
springboot是2.1.1版本
項目結構:服務端和客戶端
服務端代碼:
import org.apache.cxf.Bus; import org.apache.cxf.bus.spring.SpringBus; 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; import com.qm.boot.web.webService.server.Interceptor.AuthInterceptor; import com.qm.boot.web.webService.server.service.DataCenterService; import javax.xml.ws.Endpoint; @Configuration public class CxfConfig { @Autowired private DataCenterService DataCenterServiceImpl; /** * 為了不被springSecurity攔截,自定義暴露的路徑 * 默認為/services/** */ @Bean public ServletRegistrationBean<CXFServlet> myServlet(){ ServletRegistrationBean<CXFServlet> registrationBean =new ServletRegistrationBean<CXFServlet>(new CXFServlet(),"/webService/*"); return registrationBean; } @Bean(name = Bus.DEFAULT_BUS_ID) public SpringBus springBus() { return new SpringBus(); } //終端路徑 @Bean public Endpoint endpoint() { EndpointImpl endpoint = new EndpointImpl(springBus(), DataCenterServiceImpl); endpoint.getInInterceptors().add(new AuthInterceptor());//添加校驗攔截器 endpoint.publish("/dataShare"); return endpoint; } }
1 import org.apache.cxf.binding.soap.SoapHeader; 2 import org.apache.cxf.binding.soap.SoapMessage; 3 import org.apache.cxf.common.util.StringUtils; 4 import org.apache.cxf.headers.Header; 5 import org.apache.cxf.interceptor.Fault; 6 import org.apache.cxf.phase.AbstractPhaseInterceptor; 7 import org.apache.cxf.phase.Phase; 8 import org.slf4j.Logger; 9 import org.slf4j.LoggerFactory; 10 import org.w3c.dom.Element; 11 import org.w3c.dom.NodeList; 12 13 import javax.xml.soap.SOAPException; 14 import java.util.List; 15 /** 16 * 自定義攔截器 17 */ 18 public class AuthInterceptor extends AbstractPhaseInterceptor<SoapMessage> { 19 Logger logger = LoggerFactory.getLogger(this.getClass()); 20 private static final String USERNAME="root"; 21 private static final String PASSWORD="admin"; 22 23 public AuthInterceptor() { 24 //定義在哪個階段進行攔截 25 super(Phase.PRE_PROTOCOL); 26 } 27 28 @Override 29 public void handleMessage(SoapMessage soapMessage) throws Fault { 30 List<Header> headers = null; 31 String username=null; 32 String password=null; 33 try { 34 headers = soapMessage.getHeaders(); 35 } catch (Exception e) { 36 logger.error("getSOAPHeader error: {}",e.getMessage(),e); 37 } 38 39 if (headers == null) { 40 throw new Fault(new IllegalArgumentException("找不到Header,無法驗證用戶信息")); 41 } 42 //獲取用戶名,密碼 43 for (Header header : headers) { 44 SoapHeader soapHeader = (SoapHeader) header; 45 Element e = (Element) soapHeader.getObject(); 46 NodeList usernameNode = e.getElementsByTagName("username"); 47 NodeList pwdNode = e.getElementsByTagName("password"); 48 username=usernameNode.item(0).getTextContent(); 49 password=pwdNode.item(0).getTextContent(); 50 if( StringUtils.isEmpty(username)||StringUtils.isEmpty(password)){ 51 throw new Fault(new IllegalArgumentException("用戶信息為空")); 52 } 53 } 54 //校驗用戶名密碼 55 if(!(username.equals(USERNAME) && password.equals(PASSWORD))){ 56 SOAPException soapExc = new SOAPException("用戶認證信息錯誤"); 57 logger.debug("用戶認證信息錯誤"); 58 throw new Fault(soapExc); 59 } 60 61 } 62 63 }
1 import java.io.UnsupportedEncodingException; 2 3 import javax.jws.WebMethod; 4 import javax.jws.WebParam; 5 import javax.jws.WebService; 6 7 @WebService 8 public interface DataCenterService { 9 @WebMethod 10 public String dataShare(@WebParam(name = "code")String code,@WebParam(name = "type")String type,@WebParam(name = "count")Integer count, @WebParam(name = "currentPage")Integer currentPage,@WebParam(name = "startTime")String startTime,@WebParam(name = "userName")String userName, @WebParam(name = "sign")String sign)throws UnsupportedEncodingException; 11 }
import java.io.UnsupportedEncodingException; import javax.jws.WebService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.alibaba.fastjson.JSON; import com.qm.boot.web.api.component.DataShareComponent; import com.qm.boot.web.webService.server.service.DataCenterService; /** * targetNamespace:按照endpointInterface反寫,很關鍵 */ @Service @WebService(name = "dataShare" ,targetNamespace ="http://service.server.webService.web.boot.qm.com/" ,endpointInterface = "com.qm.boot.web.webService.server.service.DataCenterService") public class DataCenterServiceImpl implements DataCenterService { @Autowired private DataShareComponent dataShareComponent; @Override public String dataShare(String code, String type, Integer count, Integer currentPage, String startTime, String userName, String sign) throws UnsupportedEncodingException { return JSON.toJSONString(dataShareComponent.dataShare(code, type, count, currentPage, startTime, userName, sign)); } }
客戶端代碼:
1 import org.apache.cxf.binding.soap.SoapMessage; 2 import org.apache.cxf.headers.Header; 3 import org.apache.cxf.helpers.DOMUtils; 4 import org.apache.cxf.interceptor.Fault; 5 import org.apache.cxf.phase.AbstractPhaseInterceptor; 6 import org.apache.cxf.phase.Phase; 7 import org.w3c.dom.Document; 8 import org.w3c.dom.Element; 9 10 import javax.xml.namespace.QName; 11 import java.util.List; 12 /** 13 * 自定義攔截器驗證用戶名密碼 14 */ 15 public class LoginInterceptor extends AbstractPhaseInterceptor<SoapMessage> { 16 private String username="root"; 17 private String password="admin"; 18 public LoginInterceptor(String username, String password) { 19 //設置在發送請求前階段進行攔截 20 super(Phase.PREPARE_SEND); 21 this.username=username; 22 this.password=password; 23 } 24 25 @Override 26 public void handleMessage(SoapMessage soapMessage) throws Fault { 27 List<Header> headers = soapMessage.getHeaders(); 28 Document doc = DOMUtils.createDocument(); 29 Element auth = doc.createElementNS("http://service.server.webService.web.boot.qm.com/","SecurityHeader"); 30 Element UserName = doc.createElement("username"); 31 Element UserPass = doc.createElement("password"); 32 33 UserName.setTextContent(username); 34 UserPass.setTextContent(password); 35 36 auth.appendChild(UserName); 37 auth.appendChild(UserPass); 38 39 headers.add(0, new Header(new QName("SecurityHeader"),auth)); 40 } 41 }
1 import com.alibaba.fastjson.JSON; 2 import com.alibaba.fastjson.JSONObject; 3 import com.qm.boot.web.webService.client.interceptor.LoginInterceptor; 4 5 import org.apache.cxf.endpoint.Client; 6 import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory; 7 //客戶端測試demo 8 public class CxfClient { 9 //webService接口地址 10 private static String address = "http://localhost:8080/services/test?wsdl"; 11 12 13 public static void main(String[] args) { 14 run(); 15 } 16 17 /** 18 * 動態調用方式 19 */ 20 public static void run() { 21 22 JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance(); // 創建動態客戶端 23 Client client = dcf.createClient(address); 24 25 client.getOutInterceptors().add(new LoginInterceptor("root","admin"));// 需要密碼的情況需要加上用戶名和密碼 26 Object[] objects = new Object[0]; 27 try { 28 // invoke("方法名",參數1,參數2,參數3....); 29 objects = client.invoke("dataShare", "1","1",100,1,"","1","lXIh6PzSVPIyG6qMD"); 30 System.out.println("返回數據:" + JSONObject.parse(JSON.toJSONString(objects[0]))); 31 } catch (Exception e) { 32 System.out.println(e.getMessage()); 33 //e.printStackTrace(); 34 } 35 } 36 }
如果項目集成了springSecurity,切記一定要排除對webService地址的攔截,不然客戶端會請求失敗
啟動項目后,訪問webService地址,可以看到發布成功如下:
客戶端調用成功: