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 .