上一章節,講解了如何使用
Spring-WS
構建WebService
服務。其實,建立WebService
的方式有不少的,今天來看看如何使用apache cxf
來構建及調用WebService
服務。html
Apache CXF
是一個開源的Services
框架,CXF
幫助您利用Frontend
編程 API 來構建和開發Services,像JAX-WS
、JAX-RS
。這些Services
能夠支持多種協議,好比:SOAP
、XML/HTTP
、RESTful HTTP
或者CORBA
,而且能夠在多種傳輸協議上運行,好比:HTTP
、JMS
或者JBI
,CXF大大簡化了 Services 的建立,同時它能夠自然地和Spring進行無縫集成。java
如下是官網給出的介紹:https://github.com/apache/cxfgit
最經常使用的是使用cxf
開發web-service
。自己是基於JAX-WS
規範來實現的。固然,自己CXF
也實現了JAX-RS
規範來實現RESTFul Service
。github
JAX-WS
全稱:Java API for XML-Based Web Services
。JAX-WS
是一種編程模型,它經過支持將基於註釋的標準模型用於開發Web Service
應用程序和客戶機來簡化應用程序開發。web
JAX-WS
是Java程序設計語言一個用來建立Web服務的API。spring
Java
語言定義遠程調用所須要實現的接口SEI(service endpoint interface)
,並提供相關的實現,經過調用JAX-WS
的服務發佈接口就能夠將其發佈爲WebService
接口。JAX-WS
的API建立一個代理(用本地對象來替代遠程的服務)來實現對於遠程服務器端的調用。固然JAX-WS
也提供了一組針對底層消息進行操做的API調用,你能夠經過Dispatch
直接使用SOAP消息或XML消息發送請求或者使用Provider處理SOAP或XML消息。
JAX-WS
提供了一系列的註解,能夠對WebService
的接口規範化。如下介紹下最經常使用的幾個註解。apache
@WebService:用於將Java類標記爲實現Web Service
或者將服務端點接口 (SEI) 標記爲實現Web Service
接口。 其包含的屬性有:編程
Web Service
的服務名稱:wsdl:service。缺省值爲 Java 類的簡單名稱 + Service。(字符串)WebService.name+Port
@WebMethod:表示做爲一項Web Service
操做的方法。僅支持在使用@WebService
註解的類上使用@WebMethod
註解。安全
@WebParam:用於定製從單個參數至Web Service
消息部件和XML
元素的映射。springboot
其餘註解,能夠查看:WebService註解總結
爲了有個直觀感覺,你們能夠看看如下這個wsdl文件,對應以上各註解屬性的值(加了前綴oKong
)。
//@WebService 屬性示例 @WebService(targetNamespace = 'http://www.lqdev.cn/webservice' ,name = "oKongName", serviceName="oKongServiceName", portName = "oKongPortName",endpointInterface="cn.lqdev.learning.springboot.cxf.service.AuthorService") //@webMethod @WebParam 經常使用屬性示例 @WebMethod(operationName="oKongOperationName",action="oKongAction") String getAuthorName(@WebParam(name = "paramName") String name);
標記的有點花,⊙﹏⊙‖∣。你們能夠本身對照下。
接下來,咱們以一個簡單的示例來演示下,如何發佈服務及如何進行服務調用。
建立一個工程:spring-boot-cxf-service
.
0.引入CXF的POM文件
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-spring-boot-starter-jaxws</artifactId> <version>3.2.5</version> </dependency>
1.建立實體,按JAX-WS
規範,建立接口及其實現類。 AuthorDto.java
/** * 做者信息實體 * @author oKong * */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class AuthorDto { String name; List<String> hobby; String birthday; String description; Sex sex; }
Sex.java
性別枚舉類
/** * 性別枚舉類 * @author oKong * */ public enum Sex { MALE("male"), FEMALE("female"); String value; Sex(String value) { this.value = value; } public String value() { return value; } public static Sex fromValue(String v) { for (Sex c : Sex.values()) { if (c.value.equals(v)) { return c; } } throw new IllegalArgumentException(v); } }
AuthorService.java
接口類
/** * 建立服務接口 * @author oKong * */ @WebService(targetNamespace = WsConst.NAMESPACE_URI ,name = "authorPortType") public interface AuthorService { /** * 根據名稱獲取做者信息 * @author 做者:oKong */ @WebMethod(operationName="getAuthorByName") AuthorDto getAuthor(@WebParam(name = "authorName") String name); /** * 獲取做者列表信息 * @author oKong */ @WebMethod List<AuthorDto> getAuthorList(); /** * 返回字符串測試 * @author oKong */ String getAuthorString(@WebParam(name = "authorName")String name); }
AuthorServiceImpl.java
接口實現類
@WebService( targetNamespace = WsConst.NAMESPACE_URI, //wsdl命名空間 name = "authorPortType", //portType名稱 客戶端生成代碼時 爲接口名稱 serviceName = "authorService", //服務name名稱 portName = "authorPortName", //port名稱 endpointInterface = "cn.lqdev.learning.springboot.cxf.service.AuthorService")//指定發佈webservcie的接口類,此類也須要接入@WebService註解 public class AuthorServiceImpl implements AuthorService{ @Override public AuthorDto getAuthor(String name) { AuthorDto author = new AuthorDto(); author.setBirthday("1990-01-23"); author.setName("姓名:" + name); author.setSex(Sex.MALE); author.setHobby(Arrays.asList("電影","旅遊")); author.setDescription("描述:一枚趔趄的猿。如今時間:" + new Date().getTime()); return author; } @Override public List<AuthorDto> getAuthorList() { List<AuthorDto> resultList = new ArrayList<>(); AuthorDto author = new AuthorDto(); author.setBirthday("1990-01-23"); author.setName("姓名:oKong"); author.setSex(Sex.MALE); author.setHobby(Arrays.asList("電影","旅遊")); author.setDescription("描述:一枚趔趄的猿。如今時間:" + new Date().getTime()); resultList.add(author); resultList.add(author); return resultList; } @Override public String getAuthorString(String name) { AuthorDto author = getAuthor(name); return author.toString(); } }
注意:相關注解能夠查看章節:經常使用註解介紹
主要是接口實現類的@WebService
對應屬性值都要wsdl文件的映射關係。
@WebService( targetNamespace = WsConst.NAMESPACE_URI, //wsdl命名空間 name = "authorPortType", //portType名稱 客戶端生成代碼時 爲接口名稱 serviceName = "authorService", //服務name名稱 portName = "authorPortName", //port名稱 endpointInterface = "cn.lqdev.learning.springboot.cxf.service.AuthorService")//指定發佈webservcie的接口類,此類也須要接入@WebService註解
2.建立常量類,配置類,設置訪問uri路徑等。
WsConst.java
/** * 常量類 * @author oKong * */ public class WsConst { public static final String NAMESPACE_URI = "http://www.lqdev.cn/webservice"; }
CxfWebServiceConfig.java
/** * cxf配置類 * @author oKong * */ @Configuration public class CxfWebServiceConfig { //這裏須要注意 因爲springmvc 的核心類 爲DispatcherServlet //此處若不重命名此bean的話 本來的mvc就被覆蓋了。可查看配置類:DispatcherServletAutoConfiguration //一種方法是修改方法名稱 或者指定bean名稱 //這裏須要注意 若beanName命名不是 cxfServletRegistration 時,會建立兩個CXFServlet的。 //具體可查看下自動配置類:Declaration org.apache.cxf.spring.boot.autoconfigure.CxfAutoConfiguration //也能夠不設置此bean 直接經過配置項 cxf.path 來修改訪問路徑的 @Bean("cxfServletRegistration") public ServletRegistrationBean dispatcherServlet() { //註冊servlet 攔截/ws 開頭的請求 不設置 默認爲:/services/* return new ServletRegistrationBean(new CXFServlet(), "/ws/*"); } /** * 申明業務處理類 固然也能夠直接 在實現類上標註 @Service * @author oKong */ @Bean public AuthorService authorService() { return new AuthorServiceImpl(); } /* * 非必要項 */ @Bean(name = Bus.DEFAULT_BUS_ID) public SpringBus springBus() { SpringBus springBus = new SpringBus(); return springBus; } /* * 發佈endpoint */ @Bean public Endpoint endpoint(AuthorService authorService) { EndpointImpl endpoint = new EndpointImpl(springBus(), authorService); endpoint.publish("/author");//發佈地址 return endpoint; } }
注意事項:
ServletRegistrationBean
時,須要注意設置方法的名稱或者bean的名稱時,不要和默認的DispatcherServlet
類重名了,會致使原先的mvc
接口沒法使用,由於被覆蓋了。ServletRegistrationBean
來修改,但同時,要注意須要設置bean的名稱爲cxfServletRegistration
,否則會形成註冊多個CXFServlet
的。具體緣由可查看自動配置類:org.apache.cxf.spring.boot.autoconfigure.CxfAutoConfiguration
。因此,修改訪問路徑還能夠經過配置項:cxf.path
來設置。其默認的訪問url爲:/services
3.建立啓動類,同時啓動應用。
/** * cxf服務發佈示例 * @author oKong * */ @SpringBootApplication @Slf4j public class CxfServiceApplication { public static void main(String[] args) throws Exception { SpringApplication.run(CxfServiceApplication.class, args); log.info("spirng-boot-cxf-service-chapter34啓動!"); } }
啓動後,能夠從控制檯看見能夠訪問的url路徑信息。
2018-11-10 22:06:40.898 INFO 46364 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'CXFServlet' to [/ws/*] 2018-11-10 22:06:40.899 INFO 46364 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
訪問:http://127.0.0.1:8080/ws/author?wsdl ,驗證是否發佈成功。
自此,webService
發佈成功了。
建立一個客戶端工程:spring-boot-cxf-client
。
0.引入cxf依賴。
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-spring-boot-starter-jaxws</artifactId> <version>3.2.5</version> </dependency>
1.建立wsdl
文件,同時利用插件:cxf-codegen-plugin
建立相關類。
<!-- cxf-codegen-plugin --> <plugin> <groupId>org.apache.cxf</groupId> <artifactId>cxf-codegen-plugin</artifactId> <version>3.2.5</version> <executions> <execution> <id>generate-sources</id> <phase>generate-sources</phase> <configuration> <sourceRoot>${project.build.directory}/generated/cxf</sourceRoot> <wsdlOptions> <wsdlOption> <wsdl>src/main/resources/wsdl/author.wsdl</wsdl> <wsdlLocation>classpath:wsdl/author.wsdl</wsdlLocation> </wsdlOption> </wsdlOptions> </configuration> <goals> <goal>wsdl2java</goal> </goals> </execution> </executions> </plugin>
將wsdl
文件,放入main/resources/wsdl
目錄下。以後執行:mvn generate-sources
命令,就會自動建立相應的類文件了。拷貝相應的類文件至src/java
目錄下便可。或者直接指定sourceRoot
也是能夠的。
2.建立調用的配置類,這裏演示兩種方式。
WsConst.java
/** * 常量類 * @author oKong * */ public class WsConst { public static final String NAMESPACE_URI = "http://www.lqdev.cn/webservice"; public static final String SERVICE_ADDRESS= "http://127.0.0.1:8080/ws/author?wsdl"; }
CxfClinetConfig.java
/** * 配置類 * * @author oKong * */ @Configuration public class CxfClientConfig { /** * 以接口代理方式進行調用 AuthorPortType接口 */ @Bean("cxfProxy") public AuthorPortType createAuthorPortTypeProxy() { JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean(); jaxWsProxyFactoryBean.setServiceClass(AuthorPortType.class); jaxWsProxyFactoryBean.setAddress(WsConst.SERVICE_ADDRESS);//服務地址:http://127.0.0.1:8080/ws/autho return (AuthorPortType) jaxWsProxyFactoryBean.create(); } /* * 採用動態工廠方式 不須要指定服務接口 */ @Bean public Client createDynamicClient() { JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance(); Client client = dcf.createClient(WsConst.SERVICE_ADDRESS); return client; } }
注意:除了使用JaxWsProxyFactoryBean
和JaxWsDynamicClientFactory
調用外,還能夠直接使用自動生成的AuthorService
類直接調用的,此類繼承至javax.xml.ws.Service
。 如:
/* * 直接調用 */ @Bean("jdkProxy") public AuthorPortType createJdkService() { AuthorService authorService = new AuthorService(); return authorService.getAuthorPortName(); }
其實,最後都是使用AuthorPortType
進行調用的。
3.建立控制層,進行調用示例。
/** * 調用示例 * @author oKong * */ @RestController @RequestMapping("/cxf") public class DemoController { @Autowired Client client; @Autowired @Qualifier("cxfProxy") AuthorPortType authorPort; @GetMapping("/getauthorstring") public String getAuthorString(String authorName) { return authorPort.getAuthorString(authorName); } @GetMapping("/getauthor") public AuthorDto getAuthor(String authorName) { return authorPort.getAuthorByName(authorName); } @GetMapping("/getauthorlist") public List<AuthorDto> getAuthorList() { return authorPort.getAuthorList(); } @GetMapping("/dynamic/{operation}") public Object getAuthorStringByDynamic(@PathVariable("operation")String operationName, String authorName) throws Exception { //這裏就簡單的判斷了 Object[] objects = null; // client.getEndpoint().getBinding().getBindingInfo().getOperations() if ("getAuthorList".equalsIgnoreCase(operationName)) { objects = client.invoke(operationName); } else if ("getAuthorString".equalsIgnoreCase(operationName)) { objects = client.invoke(operationName, authorName); } else if ("getAuthorByName".equalsIgnoreCase(operationName)) { objects = client.invoke(operationName, authorName); } else { throw new RuntimeException("無效的調用方法"); } return objects != null && objects.length > 0 ? objects[0] : "返回異常"; } }
4.編寫啓動類,同時制定應用端口爲:8090。
/** * cxf-客戶端調用示例 * * @author oKong * */ @SpringBootApplication @Slf4j public class CxfClientApplication { public static void main(String[] args) throws Exception { SpringApplication.run(CxfClientApplication.class, args); log.info("spring-boot-cxf-client-chapter34啓動!"); } }
端口號配置:
server.port=8090
5.啓動應用,依次訪問。查看是否調用成功。
http://127.0.0.1:8090/cxf/getauthorstring?authorName=oKong
http://127.0.0.1:8090/cxf//getauthorlist?authorName=oKong
動態工廠方式調用: http://127.0.0.1:8090/cxf/dynamic/getAuthorList?authorName=oKong
其餘的就不一一貼圖了,可自行訪問下。
Cxf
發生異常時,會統一拋出:org.apache.cxf.interceptor.Fault
類的,因此想要捕獲異常,能夠在統一異常裏面進行捕獲,關於統一異常處理,能夠查看文章:第八章:統一異常、數據校驗處理。
CXF的攔截器分爲兩種:InInterceptor
和OutInterceptor
。顯而易見,InInterceptor
能夠處理soap請求消息,OutInterceptor
能夠處理soap響應消息。其攔截器都繼承至AbstractPhaseInterceptor<Message>
接口類,並且,自己也自帶了不少的攔截器,能夠自行添加看看,好比日誌攔截器之類的:LoggingInInterceptor
和LoggingOutInterceptor
。
請求流程圖:
攔截器鏈的階段:
輸入攔截器鏈有以下幾個階段,這些階段按照在攔截器鏈中的前後順序排列。
輸出攔截器鏈有以下幾個階段,這些階段按照在攔截器鏈中的前後順序排列。
具體名稱,可查看:org.apache.cxf.phase.Phase
。
如今,咱們自定義個實現攔截器,實現請求時header須要帶上特定參數,或者你們可不寫一些安全校驗的自定義攔截器,本例只是簡單的示例。
1.檢驗攔截器:CheckAuthInterceptor.java
/** * 簡易-安全校驗攔截器 * * @author oKong * */ @Slf4j public class CheckAuthInterceptor extends AbstractPhaseInterceptor<SoapMessage> { public CheckAuthInterceptor() { super(Phase.PRE_INVOKE);// 攔截節點:調用以前 } @Override public void handleMessage(SoapMessage message) throws Fault { log.info("檢驗攔截器開始檢驗:{}", message); // 處理方法 List<Header> headers = message.getHeaders(); // 判斷是否存header // 檢查headers是否存在 if (headers == null | headers.size() < 1) { throw new Fault(new IllegalArgumentException("驗證失敗,請傳入正確參數(40001)"));//可自定義編碼規範 } //取出header Header header = headers.get(0); //獲取對象 Element element = (Element) header.getObject();//這裏獲取的就時 auth對象了 NodeList tokenNode = element.getElementsByTagName("token"); if(tokenNode == null || tokenNode.getLength() < 1) { //無token節點 throw new Fault(new IllegalArgumentException("驗證失敗,請傳入正確參數(40002)"));//自定義編碼規範 } //獲取token String token = tokenNode.item(0).getTextContent(); log.info("請求的token爲:{}", token); //這裏能夠對token 有效性進行判斷 } }
2.Endpoint中加入攔截器配置。
/* * 發佈endpoint */ @Bean public Endpoint endpoint(AuthorService authorService) { EndpointImpl endpoint = new EndpointImpl(springBus(), authorService); endpoint.publish("/author");//發佈地址 endpoint.getInInterceptors().add(createCheckAuthInterceptor());//加入攔截器 // endpoint.getOutInterceptors().add()//響應攔截器 return endpoint; } @Bean public Interceptor<SoapMessage> createCheckAuthInterceptor(){ return new CheckAuthInterceptor(); }
1.編寫攔截器。
/** * 簡易-安全校驗攔截器 * @author oKong * */ public class AuthInterceptor extends AbstractPhaseInterceptor<SoapMessage>{ public AuthInterceptor() { super(Phase.PREPARE_SEND);//準備請求時進行攔截 } @Override public void handleMessage(SoapMessage message) throws Fault { //處理方法 List<Header> headers = message.getHeaders(); Document doc = DOMUtils.createDocument(); Element element = doc.createElement("auth"); Element tokenEle = doc.createElement("token"); tokenEle.setTextContent(UUID.randomUUID().toString()); element.appendChild(tokenEle); //這裏須要注意 默認狀況下 是使用 org.w3c.dom.Element對象設置對象值的。 //也能夠指定 DataBinding 設置對象的。可繼承抽象類: org.apache.cxf.databinding.AbstractDataBinding //具體源碼可查看:org.apache.cxf.binding.soap.interceptor.SoapOutInterceptor Header tokenHeader = new SoapHeader(new QName(""), element); // tokenHeader.setDataBinding() headers.add(tokenHeader); } }
這裏須要注意:
org.w3c.dom.Element
對象。DataBinding
類來解析(何嘗試,只是看了一眼源碼,裏面有此邏輯,有興趣的同窗能夠自行試試)。2.請求類中加入攔截器。 CxfClientConfig.java
/** * 以接口代理方式進行調用 AuthorPortType接口 */ @Bean("cxfProxy") public AuthorPortType createAuthorPortTypeProxy() { JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean(); jaxWsProxyFactoryBean.setServiceClass(AuthorPortType.class); jaxWsProxyFactoryBean.setAddress(WsConst.SERVICE_ADDRESS);//服務地址:http://127.0.0.1:8080/ws/autho jaxWsProxyFactoryBean.getOutInterceptors().add(createInterceptor());//加入自定義攔截器 return (AuthorPortType) jaxWsProxyFactoryBean.create(); } /* * 採用動態工廠方式 不須要指定服務接口 */ @Bean public Client createDynamicClient() { JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance(); Client client = dcf.createClient(WsConst.SERVICE_ADDRESS); client.getOutInterceptors().add(createInterceptor()); return client; } @Bean public Interceptor<SoapMessage> createInterceptor() { return new AuthInterceptor(); }
從新啓動後,再次請求就能夠看見相關日誌輸出了,能夠試着不設置token,看看有攔截。
異常請求:
本章節主要簡單介紹了
apache-cxf
的使用。這文章示例寫下來,我發現比spring-ws
更簡單呀,也更讓人容易理解、邏輯比較清晰,並且也能設置一些差別化的東西。不知道是否是真的對spring-ws
瞭解的不夠呀,沒有發現spring-ws
的優勢呀。自此,關於WebService
的文章就暫時告一段落了。
目前互聯網上不少大佬都有
SpringBoot
系列教程,若有雷同,請多多包涵了。原創不易,碼字不易,還但願你們多多支持。若文中有所錯誤之處,還望提出,謝謝。
499452441
lqdevOps
我的博客:http://blog.lqdev.cn
完整示例:https://github.com/xie19900123/spring-boot-learning/tree/master/chapter-34
原文地址:http://blog.lqdev.cn/2018/11/12/springboot/chapter-thirty-four/