1. 程式人生 > 程式設計 >Springboot 基於CXF構建WebService服務

Springboot 基於CXF構建WebService服務

前言

最近因為系統需要接入了一個新的支付通道,一般來說都是使用RestApi 來接入,但是本次接入的支付通道為境外支付,使用的WebService,對於WS我們在實際業務中基本上不會用到,所以查閱了一些資料,記錄一下自己專案中使用的WS。

WebService概述

  • 什麼是WebService
    Web Service技術,能使得執行在不同機器上的不同應用無須藉助附加的、專門的第三方軟體或硬體, 就可相互交換資料或整合。依據Web Service規範實施的應用之間,無論它們所使用的語言、平臺或內部協議是什麼,都可以相互交換資料。
    簡單來說WebService就是一種跨程式語言和跨作業系統平臺的遠端呼叫技術。
  • WebServcie技術支援
    主要是瞭解XML和XSDSOAPWSDLUDDI,詳細內容請參閱百度百科:Web Service

Apache-CXF概述

  • 什麼是Apache-CXF
    Apache CXF是一個開源的Services框架,CXF幫助您利用Frontend程式設計 API 來構建和開發Services,像JAX-WS、JAX-RS。這些Services可以支援多種協議,比如:SOAP、XML/HTTP、RESTful HTTP或者CORBA,並且可以在多種傳輸協議上執行,比如:HTTP、JMS 或者JBI,
    CXF大大簡化了 Services 的建立,同時它可以天然地和Spring進行無縫整合。

    詳細內容請參閱官方:Apache CXF
  • JAX-WS規範
    JAX-WS全稱:Java API for XML-Based Web Services。JAX-WS是一種程式設計模型,它通過支援將基於註釋的標準模型用於開發Web Service應用程式和客戶機來簡化應用程式開發。
    JAX-WS是Java程式設計語言一個用來建立Web服務的API。
    • 在伺服器端,使用者只需要通過Java語言定義遠端呼叫所需要實現的介面SEI(service endpoint interface),並提供相關的實現,通過呼叫JAX-WS的服務釋出介面就可以將其釋出為WebService介面。
    • 在客戶端,使用者可以通過JAX-WS的API建立一個代理(用本地物件來替代遠端的服務)來實現對於遠端伺服器端的呼叫。當然JAX-WS也提供了一組針對底層訊息進行操作的API呼叫,你可以通過Dispatch直接使用SOAP訊息或XML訊息傳送請求或者使用Provider處理SOAP或XML訊息。
  • 常用註解參考WebService註解總結

SpringBoot整合CXF例項

接下來,我們以一個簡單的示例來演示下,如何釋出服務及如何進行服務呼叫。

