«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, но при этом использовались специфические возможности фреймворка во время выполнения.