Apache CXF 与 WSDL 优先



您开发的 CXF-POJO 应用程序导致客户端和服务器之间存在非常紧密的耦合。直接访问服务接口也可能带来严重的安全威胁。因此,通常需要在客户端和服务器之间进行解耦,这可以通过使用 WSDL(Web 服务描述语言)来实现。

我们使用基于 XML 的 WSDL 文档编写 Web 服务接口。我们将使用一个工具将此 WSDL 映射到 Apache CXF 接口,然后由我们的客户端和服务器应用程序实现和使用这些接口。为了提供解耦,从 WSDL 开始是一种首选方式。为此,您需要首先学习一门新语言 - WSDL。编写 WSDL 需要谨慎的方法,如果您在开始使用它之前能够对其有所了解,将会更好。

在本课中,我们将首先在 WSDL 文档中定义 Web 服务接口。我们将学习如何使用 CXF 从 WSDL 创建服务器和客户端应用程序。我们将使应用程序保持简单,以保持对 CXF 用法的关注。创建服务器应用程序后,我们将使用内置的 CXF 类将其发布到所需的 URL。

首先,让我们描述我们将要使用的 WSDL。

HelloWorld 的 WSDL

我们将要实现的 Web 服务将有一个名为 **greetings** 的单个 Web 方法,它接受一个包含用户名的 **string** 参数,并在将问候消息附加到用户名后,将字符串消息返回给调用方。完整的 wsdl 如下所示 -

//Hello.wsdl
<?xml version = "1.0" encoding = "UTF-8"?>
<wsdl:definitions xmlns:soap = "http://schemas.xmlsoap.org/wsdl/soap/"
   xmlns:tns = "http://helloworld.tutorialspoint.com/"
   xmlns:wsdl = "http://schemas.xmlsoap.org/wsdl/"
   xmlns:xsd = "http://www.w3.org/2001/XMLSchema"
   name = "HelloWorld"
   targetNamespace = "http://helloworld.tutorialspoint.com/">
   <wsdl:types>
      <xsd:schema attributeFormDefault = "unqualified"
         elementFormDefault = "qualified"
         targetNamespace = "http://helloworld.tutorialspoint.com/">
         <xsd:element name = "greetings" type = "tns:greetings"/>
         <xsd:complexType name = "greetings">
            <xsd:sequence>
               <xsd:element minOccurs = "0" name = "arg0" type = "xsd:string"/>
            </xsd:sequence>
         </xsd:complexType>
         <xsd:element name = "greetingsResponse"
         type = "tns:greetingsResponse"/>
         <xsd:complexType name = "greetingsResponse">
            <xsd:sequence>
               <xsd:element minOccurs = "0" name = "return" type = "xsd:string"/>
            </xsd:sequence>
         </xsd:complexType>
      </xsd:schema>
   </wsdl:types>
   <wsdl:message name = "greetings">
      <wsdl:part element = "tns:greetings" name = "parameters"> </wsdl:part>
   </wsdl:message>
   <wsdl:message name = "greetingsResponse">
      <wsdl:part element = "tns:greetingsResponse" name = "parameters"> </wsdl:part>
   </wsdl:message>
   <wsdl:portType name = "HelloWorldPortType">
      <wsdl:operation name = "greetings">
         <wsdl:input message = "tns:greetings" name = "greetings">  </wsdl:input>
         <wsdl:output message = "tns:greetingsResponse" name = "greetingsResponse">
         </wsdl:output>
      </wsdl:operation>
   </wsdl:portType>
   <wsdl:binding name = "HelloWorldSoapBinding" type = "tns:HelloWorldPortType">
      <soap:binding style = "document"
      transport = "http://schemas.xmlsoap.org/soap/http"/>
      <wsdl:operation name = "greetings">
         <soap:operation soapAction = "" style = "document"/>
         <wsdl:input name = "greetings"></wsdl:input>
         <wsdl:output name = "greetingsResponse">
            <soap:body use = "literal"/>
         </wsdl:output>
         </wsdl:operation>
   </wsdl:binding>
   <wsdl:service name = "HelloWorldService">
      <wsdl:port binding = "tns:HelloWorldSoapBinding" name = "HelloWorldPort">
         <soap:address location = "https://127.0.0.1:9090/HelloServerPort"/>
      </wsdl:port>
   </wsdl:service>
</wsdl:definitions>