服務端構建

  1. 建立工程spring-boot-cxf-service
  2. 引入maven依賴
    <dependencies>
        <!--spring-boot-starter-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--lombok懶人依賴 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--test依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--cxf依賴 -->
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-spring-boot-starter-jaxws</artifactId>
            <version>3.2.5</version>
        </dependency>
        <!--commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    複製程式碼
  3. 建立所需的實體
    1.NBAPlayer.java
    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    public class NBAPlayer {
       String name;
       String team;
       List<String> teams;
       String description;
       Position position;
       @Override
       public String toString() {
           return "NBA現役球員" + name + ",司職" + position.value + "現效力於" + team + ",曾效力球隊" + teams.toString();
       }
    }
    複製程式碼
    2.Positon.java
    public enum Position {
       FORWARD("小前鋒"),SHOOTING("控球後衛");
       String value;
       Position(String value) {
           this.value = value;
       }
       public String value() {
           return value;
       }
       public static Position fromValue(String v) {
           for (Position c : Position.values()) {
               if (c.value.equals(v)) {
                   return c;
               }
           }
           throw new IllegalArgumentException(v);
       }
    }
    複製程式碼
    3.NBAPlayerSoapService.java
    @WebService(targetNamespace = WsConst.NAMESPACE_URI,name = "NBAPlayerSoap")
    public interface NBAPlayerSoapService {
        /**
         * description  根據姓名獲取球員資訊
         * date         2019/10/15
         *
         * @param NBAPlayerName
         * @return cn.forward.springboot.cxf.service.entity.NBAPlayer
         */
        @WebMethod(operationName = "getNBAPlayerByName")
        NBAPlayer getNBAPlayerByName(@WebParam(name = "NBAPlayerName") String NBAPlayerName);
    
        /**
         * description  獲取球員列表
         * date         2019/10/15
         *
         * @return java.util.List<cn.forward.springboot.cxf.service.entity.NBAPlayer>
         */
        @WebMethod
        List<NBAPlayer> getNBAPlayerList();
    
    }
    複製程式碼
    4.NBAPlayerSoapImpl.java
    @WebService(
        targetNamespace = WsConst.NAMESPACE_URI,//wsdl名稱空間
        name = "NBAPlayerSoap",//portType名稱 客戶端生成程式碼時 為介面名稱
        serviceName = "NBAPlayerSoapService",//服務name名稱
        portName = "NBAPlayerPortName",//port名稱
        endpointInterface = "cn.forward.springboot.cxf.service.service.NBAPlayerSoapService")
    public class NBAPlayerSoapImpl implements NBAPlayerSoapService {
    
        @Override
        public NBAPlayer getNBAPlayerByName(String name) {
            List<NBAPlayer> nbaPlayers = getNBAPlayerList();
            AtomicReference<NBAPlayer> player = new AtomicReference<>(nbaPlayers.get(new Random().nextInt(2)));
            nbaPlayers.forEach(nbaPlayer -> {
                if (nbaPlayer.getName().equals(name)) {
                    player.set(nbaPlayer);
                }
            });
            return player.get();
        }
        @Override
        public List<NBAPlayer> getNBAPlayerList() {
            return generatorList();
        }
        public List<NBAPlayer> generatorList() {
            List<NBAPlayer> resultList = new ArrayList<>();
            NBAPlayer LBJ = new NBAPlayer();
            LBJ.setName("勒布朗·詹姆斯");
            LBJ.setTeam("洛杉磯湖人");
            LBJ.setTeams(Arrays.asList("克里夫蘭騎士(2003−2010)","邁阿密熱火(2010−2014)"));
            LBJ.setPosition(Position.FORWARD);
            LBJ.setDescription(LBJ.toString());
            resultList.add(LBJ);
            NBAPlayer WS = new NBAPlayer();
            WS.setName("羅素·威斯布魯克");
            WS.setTeam("休斯頓火箭");
            WS.setTeams(Arrays.asList("奧克拉荷馬雷霆 (2008-2019)"));
            WS.setPosition(Position.SHOOTING);
            WS.setDescription(WS.toString());
            resultList.add(WS);
            return resultList;
        }
    }
    複製程式碼
    5.WsConst.java
    public class WsConst {
        public static final String NAMESPACE_URI = "http://www.lqdev.cn/webservice";
    }
    複製程式碼
    6.CxfWebServiceConfig.java
    @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 攔截/NBA 開頭的請求 不設定 預設為:/services/*
            return new ServletRegistrationBean(new CXFServlet(),"/NBA/*");
        }
        /**
         * 申明業務處理類 當然也可以直接 在實現類上標註 @Service
         * @author ShiFan
         */
        @Bean
        public NBAPlayerSoapService authorService() {
            return new NBAPlayerSoapImpl();
        }
        /**
         * 非必要項
         */
        @Bean(name = Bus.DEFAULT_BUS_ID)
        public SpringBus springBus() {
            SpringBus springBus = new SpringBus();
            return springBus;
        }
        /**
         * 釋出endpoint
         */
        @Bean
        public Endpoint endpoint(NBAPlayerSoapService NBAPlayerSoap) {
            EndpointImpl endpoint = new EndpointImpl(springBus(),NBAPlayerSoap);
            //釋出地址
            endpoint.publish("/player");
            return endpoint;
        }
    }
    複製程式碼
    注意事項: 配置ServletRegistrationBean時,需要注意設定方法的名稱或者bean的名稱時,不要和預設的DispatcherServlet類重名了,會導致原先的mvc介面無法使用,因為被覆蓋了。 修改訪問的路徑可以通過設定ServletRegistrationBean來修改,但同時,要注意需要設定bean的名稱為cxfServletRegistration,不然會造成註冊多個CXFServlet的。具體原因可檢視自動配置類:org.apache.cxf.spring.boot.autoconfigure.CxfAutoConfiguration。
    所以,修改訪問路徑還可以通過配置項:cxf.path來設定。其預設的訪問url為:/services
  4. 建立啟動類,同時啟動應用
@SpringBootApplication
@Slf4j
public class SpringBootCxfServiceApplication {
	public static void main(String[] args) {
		SpringApplication.run(SpringBootCxfServiceApplication.class,args);
		log.info("spring-boot-cxf-service啟動!");
	}
}
複製程式碼

檢視輸入日誌

