Introduction to Apache CXF – Apache CXF简介

最后修改: 2016年 7月 11日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Apache CXF is a JAX-WS fully compliant framework.

Apache CXF是一个完全兼容JAX-WS的框架。

On top of features defined by JAX-WS standards, Apache CXF provides the capability of conversion between WSDL and Java classes, APIs used to manipulate raw XML messages, the support for JAX-RS, integration with the Spring Framework, etc.

在JAX-WS标准定义的功能之上,Apache CXF提供了WSDL和Java类之间的转换能力、用于操作原始XML消息的API、对JAX-RS的支持、与Spring框架的集成等等。

This tutorial is the first of a series on Apache CXF, introducing basic characteristics of the framework. It only uses the JAX-WS standard APIs in source code while still takes advantage of Apache CXF behind the scenes, such as automatically generated WSDL metadata and CXF default configuration.

本教程是关于Apache CXF系列的第一篇,介绍了该框架的基本特征。它只在源代码中使用JAX-WS标准API,同时仍然利用了Apache CXF的幕后优势,如自动生成的WSDL元数据和CXF默认配置。

2. Maven Dependencies

2.Maven的依赖性

The key dependency needed to use Apache CXF is org.apache.cxf:cxfrtfrontendjaxws. This provides a JAX-WS implementation to replace the built-in JDK one:

使用Apache CXF需要的关键依赖是org.apache.cxf:cxfrtfrontendjaxws。这提供了一个JAX-WS的实现,以取代内置的JDK的实现:

<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-frontend-jaxws</artifactId>
    <version>3.1.6</version>
</dependency>

Notice that this artifact contains a file named javax.xml.ws.spi.Provider inside the META-INF/services directory. Java VM looks at the first line of this file to determine the JAX-WS implementation to make use of. In this case, content of the line is org.apache.cxf.jaxws.spi.ProviderImpl, referring to the implementation supplied by Apache CXF.

注意这个工件包含一个名为javax.xml.ws.spi.Provider的文件,位于META-INF/services目录中。Java VM 通过查看该文件的第一行来确定要使用的 JAX-WS 实现。在这种情况下,这一行的内容是 org.apache.cxf.jaxws.spi.ProviderImpl,指的是由 Apache CXF 提供的实现。

In this tutorial, we do not use a servlet container to publish the service, therefore another dependency is required to provide necessary Java type definitions:

在本教程中,我们没有使用servlet容器来发布服务,因此需要另一个依赖关系来提供必要的Java类型定义。

<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-transports-http-jetty</artifactId>
    <version>3.1.6</version>
</dependency>

For the latest versions of these dependencies, please check out cxf-rt-frontend-jaxws and cxf-rt-transports-http-jetty in the Maven central repository.

关于这些依赖的最新版本,请查看Maven中央仓库中的cxf-rt-frontend-jaxwscxf-rt-transports-http-jetty

3. Web Service Endpoint

3.网络服务端点

Let’s start with the implementation class used to configure the service endpoint:

让我们从用于配置服务端点的实现类开始。

@WebService(endpointInterface = "com.baeldung.cxf.introduction.Baeldung")
public class BaeldungImpl implements Baeldung {
    private Map<Integer, Student> students 
      = new LinkedHashMap<Integer, Student>();

    public String hello(String name) {
        return "Hello " + name;
    }

    public String helloStudent(Student student) {
        students.put(students.size() + 1, student);
        return "Hello " + student.getName();
    }

    public Map<Integer, Student> getStudents() {
        return students;
    }
}

The most important thing to be noticed over here is the presence of the endpointInterface attribute in the @WebService annotation. This attribute points to an interface defining an abstract contract for the web service.

这里需要注意的最重要的事情是@WebService注解中endpointInterface属性的存在。这个属性指向一个定义了Web服务的抽象契约的接口。

All method signatures declared in the endpoint interface need to be implemented, but it’s not required to implement the interface.

所有在端点接口中声明的方法签名都需要被实现,但并不要求实现该接口。

Here the BaeldungImpl implementation class still implements the following endpoint interface to make it clear that all the declared methods of the interface have been implemented, but doing this is optional:

在这里,BaeldungImpl实现类仍然实现了下面的端点接口,以明确该接口的所有声明方法都已实现,但这样做是可选的。

@WebService
public interface Baeldung {
    public String hello(String name);

    public String helloStudent(Student student);

    @XmlJavaTypeAdapter(StudentMapAdapter.class)
    public Map<Integer, Student> getStudents();
}

