1. 概述

本文将介绍 Armeria —— 一个用于高效构建微服务的灵活框架。我们将探讨它的核心功能、应用场景以及使用方法。

简单来说,Armeria 提供了便捷的方式构建支持多种协议的微服务客户端和服务器,包括 REST、gRPCThriftGraphQL。此外,它还深度集成了多种技术栈:

✅ 服务发现:支持 ConsulEurekaZookeeper
✅ 分布式追踪:集成 Zipkin
✅ 框架整合:兼容 Spring Boot、DropwizardRESTEasy

2. 依赖配置

使用 Armeria 前需引入依赖,当前最新版本为 1.29.2

核心依赖位于 com.linecorp.armeria:armeria,Maven 配置如下:

<dependency>
    <groupId>com.linecorp.armeria</groupId>
    <artifactId>armeria</artifactId>
    <version>1.29.2</version>
</dependency>

根据具体需求,还可添加其他集成依赖(如 gRPC、GraphQL 等)。

2.1. 使用 BOM 管理依赖

Armeria 依赖众多,推荐使用 Maven BOM 统一管理版本:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.linecorp.armeria</groupId>
            <artifactId>armeria-bom</artifactId>
            <version>1.29.2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

配置后,引入依赖时无需指定版本:

<dependency>
    <groupId>com.linecorp.armeria</groupId>
    <artifactId>armeria</artifactId>
</dependency>

⚠️ 单依赖时优势不明显,但项目规模扩大后能显著简化版本管理。

3. 启动服务器

配置好依赖后,我们先从 HTTP 服务器入手。Armeria 通过 ServerBuilder 配置服务器,构建后启动:

ServerBuilder sb = Server.builder();
sb.service("/handler", (ctx, req) -> HttpResponse.of("Hello, world!"));

Server server = sb.build();
CompletableFuture<Void> future = server.start();
future.join();

这段代码会启动一个监听随机端口的服务器,并注册一个硬编码处理器。 启动日志会显示监听地址:

07:36:46.508 [main] INFO com.linecorp.armeria.common.Flags -- verboseExceptions: rate-limit=10 (default)
07:36:46.957 [main] INFO com.linecorp.armeria.common.Flags -- useEpoll: false (default)
07:36:46.971 [main] INFO com.linecorp.armeria.common.Flags -- annotatedServiceExceptionVerbosity: unhandled (default)
07:36:47.262 [main] INFO com.linecorp.armeria.common.Flags -- Using Tls engine: OpenSSL BoringSSL, 0x1010107f
07:36:47.321 [main] INFO com.linecorp.armeria.common.util.SystemInfo -- hostname: k5mdq05n (from 'hostname' command)
07:36:47.399 [armeria-boss-http-*:49167] INFO com.linecorp.armeria.server.Server -- Serving HTTP at /[0:0:0:0:0:0:0:0%0]:49167 - http://127.0.0.1:49167/

3.1. 服务器配置

启动前可通过多种方式配置服务器:

最常用的是指定监听端口,否则会随机选择可用端口:

ServerBuilder sb = Server.builder();
sb.http(8080);  // HTTP 端口

若需 HTTPS,需先配置 TLS 证书。Armeria 提供自签名证书生成工具:

ServerBuilder sb = Server.builder();
sb.tlsSelfSigned();  // 自动生成自签名证书
sb.https(8443);      // HTTPS 端口

3.2. 添加访问日志

默认不记录请求日志(通常由负载均衡器处理)。如需启用,通过 ServerBuilder.accessLogWriter() 配置:

// Apache Common Log 格式
sb.accessLogWriter(AccessLogWriter.common(), true);
// Apache Combined Log 格式
sb.accessLogWriter(AccessLogWriter.combined(), true);

日志通过 SLF4J 输出,示例:

07:25:16.481 [armeria-common-worker-kqueue-3-2] INFO com.linecorp.armeria.logging.access -- 0:0:0:0:0:0:0:1%0 - - 17/Jul/2024:07:25:16 +0100 "GET /#EmptyServer$$Lambda/0x0000007001193b60 h1c" 200 13
07:28:37.332 [armeria-common-worker-kqueue-3-3] INFO com.linecorp.armeria.logging.access -- 0:0:0:0:0:0:0:1%0 - - 17/Jul/2024:07:28:37 +0100 "GET /unknown#FallbackService h1c" 404 35

4. 添加服务处理器