请注意,编写语法正确的 wsdl 一直是开发人员面临的挑战;有许多工具和在线编辑器可用于创建 wsdl。这些编辑器会询问您要实现的消息名称以及您希望在消息中传递的参数以及您希望客户端应用程序接收的返回消息类型。如果您了解 wsdl 语法,则可以手动编写整个文档或使用其中一个编辑器创建自己的文档。

在上面的 wsdl 中,我们定义了一个名为 **greetings** 的单个消息。该消息传递给名为 **HelloWorldService** 的服务,该服务正在 **https://127.0.0.1:9090/HelloServerPort** 上运行。

有了这些,我们现在将继续进行服务器开发。在开发服务器之前,我们需要为我们的 Web 服务生成 Apache CXF 接口。这需要从给定的 wsdl 完成。为此,您使用一个名为 **wsdl2java** 的工具。

wsdl2java 插件

由于我们将使用 maven 构建项目,因此您需要将以下插件添加到 **pom.xml** 文件中。

<plugins>
   <plugin>
      <groupId>org.apache.cxf</groupId>
      <artifactId>cxf-codegen-plugin</artifactId>
      <version>3.3.0</version>
      <executions>
         <execution>
            <id>generate-sources</id>
            <phase>generate-sources</phase>
            <configuration>
               <wsdlOptions>
                  <wsdlOption>
                     <wsdl>src/main/resources/hello.wsdl</wsdl>
                     <faultSerialVersionUID> 1 </faultSerialVersionUID>
                  </wsdlOption>
               </wsdlOptions>
            </configuration>
            <goals>
               <goal>wsdl2java</goal>
            </goals>
         </execution>
      </executions>
   </plugin>
</plugins>

请注意,我们将 wsdl 文件的位置指定为 **src/main/resources/Hello.wsdl**。您需要确保为您的项目创建了适当的目录结构并将前面显示的 **hello.wsdl** 文件添加到指定的文件夹中。

**wsdl2java** 插件将编译此 wsdl 并在预定义的文件夹中创建 Apache CXF 类。为了方便您参考,这里显示了完整的项目结构。

WSDL2Apache CXF Predefined Folder

现在,您可以使用 **wsdl2java** 生成的类创建服务器了。wsdl2java 创建的类如下图所示 -

WSDL2Apache CXF generated classes

生成的 Service 接口

在生成的类列表中,您一定注意到了其中一个类是 Apache CXF 接口 - 这是 **HelloWorldPortType.java**。在您的代码编辑器中检查此文件。为了方便您参考,这里显示了文件内容 -

//HelloWorldPortType.java
package com.tutorialspoint.helloworld;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;
/**
* This class was generated by Apache CXF 3.3.0
* 2019-02-11T12:05:55.220+05:30
* Generated source version: 3.3.0
*
*/

@WebService(targetNamespace = "http://helloworld.tutorialspoint.com/",
   name = "HelloWorldPortType")
@XmlSeeAlso({ObjectFactory.class})
public interface HelloWorldPortType {
   @WebMethod
   @RequestWrapper(localName = "greetings", targetNamespace =
      "http://helloworld.tutorialspoint.com/", className =
      "com.tutorialspoint.helloworld.Greetings")
      @ResponseWrapper(localName = "greetingsResponse", targetNamespace =
         "http://helloworld.tutorialspoint.com/", className =
         "com.tutorialspoint.helloworld.GreetingsResponse")
   @WebResult(name = "return", targetNamespace =
      "http://helloworld.tutorialspoint.com/")
   public java.lang.String greetings(
      @WebParam(name = "arg0", targetNamespace =
      "http://helloworld.tutorialspoint.com/")
      java.lang.String arg0
   );
}

请注意,该接口包含一个名为 **greetings** 的方法。这是我们 wsdl 中的一种消息类型。**wsdl2java** 工具已将此方法添加到生成的接口中。现在,您可以理解,您在 wsdl 中编写的任何消息,都将在接口中生成对应的方法。

现在,您的任务将是实现所有这些方法,这些方法对应于您在 wsdl 中定义的各种消息。请注意,在前面 Apache CXF-First 的示例中,我们从 Web 服务的 Apache CXF 接口开始。在这种情况下,Apache CXF 接口是从 wsdl 创建的。

实现 Service 接口

服务接口的实现非常简单。完整的实现如下所示 -

