«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.baeldung.cxf.introduction.Baeldung")
public class BaeldungImpl implements Baeldung {
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. аннотация. Этот атрибут указывает на интерфейс, определяющий абстрактный контракт для веб-службы.
Все сигнатуры методов, объявленные в интерфейсе конечной точки, должны быть реализованы, но это не обязательно для реализации интерфейса.
Здесь класс реализации BaeldungImpl по-прежнему реализует следующий интерфейс конечной точки, чтобы было ясно, что все объявленные методы интерфейса были реализованы, но делать это необязательно:
@WebService
public interface Baeldung {
public String hello(String name);
public String helloStudent(Student student);
@XmlJavaTypeAdapter(StudentMapAdapter.class)
public Map<Integer, Student> getStudents();
}
По умолчанию Apache CXF использует JAXB как его архитектура привязки данных. Однако, поскольку JAXB напрямую не поддерживает привязку карты, которая возвращается из метода getStudents, нам нужен адаптер для преобразования карты в класс Java, который может использовать JAXB.
Кроме того, чтобы отделить элементы контракта от их реализации, мы определяем Student как интерфейс, а JAXB также напрямую не поддерживает интерфейсы, поэтому нам нужен еще один адаптер, чтобы справиться с этим. Фактически, для удобства мы можем объявить Student как класс. Использование этого типа в качестве интерфейса — еще одна демонстрация использования классов адаптации.
Адаптеры демонстрируются в разделе справа ниже.
4. Пользовательские адаптеры
В этом разделе показано, как использовать классы адаптации для поддержки связывания интерфейса Java и Карты с использованием JAXB.
4.1. Интерфейсный адаптер
Вот как определяется интерфейс Student:
@XmlJavaTypeAdapter(StudentAdapter.class)
public interface Student {
public String getName();
}
Этот интерфейс объявляет только один метод, возвращающий String, и указывает 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 интерфейса конечной точки Baeldung возвращает карту и указывает класс адаптации для преобразования карты в тип, который может обрабатываться 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\u003cInteger, Student\u003e со значением типа 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 {
BaeldungImpl implementor = new BaeldungImpl();
String address = "http://localhost:8080/baeldung";
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.baeldung.cxf.introduction.Server</mainClass>
</configuration>
</plugin>
Конфигурация mainClass относится к классу Server, в котором опубликована конечная точка веб-службы. После запуска цели java этого плагина мы можем проверить схему WSDL, автоматически сгенерированную Apache CXF, перейдя по URL-адресу http://localhost:8080/baeldung?wsdl.
6. Тестовые случаи
В этом разделе описаны этапы написания тестовых случаев, используемых для проверки ранее созданного веб-сервиса.
Обратите внимание, что нам нужно выполнить цель exec:java, чтобы запустить сервер веб-службы перед запуском любого теста.
6.1. Подготовка
Первым шагом является объявление нескольких полей для тестового класса:
public class StudentTest {
private static QName SERVICE_NAME
= new QName("http://introduction.cxf.baeldung.com/", "Baeldung");
private static QName PORT_NAME
= new QName("http://introduction.cxf.baeldung.com/", "BaeldungPort");
private Service service;
private Baeldung baeldungProxy;
private BaeldungImpl baeldungImpl;
// other declarations
}
Следующий блок инициализатора используется для инициализации поля службы типа javax.xml.ws.Service перед запуском любого теста:
{
service = Service.create(SERVICE_NAME);
String endpointAddress = "http://localhost:8080/baeldung";
service.addPort(PORT_NAME, SOAPBinding.SOAP11HTTP_BINDING, endpointAddress);
}
После добавления зависимости JUnit в файл POM мы можем использовать аннотацию @Before, как показано в фрагменте кода ниже. Этот метод запускается перед каждым тестом для повторного создания экземпляров полей Baeldung:
@Before
public void reinstantiateBaeldungInstances() {
baeldungImpl = new BaeldungImpl();
baeldungProxy = service.getPort(PORT_NAME, Baeldung.class);
}
Переменная baeldungProxy — это прокси для конечной точки веб-службы, а baeldungImpl — это просто простой объект Java. Этот объект используется для сравнения результатов вызовов методов удаленной конечной точки через прокси с вызовами локальных методов.
Обратите внимание, что экземпляр QName идентифицируется двумя частями: URI пространства имен и локальной частью. Если аргумент PORT_NAME типа QName метода Service.getPort опущен, Apache CXF будет считать, что URI пространства имен аргумента — это имя пакета интерфейса конечной точки в обратном порядке, а его локальная часть — это имя интерфейса, к которому добавлен порт. , что совпадает со значением PORT_NAME. Поэтому в этом уроке мы можем опустить этот аргумент.
6.2. Реализация теста
Первый тестовый пример, который мы проиллюстрируем в этом подразделе, предназначен для проверки ответа, возвращаемого удаленным вызовом метода hello на конечной точке службы:
@Test
public void whenUsingHelloMethod_thenCorrect() {
String endpointResponse = baeldungProxy.hello("Baeldung");
String localResponse = baeldungImpl.hello("Baeldung");
assertEquals(localResponse, endpointResponse);
}
Понятно, что метод удаленной конечной точки возвращает тот же ответ, что и у локального метода, что означает, что веб-служба работает должным образом.
Следующий тестовый пример демонстрирует использование метода helloStudent:
@Test
public void whenUsingHelloStudentMethod_thenCorrect() {
Student student = new StudentImpl("John Doe");
String endpointResponse = baeldungProxy.helloStudent(student);
String localResponse = baeldungImpl.helloStudent(student);
assertEquals(localResponse, endpointResponse);
}
В этом случае клиент отправляет объект Student конечной точке и получает в ответ сообщение, содержащее имя студента. Как и в предыдущем тестовом примере, ответы как на удаленные, так и на локальные вызовы одинаковы.
Последний тестовый пример, который мы показываем здесь, более сложен. Как определено классом реализации конечной точки службы, каждый раз, когда клиент вызывает метод helloStudent в конечной точке, отправленный объект Student будет сохраняться в кэше. Этот кеш можно получить, вызвав метод getStudents на конечной точке. Следующий тестовый пример подтверждает, что содержимое кэша учащихся представляет собой то, что клиент отправил в веб-службу:
@Test
public void usingGetStudentsMethod_thenCorrect() {
Student student1 = new StudentImpl("Adam");
baeldungProxy.helloStudent(student1);
Student student2 = new StudentImpl("Eve");
baeldungProxy.helloStudent(student2);
Map<Integer, Student> students = baeldungProxy.getStudents();
assertEquals("Adam", students.get(1).getName());
assertEquals("Eve", students.get(2).getName());
}
«
7. Заключение
В этом руководстве был представлен Apache CXF, мощный фреймворк для работы с веб-сервисами на Java. Основное внимание уделялось применению фреймворка в качестве стандартной реализации JAX-WS, но при этом использовались специфические возможности фреймворка во время выполнения.