By default, Apache CXF uses JAXB as its data binding architecture. However, since JAXB does not directly support binding of a Map, which is returned from the getStudents method, we need an adapter to convert the Map to a Java class that JAXB can use.

默认情况下,Apache CXF使用JAXB作为其数据绑定架构。然而,由于JAXB不直接支持Map的绑定,它是由getStudents方法返回的,我们需要一个适配器来将Map转换成JAXB可以使用的Java类

In addition, in order to separate contract elements from their implementation, we define Student as an interface and JAXB does not directly support interfaces either, thus we need one more adapter to deal with this. In fact, for convenience, we may declare Student as a class. The use of this type as an interface is just one more demonstration of using adaptation classes.

此外,为了将契约元素与它们的实现分开,我们将Student定义为一个接口,而JAXB也不直接支持接口,因此我们还需要一个适配器来处理这个问题。事实上,为了方便,我们可以将Student声明为一个类。将这个类型作为一个接口使用只是使用适配类的又一个示范。

The adapters are demonstrated in the section right below.

适配器在下面的部分进行了演示。

4. Custom Adapters

4.自定义适配器

This section illustrates the way to use adaptation classes to support binding of a Java interface and a Map using JAXB.

这一节说明了使用适应性类来支持使用 JAXB 绑定 Java 接口和 Map 的方法。

4.1. Interface Adapter

4.1.接口适配器

This is how the Student interface is defined:

这就是Student接口的定义方式。

@XmlJavaTypeAdapter(StudentAdapter.class)
public interface Student {
    public String getName();
}

This interface declares only one method returning a String and specifies StudentAdapter as the adaptation class to map itself to and from a type that can apply JAXB binding.

这个接口只声明了一个返回String的方法,并指定StudentAdapter作为适应类,将其自身映射到可以应用 JAXB 绑定的类型中,并从该类型中获取。

The StudentAdapter class is defined as follows:

StudentAdapter类定义如下。

public class StudentAdapter extends XmlAdapter<StudentImpl, Student> {
    public StudentImpl marshal(Student student) throws Exception {
        if (student instanceof StudentImpl) {
            return (StudentImpl) student;
        }
        return new StudentImpl(student.getName());
    }

    public Student unmarshal(StudentImpl student) throws Exception {
        return student;
    }
}

An adaptation class must implement the XmlAdapter interface and provide implementation for the marshal and unmarshal methods. The marshal method transforms a bound type (Student, an interface that JAXB cannot directly handle) into a value type (StudentImpl, a concrete class that can be processed by JAXB). The unmarshal method does things the other way around.

一个适配类必须实现XmlAdapter接口,并为marshalunmarshal方法提供实现。marshal方法将绑定类型(Student,一个 JAXB 不能直接处理的接口)转换为值类型(StudentImpl,一个可以被 JAXB 处理的具体类)。unmarshal方法则反其道而行之。

Here is the StudentImpl class definition:

这里是StudentImpl类的定义。

@XmlType(name = "Student")
public class StudentImpl implements Student {
    private String name;

    // constructors, getter and setter
}

4.2. Map Adapter

4.2.地图适配器

The getStudents method of Baeldung endpoint interface returns a Map and indicates an adaptation class to convert the Map to a type that can be handled by JAXB. Similar to the StudentAdapter class, this adaptation class must implement marshal and unmarshal methods of the XmlAdapter interface:

Baeldung 端点接口的 getStudents 方法返回一个 Map 并指示一个适配类将 Map 转换为 JAXB 可以处理的类型。与StudentAdapter类类似,这个适配类必须实现marshalunmarshal方法的XmlAdapter接口。

public class StudentMapAdapter 
  extends XmlAdapter<StudentMap, Map<Integer, Student>> {
    public StudentMap marshal(Map<Integer, Student> boundMap) throws Exception {
        StudentMap valueMap = new StudentMap();
        for (Map.Entry<Integer, Student> boundEntry : boundMap.entrySet()) {
            StudentMap.StudentEntry valueEntry  = new StudentMap.StudentEntry();
            valueEntry.setStudent(boundEntry.getValue());
            valueEntry.setId(boundEntry.getKey());
            valueMap.getEntries().add(valueEntry);
        }
        return valueMap;
    }

    public Map<Integer, Student> unmarshal(StudentMap valueMap) throws Exception {
        Map<Integer, Student> boundMap = new LinkedHashMap<Integer, Student>();
        for (StudentMap.StudentEntry studentEntry : valueMap.getEntries()) {
            boundMap.put(studentEntry.getId(), studentEntry.getStudent());
        }
        return boundMap;
    }
}