//HelloWorldImpl.java
package com.tutorialspoint.helloworld;
public class HelloWorldImpl implements HelloWorldPortType {
   @Override
   public String greetings(String name) {
      return ("hi " + name);
   }
}

代码实现了名为 **greetings** 的唯一接口方法。该方法采用一个 **string** 类型的参数,在其前面加上“hi”消息,并将结果字符串返回给调用方。

接下来,我们将编写服务器应用程序。

开发服务器

开发服务器应用程序同样非常简单。在这里,我们将使用 CXF 提供的 **Endpoint** 类来发布我们的服务。这是在以下两行代码中完成的 -

HelloWorldPortType implementor = new HelloWorldImpl();
   Endpoint.publish("https://127.0.0.1:9090/HelloServerPort",
      implementor,
      new LoggingFeature());

首先,我们创建服务实现类 **HelloWorldImpl** 的对象。然后,我们将此引用作为第二个参数传递给 **publish** 方法。第一个参数是发布服务的目标地址 - 客户端将使用此 URL 访问服务。服务器应用程序的完整源代码如下所示 -

//Server.java
package com.tutorialspoint.helloworld;
import javax.xml.ws.Endpoint;
import org.apache.cxf.ext.logging.LoggingFeature;
public class Server {
   public static void main(String[] args) throws Exception {
      HelloWorldPortType implementor = new HelloWorldImpl();
      Endpoint.publish("https://127.0.0.1:9090/HelloServerPort",
         implementor,
         new LoggingFeature());
      System.out.println("Server ready...");
      Thread.sleep(5 * 60 * 1000);
      System.out.println("Server exiting");
      System.exit(0);
   }
}

要构建此服务器类,您需要在 **pom.xml** 中添加一个构建配置文件。如下所示 -

<profile>
   <id>server</id>
   <build>
      <defaultGoal>test</defaultGoal>
      <plugins>
         <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>1.6.0</version>
            <executions>
               <execution>
                  <phase>test</phase>
                  <goals>
                     <goal>java</goal>
                  </goals>
                  <configuration>
                     <mainClass>
                        com.tutorialspoint.helloworld.Server
                     </mainClass>
                  </configuration>
               </execution>
            </executions>
         </plugin>
      </plugins>
   </build>
   <dependencies>
      <dependency>
         <groupId>org.apache.cxf</groupId>
         <artifactId>cxf-rt-transports-http-jetty</artifactId>
         <version>3.3.0</version>
      </dependency>
   </dependencies>
</profile>

请注意,配置中指定了 **Server** 类的完全限定名称。此外,dependency 标签指定我们将使用嵌入式 jetty web 服务器来部署我们的服务器应用程序。

部署服务器

最后,要部署服务器应用程序,您需要在 pom.xml 中进行另一个修改,以将您的应用程序设置为 Web 应用程序。您需要添加到 **pom.xml** 中的代码如下所示 -

<defaultGoal>install</defaultGoal>
<pluginManagement>
   <plugins>
      <plugin>
         <artifactId>maven-war-plugin</artifactId>
         <version>3.2.2</version>
         <configuration>
            <webXml>src/main/webapp/WEB-INF/web.xml</webXml>
            <webResources>
               <resource>
                  <directory>src/main/resources</directory>
                  <targetPath>WEB-INF</targetPath>
                  <includes>
                     <include>*.wsdl</include>
                  </includes>
               </resource>
            </webResources>
         </configuration>
      </plugin>
   </plugins>
</pluginManagement>

在部署应用程序之前,您需要向项目中添加两个文件。如下面的屏幕截图所示 -

Before Deploy WSDL Application

这些文件是 CXF 标准文件,定义了 **CXFServlet** 的映射。为了方便您快速参考,这里显示了 **web.xml** 文件中的代码 -

