Eclipse MicroproFile 简介
1. 概述
在本文中,我们将专注于构建基于 Eclipse MicroProfile 的微服务。
我们将了解如何使用 JAX-RS、CDI 和 JSON-P API 编写 RESTful Web 应用程序。
2. 微服务架构
简单地说,微服务是一种软件架构风格,它由几个独立的服务组成一个完整的系统。
每个都专注于一个功能边界,并使用与语言无关的协议(例如 REST)与其他边界通信。
3. Eclipse 微配置文件
Eclipse MicroProfile 是一项旨在针对微服务架构优化 Enterprise 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>1.2</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。它简化了企业 bean 在 Web 应用程序中的使用。
现在让我们创建一个 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*注解注入到 bean BookEndpoint 中。
7.JAX-RS API
要使用 JAX-RS 创建 REST 应用程序,我们需要创建一个带有*@ApplicationPath注解的Application类和一个带有@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 端点
Endpoint类,也称为Resource类,应该定义一个资源,尽管许多相同的类型在技术上是可能的。
每个用*@Path注解的 Java 类,或者至少有一个用@Path 或 @HttpMethod*注解的方法都是一个端点。
现在,我们将创建一个公开该表示的 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* Resource 。
7.3. JAX RS JSON 媒体类型
JAX RS 支持与 REST 客户端通信的多种媒体类型,但**Eclipse MicroProfile 限制了 JSON 的使用,因为它指定了 JSOP-P API 的使用。因此,我们需要使用@Consumes(MediaType.APPLICATION_JSON)和@Produces(MediaType.APPLICATION_JSON) 来注解我们的方法。
@Consumes注解限制了接受的格式——在这个例子中,只接受 JSON 数据格式。HTTP 请求标头Content-Type应为application/json。
@Produces注解背后也有同样的想法。JAX RS 运行时应将响应编组为 JSON 格式。请求 HTTP 标头Accept应该是application/json。
8.JSON-P
JAX RS Runtime 支持开箱即用的 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 输出流,即通过创建BookMessageBodyWriter 来调用返回类型为Book的资源方法:
@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();
}
}
由于BookMessageBodyReader和BookMessageBodyWriter使用*@Provider*进行注解,它们由 JAX RS 运行时自动注册。
9. 构建和运行应用程序
MicroProfile 应用程序是可移植的,应该在任何兼容的 MicroProfile 运行时中运行。我们将解释如何在Open Liberty 中构建和运行我们的应用程序,但我们可以使用任何兼容的 Eclipse MicroProfile。
我们通过配置文件server.xml配置 Open Liberty 运行时:
<server description="OpenLiberty MicroProfile server">
<featureManager>
<feature>jaxrs-2.0</feature>
<feature>cdi-1.2</feature>
<feature>jsonp-1.0</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>
让我们将插件liberty-maven-plugin添加到我们的 pom.xml 中:
<?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>17.0.0.4</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>
上面的 exec 目标生成一个可执行的 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": "blogdemo",
"pages": 420
}
]
要获得一本书,我们请求此 URL:
curl http://localhost:9080/library/books/0001-201802
结果是 JSON:
{
"id": "0001-201802",
"isbn": "1",
"name": "Building Microservice With Eclipse MicroProfile",
"author": "blogdemo",
"pages": 420
}
现在我们将通过与 API 交互来添加一本新书:
curl
-H "Content-Type: application/json"
-X POST
-d '{"isbn": "22", "name": "Gradle in Action","author": "blogdemo","pages": 420}'
http://localhost:9080/library/books
可以看到,响应的状态是 201,表示这本书创建成功,Location是我们可以访问它的 URI:
< HTTP/1.1 201 Created
< Location: http://localhost:9080/library/books/0009-201802