Перейти к основному содержимому

Введение в Apache CXF

· 8 мин. чтения

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: cxf — rt — frontend — jaxws . Это обеспечивает реализацию JAX-WS для замены встроенной JDK:

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

Обратите внимание, что этот артефакт содержит файл с именем javax.xml.ws.spi.Provider внутри каталога META-INF/services . Java VM просматривает первую строку этого файла, чтобы определить, какую реализацию JAX-WS следует использовать. В этом случае содержание строки — org.apache.cxf.jaxws.spi.ProviderImpl , относящееся к реализации, предоставляемой Apache CXF.

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

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

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

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

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

@WebService(endpointInterface = "com.foreach.cxf.introduction.ForEach")
public class ForEachImpl implements ForEach {
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;
}
}

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

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

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

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

public String helloStudent(Student student);

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

По умолчанию Apache CXF использует JAXB в качестве архитектуры привязки данных. Однако, поскольку JAXB напрямую не поддерживает привязку Map , которая возвращается из метода getStudents , нам нужен адаптер для преобразования Map в класс Java, который может использовать JAXB .

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

Адаптеры демонстрируются в разделе справа ниже.

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

В этом разделе показано, как использовать классы адаптации для поддержки связывания интерфейса Java и Карты с использованием JAXB.

4.1. Адаптер интерфейса

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

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

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

Класс 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;
}
}

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

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

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

// constructors, getter and setter
}

4.2. Адаптер карты

Метод getStudents интерфейса конечной точки ForEach возвращает карту и указывает класс адаптации для преобразования карты в тип, который может обрабатываться JAXB. Подобно классу StudentAdapter , этот класс адаптации должен реализовывать методы маршалинга и демаршалирования интерфейса 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;
}
}

Класс 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. Развертывание

5.1. Определение сервера

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

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

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

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

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

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

Конфигурация mainClass относится к классу Server , в котором публикуется конечная точка веб-службы. После запуска цели java этого плагина мы можем проверить схему WSDL, автоматически сгенерированную Apache CXF, перейдя по URL-адресу http://localhost:8080/foreach?wsdl .

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

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

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

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

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

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

private Service service;
private ForEach foreachProxy;
private ForEachImpl foreachImpl;

// other declarations
}

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

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

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

@Before
public void reinstantiateForEachInstances() {
foreachImpl = new ForEachImpl();
foreachProxy = service.getPort(PORT_NAME, ForEach.class);
}

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

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

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

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

@Test
public void whenUsingHelloMethod_thenCorrect() {
String endpointResponse = foreachProxy.hello("ForEach");
String localResponse = foreachImpl.hello("ForEach");
assertEquals(localResponse, endpointResponse);
}

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

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

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

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

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

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

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

Map<Integer, Student> students = foreachProxy.getStudents();
assertEquals("Adam", students.get(1).getName());
assertEquals("Eve", students.get(2).getName());
}

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

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

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