1. 概述

本文将重点介绍如何基于 Eclipse MicroProfile 构建微服务。我们将探讨如何使用 JAX-RS、CDI 和 JSON-P API 开发 RESTful Web 应用程序。

2. 微服务架构

简单来说,微服务是一种软件架构风格,它将完整系统构建为多个独立服务的集合。每个服务专注于特定功能边界,并通过语言无关协议(如 REST)与其他服务通信。

3. Eclipse MicroProfile

Eclipse MicroProfile 是一项旨在优化企业级 Java 微服务架构的倡议。它基于 Jakarta EE WebProfile API 的子集,因此我们可以像构建 Jakarta EE 应用一样构建 MicroProfile 应用。

MicroProfile 的目标是定义构建微服务的标准 API,并确保应用能在多种 MicroProfile 运行时之间可移植。

4. Maven 依赖

构建 Eclipse MicroProfile 应用所需的所有依赖都通过此 BOM(物料清单)依赖提供:

<dependency>
    <groupId>org.eclipse.microprofile</groupId>
    <artifactId>microprofile</artifactId>
    <version>6.0</version>
    <type>pom</type>
    <scope>provided</scope>
</dependency>

作用域设置为 provided 是因为 MicroProfile 运行时已包含 API 和实现。

5. 数据模型

我们先创建一个简单的资源类:

public class Book {
    private String id;
    private String name;
    private String author;
    private Integer pages;
    // ...
}

可以看到,这个 Book 类没有任何注解。

6. 使用 CDI

简单来说,CDI 是提供依赖注入和生命周期管理的 API。它简化了 Web 应用中企业级 bean 的使用。

现在创建一个 CDI 管理的 bean 作为书籍数据的存储:

@ApplicationScoped
public class BookManager {

    private ConcurrentMap<String, Book> inMemoryStore
      = new ConcurrentHashMap<>();

    public String add(Book book) {
        // ...
    }

    public Book get(String id) {
        // ...
    }

    public List getAll() {
        // ...
    }
}

我们使用 @ApplicationScoped 注解该类,因为只需要一个实例供所有客户端共享状态。为此,我们使用 ConcurrentMap 作为类型安全的内存数据存储,并添加了 CRUD 操作方法。

现在这个 bean 已准备好通过 CDI 注入,可以使用 @Inject 注解注入到 BookEndpoint 中。

7. JAX-RS API

使用 JAX-RS 创建 REST 应用需要:

  1. 一个标注 @ApplicationPathApplication
  2. 一个标注 @Path 的资源类

7.1 JAX-RS 应用

JAX-RS 应用标识了 Web 应用中暴露资源的基础 URI。

创建以下 JAX-RS 应用:

@ApplicationPath("library")
public class LibraryApplication extends Application {
}

本例中,Web 应用中的所有 JAX-RS 资源类都与 LibraryApplication 关联,使它们位于相同的 library 路径下(即 @ApplicationPath 注解的值)。

这个注解类告诉 JAX-RS 运行时自动查找并暴露资源。

7.2 JAX-RS 接口

接口类(也称为资源类)通常只定义一个资源,尽管技术上支持多个类型。

任何标注 @Path 或至少有一个方法标注 @Path@HttpMethod 的 Java 类都是接口。

现在创建暴露书籍资源的 JAX-RS 接口:

@Path("books")
@RequestScoped
public class BookEndpoint {

    @Inject
    private BookManager bookManager;
 
    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getBook(@PathParam("id") String id) {
        return Response.ok(bookManager.get(id)).build();
    }
 
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getAllBooks() {
        return Response.ok(bookManager.getAll()).build();
    }
 
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response add(Book book) {
        String bookId = bookManager.add(book);
        return Response.created(
          UriBuilder.fromResource(this.getClass())
            .path(bookId).build())
            .build();
    }
}

此时,我们可以通过 Web 应用中的 /library/books 路径访问 BookEndpoint 资源。

7.3 JAX-RS JSON 媒体类型

JAX-RS 支持多种与 REST 客户端通信的媒体类型,但 Eclipse MicroProfile 限制使用 JSON,因为它指定使用 JSON-P API。因此,我们需要用 @Consumes(MediaType.APPLICATION_JSON)@Produces(MediaType.APPLICATION_JSON) 注解方法。

@Consumes 注解限制接受的格式——本例中只接受 JSON 数据格式。HTTP 请求头 Content-Type 应为 application/json

@Produces 注解同理:JAX-RS 运行时应将响应编组为 JSON 格式。请求头 Accept 应为 application/json

8. JSON-P

JAX-RS 运行时原生支持 JSON-P,因此我们可以直接使用 JsonObject 作为方法输入参数或返回类型。

但在实际开发中,我们通常使用 POJO 类。因此需要一种方式在 JsonObject 和 POJO 之间转换。这时 JAX-RS 实体提供者就派上用场了。

要将 JSON 输入流编组为 Book POJO(即调用参数类型为 Book 的资源方法),需创建 BookMessageBodyReader 类:

@Provider
@Consumes(MediaType.APPLICATION_JSON)
public class BookMessageBodyReader implements MessageBodyReader<Book> {

    @Override
    public boolean isReadable(
      Class<?> type, Type genericType, 
      Annotation[] annotations, 
      MediaType mediaType) {
 
        return type.equals(Book.class);
    }

    @Override
    public Book readFrom(
      Class type, Type genericType, 
      Annotation[] annotations,
      MediaType mediaType, 
      MultivaluedMap<String, String> httpHeaders, 
      InputStream entityStream) throws IOException, WebApplicationException {
 
        return BookMapper.map(entityStream);
    }
}

