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 = "http://localhost:9090/HelloServerPort"/>
      </wsdl:port>
   </wsdl:service>
</wsdl:definitions>

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

在上面的 wsdl 中,我们定义了一条名为 greetings 的消息。 该消息将传递到在 http://localhost:9090/HelloServerPort 上运行的名为 HelloWorldService 的服务。

这样,我们现在就可以进行服务器开发了。 在开发服务器之前,我们需要为我们的 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 预定义文件夹

现在,您已准备好使用 wsdl2java 生成的类创建服务器。 wsdl2java 创建的类如下图所示 −

WSDL2Apache CXF 生成的类

生成的服务接口

在生成的类列表中,您一定已经注意到其中一个是 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 创建的。

实现服务接口

服务接口的实现很简单。 完整的实现如下面的清单所示 −

//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("http://localhost: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("http://localhost: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 类的完全限定名称是在配置中指定的。 此外,依赖标记指定我们将使用嵌入式 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>

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

部署 WSDL 应用程序之前

这些文件是 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://www.tutorialspoint.com/"
      id="helloHTTP"
      address = "http://localhost: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 服务

现在,您已准备好运行网络应用程序。 在命令窗口中,使用以下命令运行构建脚本。

mvn clean install

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

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

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

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

打开服务器 URL

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

WSDL 输出

所以我们的服务器应用程序正在按预期运行。 您可以使用 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 方法中,您使用带有 CXF 库中的 ServerFactoryBean 类的 POJO 来创建服务器。 要创建客户端,您使用了 CXF 库中的 ClientProxyFactoryBean 类。 在 WSDL-First 方法中,您使用 Endpoint 类在所需的 URL 和指定的实现者处发布服务。 您现在可以扩展这些技术以集成不同的协议和传输。