2019-10-15 15:49:17.964  INFO 16100 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-10-15 15:49:17.967  INFO 16100 --- [           main] .f.s.c.s.SpringBootCxfServiceApplication : Started SpringBootCxfServiceApplication in 3.35 seconds (JVM running for 4.454)
2019-10-15 15:49:17.969  INFO 16100 --- [           main] .f.s.c.s.SpringBootCxfServiceApplication : spring-boot-cxf-service啟動!
複製程式碼
  1. 測試
    訪問http://localhost:8080/NBA/player?wsdl 驗證:
    Snipaste_2019-10-15_16-16-26.png

客戶端構建

  1. 建立工程spring-boot-cxf-client
  2. 引入maven依賴
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-spring-boot-starter-jaxws</artifactId>
            <version>3.2.5</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.5.17</version>
        </dependency>
    </dependencies>
    複製程式碼
  3. 建立wsdl檔案,同時利用外掛:cxf-codegen-plugin建立相關類。
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </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.basedir}/src/main/java</sourceRoot>
                            <wsdlOptions>
                                <wsdlOption>
                                    <wsdl>src/main/resources/wsdl/player.wsdl</wsdl>
                                    <wsdlLocation>classpath:wsdl/player.wsdl</wsdlLocation>
                                </wsdlOption>
                            </wsdlOptions>
                        </configuration>
                        <goals>
                            <goal>wsdl2java</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    複製程式碼
    提示:將wsdl檔案,放入main/resources/wsdl目錄下。之後執行:mvn generate-sources命令,就會自動建立相應的類檔案了。拷貝相應的類檔案至src/java目錄下即可。或者直接指定sourceRoot也是可以的
  4. 建立配置類、訪問類等
  • WsCont.java
    public class WsConst {
        public static final String SERVICE_ADDRESS= "http://localhost:8080/NBA/player?wsdl";
    }
    複製程式碼
  • CxfClientConfig.java
    @Configuration
    public class CxfClientConfig {
        /**
         * 採用代理方式
         *
         * @return NBAPlayerSoap
         */
        @Bean
        public NBAPlayerSoap createAuthorPortTypeProxy() {
            JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
            jaxWsProxyFactoryBean.setServiceClass(NBAPlayerSoap.class);
            jaxWsProxyFactoryBean.setAddress(WsConst.SERVICE_ADDRESS);
            return (NBAPlayerSoap) jaxWsProxyFactoryBean.create();
        }
    
        /**
         * 採用動態工廠方式 不需要指定服務介面
         */
        @Bean
        public Client createDynamicClient() {
            JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
            Client client = dcf.createClient(WsConst.SERVICE_ADDRESS);
            return client;
        }
    }
    複製程式碼
  • NBAPlayerController.java
    @RestController
    @RequestMapping("/nba/player")
    public class NBAPlayerController {
    
        @Resource
        NBAPlayerSoap nbaPlayerSoap;
    
        /**
         * description  根據球員名稱獲取球員資訊
         * date         2019/10/15
         *
         * @param nbaPlayerName
         * @return cn.lqdev.webservice.NbaPlayer
         */
        @GetMapping("/getNBAPlayerByName")
        public NbaPlayer getNBAPlayerByName(@RequestParam("nbaPlayerName") String nbaPlayerName) {
            return nbaPlayerSoap.getNBAPlayerByName(nbaPlayerName);
        }
    
        /**
         * description  獲取全部球員資訊
         * date         2019/10/15
         * @return java.util.List<cn.lqdev.webservice.NbaPlayer>
         */
        @GetMapping("/getNBAPlayerList")
        public List<NbaPlayer> getNBAPlayerList() {
            return nbaPlayerSoap.getNBAPlayerList();
        }
    }
    複製程式碼
  1. 建立啟動類,測試
    @SpringBootApplication
    @Slf4j
    public class SpringBootCxfClientApplication {
        public static void main(String[] args) {
            SpringApplication.run(SpringBootCxfClientApplication.class,args);
            log.info("spring-boot-cxf-client啟動!");
        }
    }
    複製程式碼
    訪問: http://localhost:8081/nba/player/getNBAPlayerByName?nbaPlayerName=勒布朗·詹姆斯
    驗證結果:
    {
        description: "NBA現役球員勒布朗·詹姆斯,司職小前鋒現效力於洛杉磯湖人,曾效力球隊[克里夫蘭騎士(2003−2010),邁阿密熱火(2010−2014)]",name: "勒布朗·詹姆斯",position: "FORWARD",team: "洛杉磯湖人",teams: [
        "克里夫蘭騎士(2003−2010)","邁阿密熱火(2010−2014)"
        ]
    }
    複製程式碼

6.參考文章

總結

本次文章主要是分享了springboot基於CXF構建WebService服務的過程。