Apache CXF Support for RESTful Web Services – Apache CXF对RESTful Web服务的支持

最后修改: 2016年 10月 28日

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

1. Overview

1.概述

This tutorial introduces Apache CXF as a framework compliant with the JAX-RS standard, which defines support of the Java ecosystem for the REpresentational State Transfer (REST) architectural pattern.

本教程介绍了Apache CXF作为一个符合JAX-RS标准的框架,该标准定义了Java生态系统对REpresentational State Transfer(REST)架构模式的支持。

Specifically, it describes step by step how to construct and publish a RESTful web service, and how to write unit tests to verify a service.

具体来说,它逐步描述了如何构建和发布一个RESTful网络服务,以及如何编写单元测试来验证一个服务。

This is the third in a series on Apache CXF; the first one focuses on the usage of CXF as a JAX-WS fully compliant implementation. The second article provides a guide on how to use CXF with Spring.

这是关于Apache CXF系列的第三篇文章;第一篇文章着重介绍了CXF作为JAX-WS完全兼容实现的用法。第二篇文章提供了关于如何使用CXF与Spring的指南。

2. Maven Dependencies

2.Maven的依赖性

The first required dependency is org.apache.cxf:cxf-rt-frontend-jaxrs. This artifact provides JAX-RS APIs as well as a CXF implementation:

第一个必要的依赖是org.apache.cxf:cxf-rt-frontend-jaxrs。这个工件提供了JAX-RS API以及CXF实现:

<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-frontend-jaxrs</artifactId>
    <version>3.1.7</version>
</dependency>

In this tutorial, we use CXF to create a Server endpoint to publish a web service instead of using a servlet container. Therefore, the following dependency needs to be included in the Maven POM file:

在本教程中,我们使用CXF创建一个Server端点来发布Web服务,而不是使用servlet容器。因此,Maven的POM文件中需要包含以下依赖关系。

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

Finally, let’s add the HttpClient library to facilitate unit tests:

最后,让我们添加HttpClient库以方便单元测试。

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.2</version>
</dependency>

Here you can find the latest version of the cxf-rt-frontend-jaxrs dependency. You may also want to refer to this link for the latest versions of the org.apache.cxf:cxf-rt-transports-http-jetty artifacts. Finally, the latest version of httpclient can be found here.

在这里您可以找到cxf-rt-frontend-jaxrs依赖的最新版本。您可能还想参考这个链接,以获取org.apache.cxf:cxf-rt-transports-http-jetty工件的最新版本。最后,最新版本的httpclient可以在这里找到。

3. Resource Classes and Request Mapping

3.资源类别和请求映射

Let’s start implementing a simple example; we’re going to set up our REST API with two resources Course and Student.

让我们开始实施一个简单的例子;我们将用两个资源Course Student设置我们的REST API。课程学生

We’ll start simple and move towards a more complex example as we go.

我们将从简单的开始,随着时间的推移走向更复杂的例子。

3.1. The Resources

3.1.资源

Here is the definition of the Student resource class:

这里是Student资源类的定义。

@XmlRootElement(name = "Student")
public class Student {
    private int id;
    private String name;

    // standard getters and setters
    // standard equals and hashCode implementations

}

Notice we’re using the @XmlRootElement annotation to tell JAXB that instances of this class should be marshaled to XML.

注意我们使用了@XmlRootElement注解来告诉JAXB,这个类的实例应该被编入XML。

Next, comes the definition of the Course resource class:

接下来是对Course资源类的定义。

@XmlRootElement(name = "Course")
public class Course {
    private int id;
    private String name;
    private List<Student> students = new ArrayList<>();

    private Student findById(int id) {
        for (Student student : students) {
            if (student.getId() == id) {
                return student;
            }
        }
        return null;
    }
    // standard getters and setters
    // standard equals and hasCode implementations
    
}

Finally, let’s implement the CourseRepository – which is the root resource and serves as the entry point to web service resources:

最后,让我们来实现 CourseRepository–它是根资源,是网络服务资源的入口。

@Path("course")
@Produces("text/xml")
public class CourseRepository {
    private Map<Integer, Course> courses = new HashMap<>();

    // request handling methods

    private Course findById(int id) {
        for (Map.Entry<Integer, Course> course : courses.entrySet()) {
            if (course.getKey() == id) {
                return course.getValue();
            }
        }
        return null;
    }
}

Notice the mapping with the @Path annotation. The CourseRepository is the root resource here, so it’s mapped to handle all URLS starting with course.