//cxf-servlet.xml
<web-app xmlns = "http://java.sun.com/xml/ns/javaee"
   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" version="2.5"
   xsi:schemaLocation = "http://java.sun.com/xml/ns/javaee
   http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
   <display-name>cxf</display-name>
   <servlet>
      <description>Apache CXF Endpoint</description>
      <display-name>cxf</display-name>
      <servlet-name>cxf</servlet-name>
      <servlet-class>
         org.apache.cxf.transport.servlet.CXFServlet
      </servlet-class>
      <load-on-startup>1</load-on-startup>
   </servlet>
   <servlet-mapping>
      <servlet-name>cxf</servlet-name>
      <url-pattern>/services/*</url-pattern>
   </servlet-mapping>
   <session-config>
      <session-timeout>60</session-timeout>
   </session-config>
</web-app>

在 **cxf-servlet.xml** 中,您声明了服务端点属性。这在下面的代码片段中显示 -

<beans ...>
   <jaxws:endpoint xmlns:helloworld = "https://tutorialspoint.com/"
      id="helloHTTP"
      address = "https://127.0.0.1:9090/HelloServerPort"
      serviceName = "helloworld:HelloServiceService"
      endpointName = "helloworld:HelloServicePort">
   </jaxws:endpoint>
</beans>

在这里,我们定义了服务端点的 id、服务可用的地址、服务名称和端点名称。现在,您了解了服务如何被 CXF servlet 路由和处理。

最终的 pom.xml

**pom.xml** 包含更多依赖项。与其描述所有依赖项,不如在下面包含 pom.xml 的最终版本 -

<?xml version="1.0" encoding = "UTF-8"?>
<project xmlns = "http://maven.apache.org/POM/4.0.0"
   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.tutorialspoint</groupId>
   <artifactId>cxf-wsdl</artifactId>
   <version>1.0</version>
   <packaging>jar</packaging>
   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <maven.compiler.source>1.8</maven.compiler.source>
      <maven.compiler.target>1.8</maven.compiler.target>
   </properties>
   <build>
      <defaultGoal>install</defaultGoal>
      <pluginManagement>
         <plugins>
            <plugin>
               <artifactId>maven-war-plugin</artifactId>
               <version>3.2.2</version>
               <configuration>
                  <webXml>src/main/webapp/WEB-INF/web.xml</webXml>
                  <webResources>
                     <resource>
                        <directory>src/main/resources</directory>
                        <targetPath>WEB-INF</targetPath>
                        <includes>
                           <include>*.wsdl</include>
                        </includes>
                     </resource>
                  </webResources>
               </configuration>
            </plugin>
         </plugins>
      </pluginManagement>
      <plugins>
         <plugin>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-codegen-plugin</artifactId>
            <version>3.3.0</version>
            <executions>
               <execution>
                  <id>generate-sources</id>
                  <phase>generate-sources</phase>
                  <configuration>
                     <wsdlOptions>
                        <wsdlOption>
                           <wsdl>src/main/resources/Hello.wsdl</wsdl>
                           <faultSerialVersionUID>1</faultSerialVersionUID>
                        </wsdlOption>
                     </wsdlOptions>
                  </configuration>
                  <goals>
                     <goal>wsdl2java</goal>
                  </goals>
               </execution>
            </executions>
         </plugin>
      </plugins>
   </build>
   <profiles>
      <profile>
         <id>server</id>
         <build>
            <defaultGoal>test</defaultGoal>
            <plugins>
               <plugin>
                  <groupId>org.codehaus.mojo</groupId>
                  <artifactId>exec-maven-plugin</artifactId>
                  <version>1.6.0</version>
                  <executions>
                     <execution>
                        <phase>test</phase>
                        <goals>
                           <goal>java</goal>
                        </goals>
                        <configuration>
                           <mainClass>
                              com.tutorialspoint.helloworld.Server
                           </mainClass>
                        </configuration>
                     </execution>
                  </executions>
               </plugin>
            </plugins>
         </build>
         <dependencies>
            <dependency>
               <groupId>org.apache.cxf</groupId>
               <artifactId>cxf-rt-transports-http-jetty</artifactId>
               <version>3.3.0</version>
            </dependency>
         </dependencies>
      </profile>
      <profile>
         <id>client</id>
         <build>
            <defaultGoal>test</defaultGoal>
            <plugins>
               <plugin>
                  <groupId>org.codehaus.mojo</groupId>
                  <artifactId>exec-maven-plugin</artifactId>
                  <executions>
                     <execution>
                        <phase>test</phase>
                        <goals>
                           <goal>java</goal>
                        </goals>
                        <configuration>
                           <mainClass>
                              com.tutorialspoint.helloworld.Client
                           </mainClass>
                        </configuration>
                     </execution>
                  </executions>
               </plugin>
            </plugins>
         </build>
      </profile>
   </profiles>
   <dependencies>
      <dependency>
         <groupId>org.apache.cxf</groupId>
         <artifactId>cxf-rt-frontend-jaxws</artifactId>
         <version>3.3.0</version>
      </dependency>
     
      <dependency>
         <groupId>org.apache.cxf</groupId>
         <artifactId>cxf-rt-transports-http</artifactId>
         <version>3.3.0</version>
      </dependency>
      
      <dependency>
         <groupId>org.apache.cxf</groupId>
         <artifactId>cxf-rt-management</artifactId>
         <version>3.3.0</version>
      </dependency>
      
      <dependency>
         <groupId>org.apache.cxf</groupId>
         <artifactId>cxf-rt-features-metrics</artifactId>
         <version>3.3.0</version>
      </dependency>
      
      <dependency>
         <groupId>org.apache.cxf.xjc-utils</groupId>
         <artifactId>cxf-xjc-runtime</artifactId>
         <version>3.3.0</version>
      </dependency>
      
      <dependency>
         <groupId>org.apache.cxf</groupId>
         <artifactId>cxf-rt-features-logging</artifactId>
         <version>3.3.0</version>
      </dependency>
     
     <dependency>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>exec-maven-plugin</artifactId>
         <version>1.6.0</version>
      </dependency>
      
      <dependency>
         <groupId>org.slf4j</groupId>
         <artifactId>slf4j-api</artifactId>
         <version>1.8.0-beta2</version>
      </dependency>
      
      <dependency>
         <groupId>org.apache.cxf</groupId>
         <artifactId>cxf-rt-transports-http-jetty</artifactId>
         <version>3.3.0</version>
      </dependency>
   </dependencies>
</project>

请注意,它还包括一个用于构建客户端的配置文件,我们将在后面的部分中学习。

运行 HelloWorld 服务

现在,您可以运行 Web 应用程序了。在命令窗口中,使用以下命令运行构建脚本。

mvn clean install

这将从您的 wsdl 生成相应的 Apache CXF 类、编译您的 Apache CXF 类、将服务器部署到嵌入式 jetty 服务器上并运行您的应用程序。

您将在控制台上看到以下消息 -

INFO: Setting the server's publish address to be 
https://127.0.0.1:9090/HelloServerPort
Server ready...

与之前一样,您可以通过在浏览器中打开服务器 URL 来测试服务器。

Opening Server URL

由于我们没有指定任何操作,因此我们的应用程序仅将错误消息返回到浏览器。现在,尝试将 **?wsdl** 添加到您的 URL 中,您将看到以下输出 -

WSDL Output

因此,我们的服务器应用程序按预期运行。您可以使用前面介绍的 SOAP 客户端(如 **Postman**)进一步测试您的服务。

本教程的下一部分是编写使用我们服务的客户端。

开发客户端

在 CXF 应用程序中编写客户端与编写服务器一样重要。以下是客户端的完整代码,它实际上只包含三行,其余行只是将服务信息打印给用户。

//Client.java
package com.tutorialspoint.helloworld;
public class Client {
   public static void main(String[] args) throws Exception {
      //Create the service client with its default wsdlurl
      HelloWorldService helloServiceService = new HelloWorldService();
      System.out.println("service: " +
         helloServiceService.getServiceName());
      System.out.println("wsdl location: " +
         helloServiceService.getWSDLDocumentLocation());
      HelloWorldPortType helloService =
         helloServiceService.getHelloWorldPort();
      System.out.println(helloService.greetings
      (System.getProperty("user.name")));
   }
}

在这里,我们简单地创建服务 **HelloWorldService** 的实例,通过调用 **getHelloWorldPort** 方法获取其端口,然后将我们的 **greetings** 消息传递给它。运行客户端,您将看到以下输出 -

service: {http://helloworld.tutorialspoint.com/}HelloWorldService
wsdl location: file:/Users/drsarang/Desktop/tutorialpoint/cxf-
wsdl/src/main/resources/Hello.wsdl
hi drsarang

到目前为止,您已经学习了如何在 CXF 中使用 Apache CXF-First 和 WSDL-First 架构。在 Apache CXF-First 方法中,您使用 POJO 和 CXF 库中的 **ServerFactoryBean** 类来创建服务器。要创建客户端,您使用了 CXF 库中的 **ClientProxyFactoryBean** 类。在 WSDL-First 方法中,您使用 **Endpoint** 类在所需的 URL 和指定的实现者处发布服务。您现在可以扩展这些技术来集成不同的协议和传输。

广告