leftso 637 0 2018-08-27 22:44:10

文章位置:左搜> 编程技术> 正文

 

自Java 9发布以来,大部分时间已经过去了一年,最终将Project Jigsaw交付给了大众。这是漫长而漫长的旅程,但它在那里,所以发生了什么变化?这是一个非常好的问题,答案并不明显和直截了当。

总的来说,Project Jigsaw是一个颠覆性的变化,原因有很多。尽管我们现有的所有应用程序都将在Java 10上运行(很快将被JDK 11取代),但只需极少或根本没有变化,Project Jigsaw为Java开发人员带来了深刻而深远的影响:将模块化应用程序包含在Java中平台方式。

有了无数令人敬畏的框架和库,将它们转换为Java模块肯定需要花费很多时间(很多时候都不会成功)。这条道路是棘手的,但即使在今天,某些事情已经成为可能。在这篇相当短的文章中,我们将学习如何使用极好的Apache CXF项目,使用最新的JDK 10以真正的模块化方式构建JAX-RS 2.1 Web API。

自3.2.5发布以来,所有Apache CXF工件的清单都使用Automatic-Module-Name指令。它不会使它们成为完整的模块,但这是朝着正确方向迈出的第一步。那么让我们开始......

如果你使用Apache Maven作为选择的构建工具,这里没有太大的改变,依赖关系的声明方式和以前一样。
$title(pom.xml)
<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文件夹中。
<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),以及其他所需的所有模块,以便可以运行。
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资源。
@Path("/api/people")
public class PeopleRestService {
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Collection<Person> getAll() {
        return List.of(new Person("John", "Smith", "john.smith@somewhere.com"));
    }
}
这看起来很容易,如何添加一些难度?,例如服务器发送事件(SSE)和RxJava? 让我们看看它是多么容易,从依赖开始。
<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>
此外,我们不应忘记通过向这些新模块添加requires指令来更新我们的module-info.java。
module com.example.cxf {
    ...
    requires org.apache.cxf.rs.sse;
    requires io.reactivex.rxjava2;
    requires transitive org.reactivestreams;
    ...

}
为了简单起见,我们的SSE端点只会广播通过API添加的每个新人。 这是实现它的实现代码片段。
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);
}
现在我们构建它:
mvn clean package
并使用模块路径运行它:
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请求添加一些随机人员,使用来自的旧伙伴卷曲 命令行:
curl -X POST http://localhost:8686/api/people \
           -d '{"email": "john@smith.com", "firstName": "John", "lastName": "Smith"}' \
           -H "Content-Type: application/json"
 
curl -X POST http://localhost:8686/api/people \
           -d '{"email": "tom@tommyknocker.com", "firstName": "Tom", "lastName": "Tommyknocker"}' \
           -H "Content-Type: application/json"
在谷歌浏览器中,我们应该能够看到由服务器推送的原始SSE事件(它们看起来不是很漂亮但足以说明流程)。
服务器推送的原始SSE事件
服务器推送的原始SSE事件


那么,应用程序包装呢? Docker和容器当然是一个可行的选择,但是对于Java 9及更高版本,我们还有另一个玩家:jlink。 它将一组模块及其依赖项汇编并优化为一个自定义的,完全足够的运行时映像。 让我们一试吧。
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是一个问题,我们仍然需要在从运行时映像运行时显式包含模块路径:
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)中,尽管存在所有怪癖,使它们成为真正的模块,然后我们可以声明胜利。 但小胜已经发生,让你的一个!