服务器需注册处理器才能处理请求。 Armeria 原生支持 HTTP 处理器,gRPC/Thrift/GraphQL 需额外依赖。

4.1. 简单处理器

最基础的方式是使用 ServerBuilder.service() 注册处理器:

sb.service("/handler", handler);

HttpService 是 SAM 接口,可直接用 lambda 实现:

sb.service("/handler", (ctx, req) -> HttpResponse.of("Hello, world!"));

处理器需实现 HttpResponse HttpService.serve(ServiceRequestContext, HttpRequest) 方法,参数提供请求上下文和内容,返回响应对象。

4.2. URL 模式

Armeria 支持多种 URL 匹配模式:

  • 精确匹配/handler
  • 路径参数
    sb.service("/curly/{name}", (ctx, req) -> HttpResponse.of("Hello, " + ctx.pathParam("name")));
    sb.service("/colon/:name", (ctx, req) -> HttpResponse.of("Hello, " + ctx.pathParam("name")));
    
  • Glob 匹配(需 glob: 前缀):
    sb.service("glob:/base/*/glob/**", 
      (ctx, req) -> HttpResponse.of("Hello, " + ctx.pathParam("0") + ", " + ctx.pathParam("1")));
    
    匹配 /base/a/glob/base/a/glob/b 等,* 匹配单段,** 匹配多段。
  • 正则表达式(需 regex: 前缀):
    sb.service("regex:^/regex/[A-Za-z]+/[0-9]+$",
      (ctx, req) -> HttpResponse.of("Hello, " + ctx.path()));
    
    // 命名捕获组
    sb.service("regex:^/named-regex/(?<name>[A-Z][a-z]+)$",
      (ctx, req) -> HttpResponse.of("Hello, " + ctx.pathParam("name")));
    

4.3. 精细化路由配置

通过 ServerBuilder.route() 可精确匹配 HTTP 方法、头信息等:

sb.route()
  .methods(HttpMethod.GET)
  .path("/get")
  .produces(MediaType.PLAIN_TEXT)
  .matchesParams("name")
  .build((ctx, req) -> HttpResponse.of("Hello, " + ctx.path()));

仅匹配:

  • GET 方法
  • 接受 text/plain 响应
  • 包含 name 查询参数

不匹配时自动返回标准错误码(如 405 Method Not Allowed)。

5. 注解式处理器

Armeria 支持通过注解自动映射方法到处理器,简化复杂服务开发。

使用 ServerBuilder.annotatedService() 注册:

sb.annotatedService(new AnnotatedHandler());

类中方法用 @Get/@Post/@Put/@Delete 等注解标记:

@Get("/handler")
public String handler() {
    return "Hello, World!";
}

方法签名更灵活,参数自动映射,返回值自动转换。

5.1. 处理器参数

以下类型参数自动注入:

  • ServiceRequestContextHttpRequestRequestHeadersQueryParamsCookies
@Get("/handler")
public String handler(ServiceRequestContext ctx) {
    return "Hello, " + ctx.path();
}

@Param 注解自动注入路径/查询参数:

@Get("/handler/{name}")
public String handler(@Param String name) {
    return "Hello, " + name;
}

⚠️ 默认参数必填,可通过 Optional<>@Nullable@Default 设为可选。

5.2. 请求体处理

支持多种请求体接收方式:

  • 原始字节:byte[]HttpData

    @Post("/byte-body")
    public String byteBody(byte[] body) {
        return "Length: " + body.length;
    }
    
  • 解码文本:StringCharSequence

    @Post("/string-body")
    public String stringBody(String body) {
        return "Hello: " + body;
    }
    
  • JSON 反序列化(需 Jackson)

    @Post("/json-body")
    public String jsonBody(JsonBody body) {
        return body.name + " = " + body.score;
    }
    
    record JsonBody(String name, int score) {}
    

自定义请求转换器: 实现 RequestConverterFunction 接口

public class UppercasingRequestConverter implements RequestConverterFunction {
    @Override
    public Object convertRequest(ServiceRequestContext ctx, AggregatedHttpRequest request,
        Class<?> expectedResultType, ParameterizedType expectedParameterizedResultType)
        throws Exception {

        if (expectedResultType.isAssignableFrom(String.class)) {
            return request.content(StandardCharsets.UTF_8).toUpperCase();
        }

        return RequestConverterFunction.fallthrough();
    }
}

通过 @RequestConverter 启用:

@Post("/uppercase-body")
@RequestConverter(UppercasingRequestConverter.class)
public String uppercaseBody(String body) {
    return "Hello: " + body;
}

