1. 概述
本教程将学习如何使用 RestExpress 构建 RESTful 微服务。
RestExpress 是一个开源 Java 框架,能快速轻松地构建 RESTful 微服务。它基于 Netty 框架,旨在减少样板代码,加速 RESTful 微服务开发。
此外,它采用插件架构,允许我们为微服务添加功能。支持缓存、安全、持久化等常见功能的插件。
2. RestExpress 原型项目
RestExpress 原型项目 提供了一组 Maven 原型,用于创建 RestExpress 项目。
目前提供三种原型:
- minimal – 包含创建 RESTful 项目所需的最少代码。包含主类、属性文件和示例 API。
- mongodb – 创建支持 MongoDB 的 RESTful 项目。在 minimal 基础上增加 MongoDB 层。
- cassandra – 类似 mongodb 原型,为 minimal 原型添加 Cassandra 层
每个原型都附带一组插件,可为微服务添加功能:
- CacheControlPlugin – 添加 Cache-Control 头支持
- CORSPlugin – 添加 CORS 支持
- MetricsPlugin – 添加 Metrics 支持
- SwaggerPlugin – 添加 Swagger 支持
- HyperExpressPlugin – 添加 HATEOAS 支持
默认仅启用 MetricsPlugin(使用 Dropwizard Metrics)。可通过添加依赖 启用其他插件,可能需要添加属性来配置和启用特定插件。
下一节将探索如何使用 mongodb 原型创建项目,并了解应用配置方式和生成代码的细节。
2.1. 使用原型创建项目
使用 mongodb 原型创建项目:
在终端中进入项目创建目录,执行 mvn archetype:generate 命令:
$ mvn archetype:generate -DarchetypeGroupId=com.strategicgains.archetype -DarchetypeArtifactId=restexpress-mongodb -DarchetypeVersion=1.18 -DgroupId=com.example -DartifactId=rest-express -Dversion=1.0-SNAPSHOT
这将创建包含示例代码和配置的项目:
原型自动创建了几个组件:
- 使用默认配置创建服务器(配置在 environment.properties 文件)
- 在 objectid 和 uuid 包中提供两套 CRUD API,每个包包含实体、控制器、服务和仓库类
- Configuration, Server, Main, 和 Routes 类 用于启动时配置服务器
下节将探索这些生成的类。
3. 生成的代码分析
探索生成的代码,重点关注主类、API 方法和数据库层,了解如何使用 RestExpress 创建简单 CRUD 应用。
3.1. 主类
主类是应用入口点,负责启动服务器和配置应用。
查看 Main 类的 main() 方法:
public static void main(String[] args) throws Exception {
Configuration config = Environment.load(args, Configuration.class);
Server server = new Server(config);
server.start().awaitShutdown();
}
代码解析:
- Environment.load() 方法 从 environment.properties 加载配置,创建 Configuration 对象
- Server 类负责启动服务器,需要 Configuration 对象设置服务器(后续分析)
- start() 启动服务器,awaitShutdown() 等待服务器关闭
3.2. 读取属性
environment.properties 文件包含应用配置。Configuration 类自动创建用于读取属性。
查看 Configuration 类的关键部分:
该类继承 Environment 类,允许 从环境读取属性。通过重写 fillValues() 方法实现:
@Override
protected void fillValues(Properties p) {
this.port = Integer.parseInt(p.getProperty(PORT_PROPERTY, String.valueOf(RestExpress.DEFAULT_PORT)));
this.baseUrl = p.getProperty(BASE_URL_PROPERTY, "http://localhost:" + String.valueOf(port));
this.executorThreadPoolSize = Integer.parseInt(p.getProperty(EXECUTOR_THREAD_POOL_SIZE, DEFAULT_EXECUTOR_THREAD_POOL_SIZE));
this.metricsSettings = new MetricsConfig(p);
MongoConfig mongo = new MongoConfig(p);
initialize(mongo);
}
代码从环境读取端口、基础 URL 和执行线程池大小并设置字段,同时创建 MetricsConfig 和 MongoConfig 对象。
下节分析 initialize() 方法。
3.3. 初始化控制器和仓库
initialize() 方法负责初始化控制器和仓库*:
private void initialize(MongoConfig mongo) {
SampleUuidEntityRepository samplesUuidRepository = new SampleUuidEntityRepository(mongo.getClient(), mongo.getDbName());
SampleUuidEntityService sampleUuidService = new SampleUuidEntityService(samplesUuidRepository);
sampleUuidController = new SampleUuidEntityController(sampleUuidService);
SampleOidEntityRepository samplesOidRepository = new SampleOidEntityRepository(mongo.getClient(), mongo.getDbName());
SampleOidEntityService sampleOidService = new SampleOidEntityService(samplesOidRepository);
sampleOidController = new SampleOidEntityController(sampleOidService);
}
代码:
- 使用 Mongo 客户端和数据库名创建 SampleUuidEntityRepository
- 使用仓库创建 SampleUuidEntityService
- 使用服务创建 SampleUuidEntityController
对 SampleOidEntityController 重复相同流程,完成 API 和数据库层初始化。
Configuration 类负责创建服务器启动时需要配置的任何对象,可在 initialize() 添加其他初始化代码。
同样,可向 environment.properties 添加属性并在 fillValues() 中读取。
也可扩展 Configuration 类实现自定义配置,此时需更新 Main 类使用自定义实现而非默认 Configuration 类。
4. RestExpress API 实现
上节分析了 Configuration 类如何初始化控制器,现在查看 SampleUuidEntityController 类了解 API 方法创建方式。
示例控制器包含 create(), read(), readAll(), update(), 和 delete() 方法的完整实现。每个方法内部调用服务类的对应方法,进而调用仓库类。
下面分析两个典型方法的工作原理。
4.1. 创建操作
查看 create() 方法:
public SampleOidEntity create(Request request, Response response) {
SampleOidEntity entity = request.getBodyAs(SampleOidEntity.class, "Resource details not provided");
SampleOidEntity saved = service.create(entity);
// 构建创建响应...
response.setResponseCreated();
// 包含 Location 头...
String locationPattern = request.getNamedUrl(HttpMethod.GET, Constants.Routes.SINGLE_OID_SAMPLE);
response.addLocationHeader(LOCATION_BUILDER.build(locationPattern, new DefaultTokenResolver()));
// 返回新创建的资源...
return saved;
}
代码执行流程:
- 读取请求体并转换为 SampleOidEntity 对象
- 调用服务类的 create() 方法传递实体对象
- 设置响应码为 201 – Created
- 向响应添加 Location 头
- 返回新创建的实体
查看服务类会发现:它执行验证并调用仓库类的 create() 方法。
SampleOidEntityRepository 继承 MongodbEntityRepository,内部使用 Mongo Java 驱动执行数据库操作:
public class SampleOidEntityRepository extends MongodbEntityRepository<SampleOidEntity> {
@SuppressWarnings("unchecked")
public SampleOidEntityRepository(MongoClient mongo, String dbName) {
super(mongo, dbName, SampleOidEntity.class);
}
}
4.2. 读取操作
查看 read() 方法:
public SampleOidEntity read(Request request, Response response) {
String id = request.getHeader(Constants.Url.SAMPLE_ID, "No resource ID supplied");
SampleOidEntity entity = service.read(Identifiers.MONGOID.parse(id));
return entity;
}
方法从请求头解析 ID,调用服务类的 read() 方法,服务类再调用仓库类的 read() 方法,仓库类从数据库检索并返回实体。
5. 服务器与路由配置
最后分析 Server 类。该类引导应用,定义路由和对应的控制器,配置服务器指标和其他插件。
5.1. 创建服务器
查看 Server 类构造函数:
public Server(Configuration config) {
this.config = config;
RestExpress.setDefaultSerializationProvider(new SerializationProvider());
Identifiers.UUID.useShortUUID(true);
this.server = new RestExpress()
.setName(SERVICE_NAME)
.setBaseUrl(config.getBaseUrl())
.setExecutorThreadCount(config.getExecutorThreadPoolSize())
.addMessageObserver(new SimpleConsoleLogMessageObserver());
Routes.define(config,server);
Relationships.define(server);
configurePlugins(config,server);
mapExceptions(server);
}
构造函数执行步骤:
- 创建 RestExpress 对象并设置名称、基础 URL、执行线程池大小和消息观察器。RestExpress 内部创建 Netty 服务器,在 Main 类调用 start() 时启动。
- 调用 Routes.define() 定义路由(下节分析)
- 定义实体关系、基于属性配置插件、映射内部异常到应用处理的异常
5.2. 路由配置
Routes.define() 方法定义路由及每个路由对应的控制器方法。
查看 SampleOidEntityController 的路由:
public static void define(Configuration config, RestExpress server) {
// 其他路由省略...
server.uri("/samples/oid/{uuid}.{format}", config.getSampleOidEntityController())
.method(HttpMethod.GET, HttpMethod.PUT, HttpMethod.DELETE)
.name(Constants.Routes.SINGLE_OID_SAMPLE);
server.uri("/samples/oid.{format}", config.getSampleOidEntityController())
.action("readAll", HttpMethod.GET)
.method(HttpMethod.POST)
.name(Constants.Routes.SAMPLE_OID_COLLECTION);
}
详细分析第一个路由定义:
- uri() 方法将模式 /samples/oid/{uuid}.{format} 映射到 SampleOidEntityController,其中 {uuid} 和 {format} 是 URL 路径参数
- **GET、PUT 和 DELETE 方法默认映射到控制器的 read(), update(), 和 delete() 方法**(Netty 服务器默认行为)
- 为路由分配名称以便按名称检索(可通过 server.getRouteUrlsByName() 实现)
第二个路由定义:
- 模式 /samples/oid.{format} 映射到 SampleOidEntityController
- action() 方法将方法名映射到 HTTP 方法:readAll() 映射到 GET
- POST 方法默认映射到控制器的 create() 方法
- 同样为路由分配名称
⚠️ 重要提示:若在控制器中定义额外方法或修改标准方法名,需使用 action() 方法映射到对应的 HTTP 方法。
其他 URL 模式需添加到 Routes.define() 方法中。
6. 运行应用
运行应用并对实体执行操作测试,使用 curl 命令执行操作。
通过运行 Main 类启动应用,应用默认在端口 8081 启动。
默认 SampleOidEntity 除 ID 和时间戳外无其他字段,添加 name 字段:
public class SampleOidEntity extends AbstractMongodbEntity implements Linkable {
private String name;
// 构造函数、getter 和 setter
}
6.1. API 测试
使用 curl 创建新实体:
$ curl -X POST -H "Content-Type: application/json" -d "{\"name\":\"test\"}" http://localhost:8081/samples/oid.json
返回带 ID 的新创建实体:
{
"_links": {
"self": {
"href": "http://localhost:8081/samples/oid/63a5d983ef1e572664c148fd"
},
"up": {
"href": "http://localhost:8081/samples/oid"
}
},
"name": "test",
"id": "63a5d983ef1e572664c148fd",
"createdAt": "2022-12-23T16:38:27.733Z",
"updatedAt": "2022-12-23T16:38:27.733Z"
}
使用返回的 id 读取创建的实体:
$ curl -X GET http://localhost:8081/samples/oid/63a5d983ef1e572664c148fd.json
返回与前相同的实体。
7. 总结
本文探讨了如何使用 RestExpress 创建 REST API:
✅ 使用 RestExpress mongodb 原型创建项目
✅ 分析项目结构和生成的类
✅ 运行应用并执行操作测试 API
本文使用的代码示例可在 GitHub 获取。