注意与@Path注解的映射。CourseRepository是这里的根资源,所以它被映射为处理所有以course开头的URLS。

The value of @Produces annotation is used to tell the server to convert objects returned from methods within this class to XML documents before sending them to clients. We’re using JAXB here as the default since no other binding mechanisms are specified.

@Produces 注解的值用于告诉服务器在将对象发送给客户之前将从这个类中的方法返回的对象转换为 XML 文档。由于没有指定其他的绑定机制,我们在这里使用JAXB作为默认值。

3.2. Simple Data Setup

3.2.简单的数据设置

Because this is a simple example implementation, we’re using in-memory data instead of a full-fledged persistent solution.

因为这是一个简单的例子实现,我们使用的是内存数据,而不是一个完整的持久化解决方案。

With that in mind, let’s implement some simple setup logic to populate some data into the system:

考虑到这一点,让我们实现一些简单的设置逻辑,将一些数据填充到系统中。

{
    Student student1 = new Student();
    Student student2 = new Student();
    student1.setId(1);
    student1.setName("Student A");
    student2.setId(2);
    student2.setName("Student B");

    List<Student> course1Students = new ArrayList<>();
    course1Students.add(student1);
    course1Students.add(student2);

    Course course1 = new Course();
    Course course2 = new Course();
    course1.setId(1);
    course1.setName("REST with Spring");
    course1.setStudents(course1Students);
    course2.setId(2);
    course2.setName("Learn Spring Security");

    courses.put(1, course1);
    courses.put(2, course2);
}

Methods within this class that take care of HTTP requests are covered in the next subsection.

该类中负责处理HTTP请求的方法将在下一小节介绍。

3.3. The API – Request Mapping Methods

3.3.API – 请求映射方法

Now, let’s go to the implementation of the actual REST API.

现在,让我们来看看实际的REST API的实现。

We’re going to start adding API operations – using the @Path annotation – right in the resource POJOs.

我们将开始添加API操作–使用@Path注解–就在资源POJOs中。

It’s important to understand that is a significant difference from the approach in a typical Spring project – where the API operations would be defined in a controller, not on the POJO itself.

要知道这与典型的Spring项目的方法有很大的不同–在Spring项目中,API操作会被定义在控制器中,而不是在POJO本身。

Let’s start with mapping methods defined inside the Course class:

让我们从定义在Course类内的映射方法开始。

@GET
@Path("{studentId}")
public Student getStudent(@PathParam("studentId")int studentId) {
    return findById(studentId);
}

Simply put, the method is invoked when handling GET requests, denoted by the @GET annotation.

简单地说,该方法在处理GET请求时被调用,由@GET注释表示。

Noticed the simple syntax of mapping the studentId path parameter from the HTTP request.

注意到从HTTP请求中映射studentId路径参数的简单语法。

We’re then simply using the findById helper method to return the corresponding Student instance.

然后我们简单地使用findById辅助方法来返回相应的Student实例。

The following method handles POST requests, indicated by the @POST annotation, by adding the received Student object to the students list:

下面的方法处理POST请求,由@POST注解表示,将收到的Student对象添加到students列表。

