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

最后修改: 2016年 10月 28日

1. Overview


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.


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


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实现:


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:



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



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.


3. Resource Classes and Request Mapping


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


Here is the definition of the Student resource class:


@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.


Next, comes the definition of the Course resource class:


@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–它是根资源,是网络服务资源的入口。

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.


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


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.setName("Student A");
    student2.setName("Student B");

    List<Student> course1Students = new ArrayList<>();

    Course course1 = new Course();
    Course course2 = new Course();
    course1.setName("REST with Spring");
    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.


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.


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.


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


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.


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


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


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


public Response createStudent(Student student) {
    for (Student element : students) {
        if (element.getId() == student.getId() {
            return Response.status(Response.Status.CONFLICT).build();
    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.


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)状态的响应。

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

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


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.


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:


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:


public Course pathToStudent(@PathParam("courseId") int courseId) {
    return findById(courseId);

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


4. Server Endpoint


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();

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:


  new SingletonResourceProvider(new CourseRepository()));

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



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


Server server = factoryBean.create();

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


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.


5. Test Cases


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.


5.1. Preparation


First, two static fields are declared within the test class, named 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:


public static void createClient() {
    client = HttpClients.createDefault();
public static void closeClient() throws IOException {

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


5.2. GET Requests


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


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


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:


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


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.


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


    <name>Student B</name>

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


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

The Content-Type header is set to tell the server that the content type of the request is 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:


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:


    <name>Student C</name>

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()
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:


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

5.4. PUT Requests


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:


    <name>Apache CXF Support for RESTful</name>

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:


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

The Content-Type header is set to tell the server that the content type of the request is 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:


HttpPut httpPut = new HttpPut(BASE_URL + "1");
InputStream resourceStream = this.getClass().getClassLoader()
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:


    <name>REST with Spring</name>

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:


    <name>Apache CXF Support for RESTful</name>

This is how the request is built and executed:


HttpPut httpPut = new HttpPut(BASE_URL + "2");
InputStream resourceStream = this.getClass().getClassLoader()
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:


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:


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

5.5. DELETE Requests


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:


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:


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

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


6. Test Execution


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


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插件。


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开头或结尾的类中包含的所有测试。如果是这种情况,应该对该插件进行配置,以排除这些测试。


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


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.