同样,要将 Book 解组为 JSON 输出流(即调用返回类型为 Book 的资源方法),需创建 BookMessageBodyWriter

@Provider
@Produces(MediaType.APPLICATION_JSON)
public class BookMessageBodyWriter 
  implements MessageBodyWriter<Book> {
 
    @Override
    public boolean isWriteable(
      Class<?> type, Type genericType, 
      Annotation[] annotations, 
      MediaType mediaType) {
 
        return type.equals(Book.class);
    }
 
    // ...
 
    @Override
    public void writeTo(
      Book book, Class<?> type, 
      Type genericType, 
      Annotation[] annotations, 
      MediaType mediaType, 
      MultivaluedMap<String, Object> httpHeaders, 
      OutputStream entityStream) throws IOException, WebApplicationException {
 
        JsonWriter jsonWriter = Json.createWriter(entityStream);
        JsonObject jsonObject = BookMapper.map(book);
        jsonWriter.writeObject(jsonObject);
        jsonWriter.close();
    }
}

由于 BookMessageBodyReaderBookMessageBodyWriter 都标注了 @Provider,它们会被 JAX-RS 运行时自动注册。

9. 构建和运行应用

MicroProfile 应用是可移植的,应在任何兼容的 MicroProfile 运行时中运行。我们将演示如何在 Open Liberty 中构建和运行应用,但实际可使用任何兼容的 Eclipse MicroProfile 实现。

通过配置文件 server.xml 配置 Open Liberty 运行时:

<server description="OpenLiberty MicroProfile server">
    <featureManager>
        <feature>jackartaee-10</feature>
        <feature>cdi-4.0</feature>
        <feature>jsonp-2.1</feature>
    </featureManager>
    <httpEndpoint httpPort="${default.http.port}" httpsPort="${default.https.port}"
      id="defaultHttpEndpoint" host="*"/>
    <applicationManager autoExpand="true"/>
    <webApplication context-root="${app.context.root}" location="${app.location}"/>
</server>

在 pom.xml 中添加 liberty-maven-plugin 插件:

<?xml version="1.0" encoding="UTF-8"?>
<plugin>
    <groupId>net.wasdev.wlp.maven.plugins</groupId>
    <artifactId>liberty-maven-plugin</artifactId>
    <version>2.1.2</version>
    <configuration>
        <assemblyArtifact>
            <groupId>io.openliberty</groupId>
            <artifactId>openliberty-runtime</artifactId>
            <version>23.0.0.5</version>
            <type>zip</type>
        </assemblyArtifact>
        <configFile>${basedir}/src/main/liberty/config/server.xml</configFile>
        <packageFile>${package.file}</packageFile>
        <include>${packaging.type}</include>
        <looseApplication>false</looseApplication>
        <installAppPackages>project</installAppPackages>
        <bootstrapProperties>
            <app.context.root>/</app.context.root>
            <app.location>${project.artifactId}-${project.version}.war</app.location>
            <default.http.port>9080</default.http.port>
            <default.https.port>9443</default.https.port>
        </bootstrapProperties>
    </configuration>
    <executions>
        <execution>
            <id>install-server</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>install-server</goal>
                <goal>create-server</goal>
                <goal>install-feature</goal>
            </goals>
        </execution>
        <execution>
            <id>package-server-with-apps</id>
            <phase>package</phase>
            <goals>
                <goal>install-apps</goal>
                <goal>package-server</goal>
            </goals>
        </execution>
    </executions>
</plugin>

该插件可通过以下属性配置:

<properties>
    <!--...-->
    <app.name>library</app.name>
    <package.file>${project.build.directory}/${app.name}-service.jar</package.file>
    <packaging.type>runnable</packaging.type>
</properties>

上述执行目标会生成可执行 JAR 文件,使应用成为独立的微服务,可单独部署运行。也可将其部署为 Docker 镜像。

创建可执行 JAR 的命令:

mvn package

运行微服务的命令:

java -jar target/library-service.jar

这将启动 Open Liberty 运行时并部署服务。可通过以下 URL 访问接口获取所有书籍:

curl http://localhost:9080/library/books

返回 JSON 数据:

[
  {
    "id": "0001-201802",
    "isbn": "1",
    "name": "Building Microservice With Eclipse MicroProfile",
    "author": "baeldung",
    "pages": 420
  }
]

获取单本书籍的 URL:

curl http://localhost:9080/library/books/0001-201802

返回 JSON 数据:

{
    "id": "0001-201802",
    "isbn": "1",
    "name": "Building Microservice With Eclipse MicroProfile",
    "author": "baeldung",
    "pages": 420
}

现在通过 API 添加新书:

curl 
  -H "Content-Type: application/json" 
  -X POST 
  -d '{"isbn": "22", "name": "Gradle in Action","author": "baeldung","pages": 420}' 
  http://localhost:9080/library/books

响应状态码为 201,表示书籍创建成功,Location 头包含访问 URI:

< HTTP/1.1 201 Created
< Location: http://localhost:9080/library/books/0009-201802

10. 总结

本文演示了如何基于 Eclipse MicroProfile 构建简单的微服务,重点讨论了 JAX-RS、JSON-P 和 CDI 的使用。

代码可在 GitHub 获取;这是一个基于 Maven 的项目,可以直接导入运行。


原始标题:Building Microservices with Eclipse MicroProfile