@POST
@Path("")
public Response createStudent(Student student) {
    for (Student element : students) {
        if (element.getId() == student.getId() {
            return Response.status(Response.Status.CONFLICT).build();
        }
    }
    students.add(student);
    return Response.ok(student).build();
}

This returns a 200 OK response if the create operation was successful, or 409 Conflict if an object with the submitted id is already existent.

如果创建操作成功,这将返回一个200 OK响应,如果一个具有提交的id的对象已经存在,则返回409 Conflict

Also note that we can skip the @Path annotation since its value is an empty String.

还要注意,我们可以跳过@Path注解,因为它的值是一个空的字符串。

The last method takes care of DELETE requests. It removes an element from the students list whose id is the received path parameter and returns a response with OK (200) status. In case there are no elements associated with the specified id, which implies there is nothing to be removed, this method returns a response with Not Found (404) status:

最后一个方法负责处理DELETE请求。它从students列表中删除一个元素,其id是收到的路径参数,并返回一个OK(200)状态的响应。如果没有与指定的id相关的元素,这意味着没有任何东西要被删除,这个方法会返回一个Not Found(404)状态的响应。

@DELETE
@Path("{studentId}")
public Response deleteStudent(@PathParam("studentId") int studentId) {
    Student student = findById(studentId);
    if (student == null) {
        return Response.status(Response.Status.NOT_FOUND).build();
    }
    students.remove(student);
    return Response.ok().build();
}

Let’s move on to request mapping methods of the CourseRepository class.

让我们继续讨论CourseRepository类的请求映射方法。

The following getCourse method returns a Course object that is the value of an entry in the courses map whose key is the received courseId path parameter of a GET request. Internally, the method dispatches path parameters to the findById helper method to do its job.

下面的getCourse方法返回一个Course对象,该对象是courses地图中的一个条目的值,其键是一个GET请求的courseId路径参数。在内部,该方法将路径参数分派给findById辅助方法来完成其工作。

@GET
@Path("courses/{courseId}")
public Course getCourse(@PathParam("courseId") int courseId) {
    return findById(courseId);
}

The following method updates an existing entry of the courses map, where the body of the received PUT request is the entry value and the courseId parameter is the associated key:

以下方法更新courses地图的一个现有条目,其中收到的PUT请求的主体是条目值,courseId参数是相关的键。

@PUT
@Path("courses/{courseId}")
public Response updateCourse(@PathParam("courseId") int courseId, Course course) {
    Course existingCourse = findById(courseId);        
    if (existingCourse == null) {
        return Response.status(Response.Status.NOT_FOUND).build();
    }
    if (existingCourse.equals(course)) {
        return Response.notModified().build();    
    }
    courses.put(courseId, course);
    return Response.ok().build();
}

This updateCourse method returns a response with OK (200) status if the update is successful, does not change anything and returns a Not Modified (304) response if the existing and uploaded objects have the same field values. In case a Course instance with the given id is not found in the courses map, the method returns a response with Not Found (404) status.

如果更新成功,这个updateCourse方法返回一个OK(200)状态的响应,不改变任何东西,如果现有对象和上传对象有相同的字段值,则返回一个Not Modified(304)响应。如果在courses地图中没有找到具有给定idCourse实例,该方法返回Not Found(404)状态的响应。

The third method of this root resource class does not directly handle any HTTP request. Instead, it delegates requests to the Course class where requests are handled by matching methods:

这个根资源类的第三个方法并不直接处理任何HTTP请求。相反,它将请求委托给Course类,在那里请求由匹配的方法处理。

@Path("courses/{courseId}/students")
public Course pathToStudent(@PathParam("courseId") int courseId) {
    return findById(courseId);
}

We have shown methods within the Course class that process delegated requests right before.

我们之前已经展示了Course类中处理委托请求的方法。

4. Server Endpoint

4.服务器端点

This section focuses on the construction of a CXF server, which is used for publishing the RESTful web service whose resources are depicted in the preceding section. The first step is to instantiate a JAXRSServerFactoryBean object and set the root resource class:

本节重点介绍CXF服务器的构建,该服务器用于发布RESTful Web服务,其资源在上一节中得到了描述。第一步是实例化一个JAXRSServerFactoryBean对象并设置根资源类。

JAXRSServerFactoryBean factoryBean = new JAXRSServerFactoryBean();
factoryBean.setResourceClasses(CourseRepository.class);

A resource provider then needs to be set on the factory bean to manage the life cycle of the root resource class. We use the default singleton resource provider that returns the same resource instance to every request:

然后需要在工厂Bean上设置一个资源提供者来管理根资源类的生命周期。我们使用默认的单子资源提供者,为每个请求返回相同的资源实例。

factoryBean.setResourceProvider(
  new SingletonResourceProvider(new CourseRepository()));

We also set an address to indicate the URL where the web service is published:

我们还设置了一个地址,表示网络服务发布的URL。

factoryBean.setAddress("http://localhost:8080/");

Now the factoryBean can be used to create a new server that will start listening for incoming connections:

现在,factoryBean可以用来创建一个新的server,它将开始监听进入的连接。

Server server = factoryBean.create();

All the code above in this section should be wrapped in the main method:

本节上面的所有代码都应该被包裹在main方法中。

public class RestfulServer {
    public static void main(String args[]) throws Exception {
        // code snippets shown above
    }
}

The invocation of this main method is presented in section 6.

这个main方法的调用将在第6节介绍。

5. Test Cases

5.测试案例

This section describes test cases used to validate the web service we created before. Those tests validate resource states of the service after responding to HTTP requests of the four most commonly used methods, namely GET, POST, PUT, and DELETE.

本节描述了用于验证我们之前创建的网络服务的测试案例。这些测试在响应四个最常用的方法(即GETPOSTPUTDELETE)的HTTP请求后验证服务的资源状态。

5.1. Preparation

5.1.准备工作

First, two static fields are declared within the test class, named RestfulTest:

首先,在测试类中声明两个静态字段,名为RestfulTest

private static String BASE_URL = "http://localhost:8080/baeldung/courses/";
private static CloseableHttpClient client;

Before running tests we create a client object, which is used to communicate with the server and destroy it afterward:

在运行测试之前,我们创建一个client对象,用来与服务器通信,之后再销毁它。

@BeforeClass
public static void createClient() {
    client = HttpClients.createDefault();
}
    
@AfterClass
public static void closeClient() throws IOException {
    client.close();
}

The client instance is now ready to be used by test cases.

client实例现在可以被测试用例所使用。

5.2. GET Requests

5.2.GET请求

In the test class, we define two methods to send GET requests to the server running the web service.

在测试类中,我们定义了两个方法来向运行网络服务的服务器发送GET请求。

The first method is to get a Course instance given its id in the resource:

第一种方法是在资源中获得一个课程实例的id

private Course getCourse(int courseOrder) throws IOException {
    URL url = new URL(BASE_URL + courseOrder);
    InputStream input = url.openStream();
    Course course
      = JAXB.unmarshal(new InputStreamReader(input), Course.class);
    return course;
}

The second is to get a Student instance given the ids of the course and student in the resource:

第二种是在资源中给定课程和学生的ids,获得一个Student实例。

private Student getStudent(int courseOrder, int studentOrder)
  throws IOException {
    URL url = new URL(BASE_URL + courseOrder + "/students/" + studentOrder);
    InputStream input = url.openStream();
    Student student
      = JAXB.unmarshal(new InputStreamReader(input), Student.class);
    return student;
}

These methods send HTTP GET requests to the service resource, then unmarshal XML responses to instances of the corresponding classes. Both are used to verify service resource states after executing POST, PUT, and DELETE requests.

这些方法向服务资源发送 HTTP GET 请求,然后将 XML 响应解密给相应类的实例。两者都用于在执行POSTPUTDELETE请求后验证服务资源状态。

5.3. POST Requests

5.3.POST请求

This subsection features two test cases for POST requests, illustrating operations of the web service when the uploaded Student instance leads to a conflict and when it is successfully created.

本小节介绍了两个POST请求的测试案例,说明了当上传的Student实例导致冲突和成功创建时网络服务的操作。

In the first test, we use a Student object unmarshaled from the conflict_student.xml file, located on the classpath with the following content:

在第一个测试中,我们使用一个从conflict_student.xml文件中解封的Student对象,该文件位于classpath上,内容如下。

<Student>
    <id>2</id>
    <name>Student B</name>
</Student>

This is how that content is converted to a POST request body:

这就是该内容被转换为POST请求体的方式。

HttpPost httpPost = new HttpPost(BASE_URL + "1/students");
InputStream resourceStream = this.getClass().getClassLoader()
  .getResourceAsStream("conflict_student.xml");
httpPost.setEntity(new InputStreamEntity(resourceStream));

The Content-Type header is set to tell the server that the content type of the request is XML:

设置Content-Type头是为了告诉服务器,请求的内容类型是XML。

httpPost.setHeader("Content-Type", "text/xml");

Since the uploaded Student object is already existent in the first Course instance, we expect that the creation fails and a response with Conflict (409) status is returned. The following code snippet verifies the expectation:

由于上传的Student对象已经存在于第一个Course实例中,我们期望创建失败并返回Conflict(409)状态的响应。下面的代码片段验证了这个期望。

HttpResponse response = client.execute(httpPost);
assertEquals(409, response.getStatusLine().getStatusCode());

In the next test, we extract the body of an HTTP request from a file named created_student.xml, also on the classpath. Here is content of the file:

在下一个测试中,我们从一个名为created_student.xml的文件中提取HTTP请求的正文,该文件也在classpath上。以下是该文件的内容。

<Student>
    <id>3</id>
    <name>Student C</name>
</Student>

Similar to the previous test case, we build and execute a request, then verify that a new instance is successfully created:

与之前的测试案例类似,我们建立并执行一个请求,然后验证一个新的实例是否被成功创建。

HttpPost httpPost = new HttpPost(BASE_URL + "2/students");
InputStream resourceStream = this.getClass().getClassLoader()
  .getResourceAsStream("created_student.xml");
httpPost.setEntity(new InputStreamEntity(resourceStream));
httpPost.setHeader("Content-Type", "text/xml");
        
HttpResponse response = client.execute(httpPost);
assertEquals(200, response.getStatusLine().getStatusCode());

We may confirm new states of the web service resource:

我们可以确认网络服务资源的新状态。

Student student = getStudent(2, 3);
assertEquals(3, student.getId());
assertEquals("Student C", student.getName());

This is what the XML response to a request for the new Student object looks like:

这就是对新Student对象请求的XML响应的样子。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Student>
    <id>3</id>
    <name>Student C</name>
</Student>

5.4. PUT Requests

5.4.PUT请求

Let’s start with an invalid update request, where the Course object being updated does not exist. Here is content of the instance used to replace a non-existent Course object in the web service resource:

让我们从一个无效的更新请求开始,被更新的Course对象并不存在。这里是用于替换Web服务资源中不存在的Course对象的实例的内容。

<Course>
    <id>3</id>
    <name>Apache CXF Support for RESTful</name>
</Course>

That content is stored in a file called non_existent_course.xml on the classpath. It is extracted and then used to populate the body of a PUT request by the code below:

这些内容被存储在classpath上一个名为non_existent_course.xml的文件中。它被提取出来,然后被下面的代码用来填充PUT请求的主体。

HttpPut httpPut = new HttpPut(BASE_URL + "3");
InputStream resourceStream = this.getClass().getClassLoader()
  .getResourceAsStream("non_existent_course.xml");
httpPut.setEntity(new InputStreamEntity(resourceStream));

The Content-Type header is set to tell the server that the content type of the request is XML:

设置Content-Type头是为了告诉服务器,请求的内容类型是XML。

httpPut.setHeader("Content-Type", "text/xml");

Since we intentionally sent an invalid request to update a non-existent object, a Not Found (404) response is expected to be received. The response is validated:

由于我们故意发送了一个无效的请求来更新一个不存在的对象,预计会收到一个Not Found(404)响应。响应是经过验证的。

HttpResponse response = client.execute(httpPut);
assertEquals(404, response.getStatusLine().getStatusCode());

In the second test case for PUT requests, we submit a Course object with the same field values. Since nothing is changed in this case, we expect that a response with Not Modified (304) status is returned. The whole process is illustrated:

PUT请求的第二个测试案例中,我们提交一个具有相同字段值的Course对象。由于在这种情况下没有任何改变,我们期望返回一个带有未修改(304)状态的响应。整个过程如图所示。

HttpPut httpPut = new HttpPut(BASE_URL + "1");
InputStream resourceStream = this.getClass().getClassLoader()
  .getResourceAsStream("unchanged_course.xml");
httpPut.setEntity(new InputStreamEntity(resourceStream));
httpPut.setHeader("Content-Type", "text/xml");
        
HttpResponse response = client.execute(httpPut);
assertEquals(304, response.getStatusLine().getStatusCode());

Where unchanged_course.xml is the file on the classpath keeping information used to update. Here is its content:

其中unchanged_course.xml是classpath上保持用于更新的信息的文件。这里是它的内容。

<Course>
    <id>1</id>
    <name>REST with Spring</name>
</Course>

In the last demonstration of PUT requests, we execute a valid update. The following is content of the changed_course.xml file whose content is used to update a Course instance in the web service resource:

PUT请求的最后一个演示中,我们执行了一个有效的更新。下面是changed_course.xml文件的内容,其内容被用来更新Web服务资源中的Course实例。

<Course>
    <id>2</id>
    <name>Apache CXF Support for RESTful</name>
</Course>

This is how the request is built and executed:

这就是请求的建立和执行方式。

HttpPut httpPut = new HttpPut(BASE_URL + "2");
InputStream resourceStream = this.getClass().getClassLoader()
  .getResourceAsStream("changed_course.xml");
httpPut.setEntity(new InputStreamEntity(resourceStream));
httpPut.setHeader("Content-Type", "text/xml");

Let’s validate a PUT request to the server and validate a successful upload:

让我们验证一个到服务器的PUT请求,并验证一个成功的上传。

HttpResponse response = client.execute(httpPut);
assertEquals(200, response.getStatusLine().getStatusCode());

Let’s verify the new states of the web service resource:

让我们验证一下网络服务资源的新状态。

Course course = getCourse(2);
assertEquals(2, course.getId());
assertEquals("Apache CXF Support for RESTful", course.getName());

The following code snippet shows the content of the XML response when a GET request for the previously uploaded Course object is sent:

下面的代码片断显示了当对先前上传的Course对象发送GET请求时的XML响应内容。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Course>
    <id>2</id>
    <name>Apache CXF Support for RESTful</name>
</Course>

5.5. DELETE Requests

5.5.DELETE请求

First, let’s try to delete a non-existent Student instance. The operation should fail and a corresponding response with Not Found (404) status is expected:

首先,让我们尝试删除一个不存在的Student实例。该操作应该是失败的,预计会有一个相应的Not Found(404)状态的响应。

HttpDelete httpDelete = new HttpDelete(BASE_URL + "1/students/3");
HttpResponse response = client.execute(httpDelete);
assertEquals(404, response.getStatusLine().getStatusCode());

In the second test case for DELETE requests, we create, execute and verify a request:

DELETE请求的第二个测试案例中,我们创建、执行并验证一个请求。

HttpDelete httpDelete = new HttpDelete(BASE_URL + "1/students/1");
HttpResponse response = client.execute(httpDelete);
assertEquals(200, response.getStatusLine().getStatusCode());

We verify new states of the web service resource with the following code snippet:

我们用下面的代码片断验证网络服务资源的新状态。

Course course = getCourse(1);
assertEquals(1, course.getStudents().size());
assertEquals(2, course.getStudents().get(0).getId());
assertEquals("Student B", course.getStudents().get(0).getName());

Next, we list the XML response that is received after a request for the first Course object in the web service resource:

接下来,我们列出在请求网络服务资源中的第一个Course对象后收到的XML响应。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Course>
    <id>1</id>
    <name>REST with Spring</name>
    <students>
        <id>2</id>
        <name>Student B</name>
    </students>
</Course>

It is clear that the first Student has successfully been removed.

很明显,第一个Student已经被成功删除。

6. Test Execution

6.测试的执行

Section 4 described how to create and destroy a Server instance in the main method of the RestfulServer class.

第4节描述了如何在RestfulServer类的main方法中创建和销毁一个Server实例。

The last step to make the server up and running is to invoke that main method. In order to achieve that, the Exec Maven plugin is included and configured in the Maven POM file:

让服务器启动并运行的最后一步是调用该main方法。为了实现这一目标,在Maven POM文件中包含并配置了Exec Maven插件。

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <version>3.0.0<version>
    <configuration>
        <mainClass>
          com.baeldung.cxf.jaxrs.implementation.RestfulServer
        </mainClass>
    </configuration>
</plugin>

The latest version of this plugin can be found via this link.

该插件的最新版本可以通过这个链接找到。

In the process of compiling and packaging the artifact illustrated in this tutorial, the Maven Surefire plugin automatically executes all tests enclosed in classes having names starting or ending with Test. If this is the case, the plugin should be configured to exclude those tests:

在编译和打包本教程中说明的工件的过程中,Maven Surefire插件会自动执行名称以Test开头或结尾的类中包含的所有测试。如果是这种情况,应该对该插件进行配置,以排除这些测试。

<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.2</version>
    <configuration>
    <excludes>
        <exclude>**/ServiceTest</exclude>
    </excludes>
    </configuration>
</plugin>

With the above configuration, ServiceTest is excluded since it is the name of the test class. You may choose any name for that class, provided tests contained therein are not run by the Maven Surefire plugin before the server is ready for connections.

在上述配置中, ServiceTest被排除在外,因为它是测试类的名称。您可以为该类选择任何名称,只要其中包含的测试在服务器准备好接受连接之前不被Maven Surefire插件运行。

For the latest version of Maven Surefire plugin, please check here.

关于Maven Surefire插件的最新版本,请查看这里

Now you can execute the exec:java goal to start the RESTful web service server and then run the above tests using an IDE. Equivalently you may start the test by executing the command mvn -Dtest=ServiceTest test in a terminal.

现在你可以执行exec:java目标来启动RESTful Web服务服务器,然后使用IDE运行上述测试。同样地,你可以通过在终端执行mvn -Dtest=ServiceTest test命令来启动该测试。

7. Conclusion

7.结论

This tutorial illustrated the use of Apache CXF as a JAX-RS implementation. It demonstrated how the framework could be used to define resources for a RESTful web service and to create a server for publishing the service.

本教程说明了Apache CXF作为JAX-RS实现的用途。它演示了如何使用该框架来定义RESTful Web服务的资源,并创建一个用于发布服务的服务器。

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

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