Статьи

Использование модульной платформы Java: Apache CXF на Java 10

Прошёл почти год с тех пор, как релиз Java 9 наконец доставил Project Jigsaw в массы Это был долгий, долгий путь, но он есть, так что же изменилось? Это очень хороший вопрос, и ответ на него не очевиден и однозначен.

В общем и целом, Project Jigsaw — это разрушительное изменение, и есть много причин, почему. Хотя в основном все наши существующие приложения будут работать на Java 10 (скоро будет заменен на JDK 11 ) с минимальными изменениями или без изменений, у Project Jigsaw есть глубокие и глубокие последствия для разработчиков Java: использование модульных приложений Java Платформа способ.

С множеством потрясающих фреймворков и библиотек, несомненно, потребуется время, много времени, чтобы преобразовать их в модули Java (многие даже не смогут это сделать). Этот путь тернист, но есть определенные вещи, которые уже возможны даже сегодня. В этом довольно коротком посте мы узнаем, как использовать потрясающий проект Apache CXF для создания по настоящему модульных веб-API JAX-RS 2.1 с использованием последней версии JDK 10 .

Начиная с версии 3.2.5 , все артефакты Apache CXF имеют свои манифесты, обогащенные директивой Automatic-Module-Name . Это не делает их полноценными модулями , но это первый шаг в правильном направлении. Итак, давайте начнем …

Если вы используете Apache Maven в качестве инструмента сборки, который здесь не сильно изменился, зависимости объявляются так же, как и раньше.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<dependencies>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-frontend-jaxrs</artifactId>
        <version>3.2.5</version>
    </dependency>
 
    <dependency>
        <groupId>com.fasterxml.jackson.jaxrs</groupId>
        <artifactId>jackson-jaxrs-json-provider</artifactId>
        <version>2.9.6</version>
    </dependency>
 
    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-server</artifactId>
        <version>9.4.11.v20180605</version>
    </dependency>
 
    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-webapp</artifactId>
        <version>9.4.11.v20180605</version>
    </dependency>
</dependencies>

Упаковка uber-jar или fat-jar на самом деле не применима к модульным Java-приложениям, поэтому мы должны собирать модули сами, например, в папке target / modules .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<plugin>
    <artifactId>maven-jar-plugin</artifactId>
    <version>3.1.0</version>
    <configuration>
        <outputDirectory>${project.build.directory}/modules</outputDirectory>
    </configuration>
</plugin>
 
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>3.1.1</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>copy-dependencies</goal>
            </goals>
            <configuration>
                <outputDirectory>${project.build.directory}/modules</outputDirectory>
                <includeScope>runtime</includeScope>
            </configuration>
        </execution>
    </executions>
</plugin>

Хорошо, следующий шаг — создать module-info.java и указать там имя нашего модуля (в нашем случае com.example.cxf ) и, среди прочего , все необходимые модули, необходимые для его работоспособности.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
module com.example.cxf {
    exports com.example.rest;
     
    requires org.apache.cxf.frontend.jaxrs;
    requires org.apache.cxf.transport.http;
    requires com.fasterxml.jackson.jaxrs.json;
     
    requires transitive java.ws.rs;
     
    requires javax.servlet.api;
    requires jetty.server;
    requires jetty.servlet;
    requires jetty.util;
     
    requires java.xml.bind;
}

Как вы можете сразу заметить, org.apache.cxf.frontend.jaxrs и org.apache.cxf.transport.http приходят из дистрибутива Apache CXF ( полный список доступен в документации ), тогда как java.ws.rs — это JAX -RS 2.1 API модуль. После этого мы можем приступить к реализации наших ресурсов JAX-RS так же, как раньше.

1
2
3
4
5
6
7
8
@Path("/api/people")
public class PeopleRestService {
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Collection<Person> getAll() {
        return List.of(new Person("John", "Smith", "[email protected]"));
    }
}

Это выглядит просто, как насчет добавления некоторого острого соуса, такого как, например, отправленные сервером события ( SSE ) и RxJava ? Давайте посмотрим, насколько это просто, начиная с зависимостей.