5.3. 响应处理

返回值自动转换为 HTTP 响应:

  • null → HTTP 204 No Content
  • byte[]/HttpDataapplication/octet-stream
  • CharSequence(含 String)→ text/plain (UTF-8)
  • JsonNodeapplication/json

JSON 响应(需 @ProducesJson):

@Get("/json-response")
@ProducesJson
public JsonBody jsonResponse() {
    return new JsonBody("Baeldung", 42);
}

自定义响应转换器: 实现 ResponseConverterFunction

public class UppercasingResponseConverter implements ResponseConverterFunction {
    @Override
    public HttpResponse convertResponse(ServiceRequestContext ctx, ResponseHeaders headers,
        @Nullable Object result, HttpHeaders trailers) {
        if (result instanceof String) {
            return HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8,
              ((String) result).toUpperCase(), trailers);
        }

        return ResponseConverterFunction.fallthrough();
    }
}

通过 @ResponseConverter 启用:

@Post("/uppercase-response")
@ResponseConverter(UppercasingResponseConverter.class)
public String uppercaseResponse(String body) {
    return "Hello: " + body;
}

5.4. 异常处理

默认异常映射:

  • IllegalArgumentException → HTTP 400 Bad Request
  • HttpStatusException/HttpResponseException → 对应 HTTP 状态码
  • 其他异常 → HTTP 500 Internal Server Error

自定义异常处理器: 实现 ExceptionHandlerFunction

public class ConflictExceptionHandler implements ExceptionHandlerFunction {
    @Override
    public HttpResponse handleException(ServiceRequestContext ctx, HttpRequest req, Throwable cause) {
        if (cause instanceof IllegalStateException) {
            return HttpResponse.of(HttpStatus.CONFLICT);
        }

        return ExceptionHandlerFunction.fallthrough();
    }
}

通过 @ExceptionHandler 启用:

@Get("/exception")
@ExceptionHandler(ConflictExceptionHandler.class)
public String exception() {
    throw new IllegalStateException();
}

6. GraphQL 支持

需添加依赖:

<dependency>
    <groupId>com.linecorp.armeria</groupId>
    <artifactId>armeria-graphql</artifactId>
</dependency>

使用 GraphqlService 暴露 GraphQL 接口:

sb.service("/graphql",
  GraphqlService.builder().graphql(buildSchema()).build());

buildSchema() 返回 GraphQL Java 库的 GraphQL 实例。

7. 客户端调用

Armeria 提供 WebClient 调用 HTTP 服务:

WebClient webClient = WebClient.of();
AggregatedHttpResponse response = webClient.get("http://localhost:8080/handler")
  .aggregate()
  .join();

WebClient.get() 发起 GET 请求,返回流式响应。 aggregate() 将响应聚合为 AggregatedHttpResponse

访问响应内容:

System.out.println(response.status());
System.out.println(response.headers());
System.out.println(response.content().toStringUtf8());

指定基础 URL:

WebClient webClient = WebClient.of("http://localhost:8080");
AggregatedHttpResponse response = webClient.get("/handler")
  .aggregate()
  .join();

POST 请求示例:

WebClient webClient = WebClient.of();
AggregatedHttpResponse response = webClient.post("http://localhost:8080/uppercase-body", "baeldung")
  .aggregate()
  .join();

7.1. 复杂请求

底层通过 execute() 方法实现:

WebClient webClient = WebClient.of("http://localhost:8080");

HttpRequest request = HttpRequest.of(
  RequestHeaders.builder()
    .method(HttpMethod.POST)
    .path("/uppercase-body")
    .contentType(MediaType.PLAIN_TEXT_UTF_8)  // 类型安全替代 add()
    .build(),
  HttpData.ofUtf8("Baeldung"));
AggregatedHttpResponse response = webClient.execute(request)
  .aggregate()
  .join();

7.2. 客户端配置

通过 ClientFactory 配置底层参数:

ClientFactory clientFactory = ClientFactory.builder()
  .connectTimeout(Duration.ofSeconds(10))  // 连接超时
  .idleTimeout(Duration.ofSeconds(60))    // 空闲超时
  .build();
WebClient webClient = WebClient.builder("http://localhost:8080")
  .factory(clientFactory)
  .build();

8. 总结

本文简要介绍了 Armeria 的核心功能。这个框架远不止于此,建议动手实践探索更多特性!


原始标题:Introduction to Armeria | Baeldung