The StudentMapAdapter class maps Map<Integer, Student> to and from the StudentMap value type with the definition as follows:

StudentMapAdapter类将Map<Integer, Student>映射到StudentMap值类型,定义如下。

@XmlType(name = "StudentMap")
public class StudentMap {
    private List<StudentEntry> entries = new ArrayList<StudentEntry>();

    @XmlElement(nillable = false, name = "entry")
    public List<StudentEntry> getEntries() {
        return entries;
    }

    @XmlType(name = "StudentEntry")
    public static class StudentEntry {
        private Integer id;
        private Student student;

        // getters and setters
    }
}

5. Deployment

5.部署

5.1. Server Definition

5.1.服务器的定义

In order to deploy the web service discussed above, we will make use of the standard JAX-WS APIs. Since we are using Apache CXF, the framework does some extra work, e.g. generating and publishing the WSDL schema. Here is how the service server is defined:

为了部署上面讨论的Web服务,我们将利用标准的JAX-WS APIs。由于我们使用的是Apache CXF,该框架会做一些额外的工作,例如生成和发布WSDL模式。下面是服务服务器的定义方式。

public class Server {
    public static void main(String args[]) throws InterruptedException {
        BaeldungImpl implementor = new BaeldungImpl();
        String address = "http://localhost:8080/baeldung";
        Endpoint.publish(address, implementor);
        Thread.sleep(60 * 1000);        
        System.exit(0);
    }
}

After the server is active for a while to facilitate testing, it should be shut down to release system resources. You may specify any working duration for the server based on your needs by passing a long argument to the Thread.sleep method.

在服务器活动了一段时间以方便测试后,应该关闭它以释放系统资源。你可以通过向Thread.sleep方法传递一个long参数,根据你的需要为服务器指定任何工作时间。

5.2. Deployment of the Server

5.2.部署服务器

In this tutorial, we use the org.codehaus.mojo:exec-maven-plugin plugin to instantiate the server illustrated above and control its lifecycle. This is declared in the Maven POM file as follows:

在本教程中,我们使用org.codehaus.mojo:exec-maven-plugin插件来实例化上述的服务器并控制其生命周期。这在Maven的POM文件中声明如下。

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <configuration>
        <mainClass>com.baeldung.cxf.introduction.Server</mainClass>
    </configuration>
</plugin>

The mainClass configuration refers to the Server class where the web service endpoint is published. After running the java goal of this plugin, we can check out the WSDL schema automatically generated by Apache CXF by accessing the URL http://localhost:8080/baeldung?wsdl.

mainClass配置是指发布Web服务端点的Server类。在运行该插件的java目标后,我们可以通过访问URL http://localhost:8080/baeldung?wsdl来查看由Apache CXF自动生成的WSDL模式。

6. Test Cases

6.测试案例

This section walks you through steps to write test cases used to verify the web service we created before.

本节将引导你完成编写测试用例的步骤,用于验证我们之前创建的网络服务。

Please note that we need to execute the exec:java goal to start the web service server before running any test.

请注意,在运行任何测试之前,我们需要执行exec:java目标来启动Web服务服务器。

6.1. Preparation

6.1.准备工作

The first step is to declare several fields for the test class:

第一步是为测试类声明几个字段。

public class StudentTest {
    private static QName SERVICE_NAME 
      = new QName("http://introduction.cxf.baeldung.com/", "Baeldung");
    private static QName PORT_NAME 
      = new QName("http://introduction.cxf.baeldung.com/", "BaeldungPort");

    private Service service;
    private Baeldung baeldungProxy;
    private BaeldungImpl baeldungImpl;

    // other declarations
}

The following initializer block is used to initiate the service field of the javax.xml.ws.Service type prior to running any test:

在运行任何测试之前,下面的初始化程序块被用来启动service字段的javax.xml.ws.Service类型。

{
    service = Service.create(SERVICE_NAME);
    String endpointAddress = "http://localhost:8080/baeldung";
    service.addPort(PORT_NAME, SOAPBinding.SOAP11HTTP_BINDING, endpointAddress);
}

After adding JUnit dependency to the POM file, we can use the @Before annotation as in the code snippet below. This method runs before every test to re-instantiate Baeldung fields:

在POM文件中加入JUnit依赖后,我们可以使用@Before注解,如下面的代码片段。这个方法在每次测试前运行,以重新确定Baeldung字段。