01
02
03
04
05
06
07
08
09
10
11
<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-rs-sse</artifactId>
    <version>3.2.5</version>
</dependency>
 
<dependency>
    <groupId>io.reactivex.rxjava2</groupId>
    <artifactId>rxjava</artifactId>
    <version>2.1.14</version>
</dependency>

Кроме того, мы не должны забывать обновить наш module-info.java , добавив директиву require к этим новым модулям.

1
2
3
4
5
6
7
8
module com.example.cxf {
    ...
    requires org.apache.cxf.rs.sse;
    requires io.reactivex.rxjava2;
    requires transitive org.reactivestreams;
    ...
 
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
private SseBroadcaster broadcaster;
private Builder builder;
private PublishSubject<Person> publisher;
     
public PeopleRestService() {
    publisher = PublishSubject.create();
}
 
@Context
public void setSse(Sse sse) {
    this.broadcaster = sse.newBroadcaster();
    this.builder = sse.newEventBuilder();
         
    publisher
        .subscribeOn(Schedulers.single())
        .map(person -> createEvent(builder, person))
        .subscribe(broadcaster::broadcast);
}
 
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response add(@Context UriInfo uriInfo, Person payload) {
    publisher.onNext(payload);
         
    return Response
        .created(
            uriInfo
                .getRequestUriBuilder()
                .path(payload.getEmail())
                .build())
        .entity(payload)
        .build();
}
     
@GET
@Path("/sse")
@Produces(MediaType.SERVER_SENT_EVENTS)
public void people(@Context SseEventSink sink) {
    broadcaster.register(sink);
}

Теперь, когда мы его построим:

1
mvn clean package

И запустите его, используя путь к модулю:

1
2
3
java --add-modules java.xml.bind \
           --module-path target/modules \
           --module com.example.cxf/com.example.Starter

Мы должны быть в состоянии дать нашему JAX-RS API тест-драйв. Самый простой способ убедиться, что все работает как положено, — это перейти в Google Chrome к конечной точке SSE http: // localhost: 8686 / api / people / sse и добавить несколько случайных людей через запросы POST , используя старый скручиваемость командная строка:

1
2
3
curl -X POST http://localhost:8686/api/people \
           -d '{"email": "[email protected]", "firstName": "John", "lastName": "Smith"}' \
           -H "Content-Type: application/json"
1
2
3
curl -X POST http://localhost:8686/api/people \
           -d '{"email": "[email protected]", "firstName": "Tom", "lastName": "Tommyknocker"}' \
           -H "Content-Type: application/json"

В Google Chrome мы должны видеть необработанные события SSE , выдвигаемые сервером (они выглядят не очень красиво, но достаточно хорошо, чтобы проиллюстрировать ход).

Apache CXF

Итак, что насчет упаковки приложений? Docker и контейнеры, безусловно, являются жизнеспособным вариантом, но с Java 9 и выше у нас есть другой игрок: jlink . Он собирает и оптимизирует набор модулей и их зависимостей в пользовательский, вполне достаточный образ времени выполнения. Давайте попробуем это.

1
2
3
4
5
6
7
8
jlink --add-modules java.xml.bind,java.management \
            --module-path target/modules \
            --verbose \
            --strip-debug \
            --compress 2 \
            --no-header-files \
            --no-man-pages \
            --output target/cxf-java-10-app

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

1
2
3
4
target/cxf-java-10-app/bin/java  \
           --add-modules java.xml.bind \
           --module-path target/modules \
           --module com.example.cxf/com.example.Starter

В конце дня все оказалось не так страшно. Мы, безусловно, находимся на самой ранней стадии принятия JPMS , это только начало. Когда каждая библиотека, каждый используемый фреймворк добавляет module-info.java к своим артефактам (JAR), делая их настоящими модулями, несмотря на все причуды , тогда мы можем объявить победу. Но маленькие победы уже происходят, сделайте их своими!

Полный исходный код проекта доступен на Github .

Опубликовано на Java Code Geeks с разрешения Андрея Редько, партнера нашей программы JCG . См. Оригинальную статью здесь: Использование модульной платформы Java: Apache CXF на Java 10

Мнения, высказанные участниками Java Code Geeks, являются их собственными.