Введение в Apache CXF

Введение в Apache CXF

1. обзор

Apache CXF - это полностью совместимая среда JAX-WS.

Помимо функций, определенных стандартами JAX-WS, Apache CXF обеспечивает возможность преобразования между классами WSDL и Java, API-интерфейсы, используемые для управления необработанными сообщениями XML, поддержку JAX-RS, интеграцию с Spring Framework и т. Д.

Это руководство является первым из серии, посвященной Apache CXF, в которой представлены основные характеристики платформы. Он использует только стандартные API-интерфейсы JAX-WS в исходном коде, но в то же время использует Apache CXF за кулисами, например, автоматически генерируемые метаданные WSDL и конфигурацию по умолчанию CXF.

2. Maven Зависимости

Ключевая зависимость, необходимая для использования Apache CXF:org.apache.cxf:[.s1]cxfrtfrontendjaxws. This provides a JAX-WS implementation to replace the built-in JDK one:


    org.apache.cxf
    cxf-rt-frontend-jaxws
    3.1.6

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, ссылаясь на реализацию, предоставленную Apache CXF.

В этом руководстве мы не используем контейнер сервлета для публикации службы, поэтому для предоставления необходимых определений типов Java требуется другая зависимость:


    org.apache.cxf
    cxf-rt-transports-http-jetty
    3.1.6

Последние версии этих зависимостей можно найти вcxf-rt-frontend-jaxws иcxf-rt-transports-http-jetty в центральном репозитории Maven.

3. Конечная точка веб-службы

Начнем с класса реализации, используемого для настройки конечной точки службы:

@WebService(endpointInterface = "com.example.cxf.introduction.example")
public class exampleImpl implements example {
    private Map students
      = new LinkedHashMap();

    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 getStudents() {
        return students;
    }
}

Самое важное, что здесь следует отметить, - это наличие атрибутаendpointInterface в аннотации@WebService. Этот атрибут указывает на интерфейс, определяющий абстрактный контракт для веб-службы.

Все сигнатуры методов, объявленные в интерфейсе конечной точки, должны быть реализованы, но это не обязательно для реализации интерфейса.

Здесь класс реализацииexampleImpl по-прежнему реализует следующий интерфейс конечной точки, чтобы было ясно, что все объявленные методы интерфейса были реализованы, но делать это необязательно:

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

    public String helloStudent(Student student);

    @XmlJavaTypeAdapter(StudentMapAdapter.class)
    public Map getStudents();
}

По умолчанию Apache CXF использует JAXB в качестве архитектуры привязки данных. Однако, поскольку JAXB не поддерживает напрямую привязкуMap, которая возвращается из методаgetStudents,we need an adapter to convert the Map to a Java class that JAXB can use.

Кроме того, чтобы отделить элементы контракта от их реализации, мы определяемStudent как интерфейс, а JAXB также не поддерживает интерфейсы напрямую, поэтому нам нужен еще один адаптер, чтобы справиться с этим. Фактически, для удобства мы можем объявитьStudent как класс. Использование этого типа в качестве интерфейса является еще одной демонстрацией использования классов адаптации.

Адаптеры показаны в разделе справа ниже.

4. Пользовательские адаптеры

В этом разделе показан способ использования классов адаптации для поддержки привязки интерфейса Java иMap с помощью JAXB.

4.1. Интерфейсный адаптер

Вот как определяется интерфейсStudent:

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

В этом интерфейсе объявляется только один метод, возвращающийString, и указываетсяStudentAdapter как класс адаптации для сопоставления себя с типом, который может применять привязку JAXB, и обратно.

КлассStudentAdapter определяется следующим образом:

public class StudentAdapter extends XmlAdapter {
    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;
    }
}

Класс адаптации должен реализовывать интерфейсXmlAdapter и обеспечивать реализацию для методовmarshal иunmarshal. Методmarshal преобразует связанный тип (Student, интерфейс, который JAXB не может обрабатывать напрямую) в тип значения (StudentImpl, конкретный класс, который может обрабатываться JAXB). Методunmarshal делает все наоборот.

Вот определение классаStudentImpl:

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

    // constructors, getter and setter
}

4.2. Map Адаптер

МетодgetStudents интерфейса конечной точкиexample возвращаетMap и указывает класс адаптации для преобразованияMap в тип, который может обрабатываться JAXB. Подобно классуStudentAdapter, этот класс адаптации должен реализовывать методыmarshal иunmarshal интерфейсаXmlAdapter:

public class StudentMapAdapter
  extends XmlAdapter> {
    public StudentMap marshal(Map boundMap) throws Exception {
        StudentMap valueMap = new StudentMap();
        for (Map.Entry 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 unmarshal(StudentMap valueMap) throws Exception {
        Map boundMap = new LinkedHashMap();
        for (StudentMap.StudentEntry studentEntry : valueMap.getEntries()) {
            boundMap.put(studentEntry.getId(), studentEntry.getStudent());
        }
        return boundMap;
    }
}

КлассStudentMapAdapter отображаетMap<Integer, Student> в тип значенияStudentMap и обратно со следующим определением:

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

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

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

        // getters and setters
    }
}