@Before
public void reinstantiateBaeldungInstances() {
    baeldungImpl = new BaeldungImpl();
    baeldungProxy = service.getPort(PORT_NAME, Baeldung.class);
}

The baeldungProxy variable is a proxy for the web service endpoint, while baeldungImpl is just a simple Java object. This object is used to compare results of invocations of remote endpoint methods through the proxy with invocations of local methods.

baeldungProxy变量是Web服务端点的代理,而baeldungImpl只是一个简单的Java对象。这个对象被用来比较通过代理调用远程端点方法和调用本地方法的结果。

Note that a QName instance is identified by two parts: a Namespace URI and a local part. If the PORT_NAME argument, of the QName type, of the Service.getPort method is omitted, Apache CXF will assume that argument’s Namespace URI is the package name of the endpoint interface in the reverse order and its local part is the interface name appended by Port, which is the exact same value of PORT_NAME. Therefore, in this tutorial we may leave this argument out.

请注意,QName 实例由两部分组成:一个命名空间 URI 和一个本地部分。如果Service.getPort方法的QName类型的PORT_NAME参数被省略,Apache CXF将假定该参数的Namespace URI是相反顺序的端点接口的包名,其本地部分是由Port附加的接口名,这与PORT_NAME的值完全相同。因此,在本教程中我们可以不使用这个参数。

6.2. Test Implementation

6.2.测试实施

The first test case we illustrate in this sub-section is to validate the response returned from a remote invocation of the hello method on the service endpoint:

我们在这个小节中说明的第一个测试案例是验证从服务端点上远程调用hello方法返回的响应。

@Test
public void whenUsingHelloMethod_thenCorrect() {
    String endpointResponse = baeldungProxy.hello("Baeldung");
    String localResponse = baeldungImpl.hello("Baeldung");
    assertEquals(localResponse, endpointResponse);
}

It is clear that the remote endpoint method returns the same response as the local method, meaning the web service works as expected.

很明显,远程端点方法返回的响应与本地方法相同,这意味着网络服务按预期工作。

The next test case demonstrates the use of helloStudent method:

下一个测试案例演示了helloStudent方法的使用。

@Test
public void whenUsingHelloStudentMethod_thenCorrect() {
    Student student = new StudentImpl("John Doe");
    String endpointResponse = baeldungProxy.helloStudent(student);
    String localResponse = baeldungImpl.helloStudent(student);
    assertEquals(localResponse, endpointResponse);
}

In this case, the client submits a Student object to the endpoint and receives a message containing the student’s name in return. Like the previous test case, the responses from both remote and local invocations are the same.

在这种情况下,客户端向端点提交一个Student对象,并收到一个包含学生姓名的消息作为回报。与之前的测试案例一样,远程和本地调用的响应都是一样的。

The last test case that we show over here is more complicated. As defined by the service endpoint implementation class, each time the client invokes the helloStudent method on the endpoint, the submitted Student object will be stored in a cache. This cache can by retrieved by calling the getStudents method on the endpoint. The following test case confirms that content of the students cache represents what the client has sent to the web service:

我们在这里展示的最后一个测试案例更为复杂。正如服务端点实现类所定义的,每次客户端调用端点上的helloStudent方法时,提交的Student对象将被存储在一个缓存中。这个缓存可以通过调用端点上的getStudents方法进行检索。下面的测试案例确认了students缓存的内容代表了客户发送给网络服务的内容。

@Test
public void usingGetStudentsMethod_thenCorrect() {
    Student student1 = new StudentImpl("Adam");
    baeldungProxy.helloStudent(student1);

    Student student2 = new StudentImpl("Eve");
    baeldungProxy.helloStudent(student2);
        
    Map<Integer, Student> students = baeldungProxy.getStudents();       
    assertEquals("Adam", students.get(1).getName());
    assertEquals("Eve", students.get(2).getName());
}

7. Conclusion

7.结论

This tutorial introduced Apache CXF, a powerful framework to work with web services in Java. It focused on the application of the framework as a standard JAX-WS implementation, while still making use of the framework’s specific capabilities at run-time.

本教程介绍了Apache CXF,一个在Java中处理Web服务的强大框架。它侧重于将该框架作为一个标准的JAX-WS实现来应用,同时在运行时仍然利用该框架的特定功能。

The implementation of all these examples and code snippets can be found in a GitHub project.

所有这些例子和代码片段的实现都可以在一个GitHub项目中找到