5. развертывание

5.1. Server Определение

Для развертывания описанного выше веб-сервиса мы будем использовать стандартные API-интерфейсы JAX-WS. Поскольку мы используем Apache CXF, фреймворк выполняет дополнительную работу, например, создание и публикация схемы WSDL. Вот как определяется сервисный сервер:

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

После того, как сервер активен некоторое время, чтобы облегчить тестирование, он должен быть выключен для освобождения системных ресурсов. Вы можете указать любую продолжительность работы сервера в зависимости от ваших потребностей, передав аргументlong методуThread.sleep.

5.2. РазвертываниеServer

В этом руководстве мы используем плагинorg.codehaus.mojo:[.s1]exec-maven-plugin для создания экземпляра сервера, показанного выше, и управления его жизненным циклом. Это объявлено в файле Maven POM следующим образом:


    org.codehaus.mojo
    exec-maven-plugin
    
        com.example.cxf.introduction.Server
    

КонфигурацияmainClass относится к классуServer, в котором публикуется конечная точка веб-службы. После выполнения целиjava этого плагина мы can check out the WSDL schema automatically generated by Apache CXF by accessing the URL http://localhost:8080/example?wsdl.

6. Тестовые случаи

В этом разделе описываются шаги для написания тестовых случаев, используемых для проверки веб-службы, которую мы создали ранее.

Обратите внимание, что нам нужно выполнить цельexec:java для запуска сервера веб-службы перед запуском любого теста.

6.1. подготовка

Первым шагом является объявление нескольких полей для тестового класса:

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

    private Service service;
    private example exampleProxy;
    private exampleImpl exampleImpl;

    // other declarations
}

Следующий блок инициализатора используется для инициализации поляservice типаjavax.xml.ws.Service перед запуском любого теста:

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

После добавления зависимости JUnit к файлу POM мы можем использовать аннотацию@Before, как в фрагменте кода ниже. Этот метод запускается перед каждым тестом для повторного создания экземпляров полейexample:

@Before
public void reinstantiateexampleInstances() {
    exampleImpl = new exampleImpl();
    exampleProxy = service.getPort(PORT_NAME, example.class);
}

ПеременнаяexampleProxy - это прокси для конечной точки веб-службы, аexampleImpl - это просто простой объект Java. Этот объект используется для сравнения результатов вызовов методов удаленной конечной точки через прокси с вызовами локальных методов.

Обратите внимание, что экземплярQName идентифицируется двумя частями: URI пространства имен и локальной частью. Если аргументPORT_NAME типаQName методаService.getPort опущен, Apache CXF будет считать, что URI пространства имен аргумента является именем пакета интерфейса конечной точки в обратном порядке и его локальная часть - это имя интерфейса с добавлениемPort, которое является точно таким же значением, какPORT_NAME.. Поэтому в этом руководстве мы можем опустить этот аргумент.

6.2. Тестовая реализация

Первый тестовый пример, который мы проиллюстрируем в этом подразделе, - это проверка ответа, возвращенного при удаленном вызове методаhello в конечной точке службы:

@Test
public void whenUsingHelloMethod_thenCorrect() {
    String endpointResponse = exampleProxy.hello("example");
    String localResponse = exampleImpl.hello("example");
    assertEquals(localResponse, endpointResponse);
}

Ясно, что метод удаленной конечной точки возвращает тот же ответ, что и локальный метод, что означает, что веб-служба работает, как и ожидалось.

Следующий тестовый пример демонстрирует использование методаhelloStudent:

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

В этом случае клиент отправляет объектStudent в конечную точку и получает взамен сообщение, содержащее имя студента. Как и в предыдущем тесте, ответы от удаленных и локальных вызовов одинаковы.

Последний тестовый пример, который мы покажем здесь, более сложный. Как определено классом реализации конечной точки службы, каждый раз, когда клиент вызывает методhelloStudent на конечной точке, отправленный объектStudent будет сохранен в кэше. Этот кеш можно получить, вызвав методgetStudents на конечной точке. Следующий тестовый пример подтверждает, что содержимое кешаstudents представляет собой то, что клиент отправил веб-службе:

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

    Student student2 = new StudentImpl("Eve");
    exampleProxy.helloStudent(student2);

    Map students = exampleProxy.getStudents();
    assertEquals("Adam", students.get(1).getName());
    assertEquals("Eve", students.get(2).getName());
}

7. Заключение

В этом руководстве был представлен Apache CXF, мощная среда для работы с веб-сервисами в Java. В нем основное внимание уделялось применению инфраструктуры как стандартной реализации JAX-WS, но при этом использовались определенные возможности платформы во время выполнения.

Реализацию всех этих примеров и фрагментов кода можно найти вa GitHub project.