Apache Juneau 常用 API 与示例详解
按 API 分节详解 Apache Juneau 9.2.0 的 JsonSerializer/JsonParser/XmlSerializer/HtmlSerializer/UonSerializer/@Bean/@Swap/RestServlet/RestClient/JettyMicroservice 用法,全部示例真实运行并附逐行回显。
Apache Juneau 常用 API 与示例详解
目标:按 API 分节梳理 Apache Juneau 9.2.0 的常用入口——序列化/解析器(JsonSerializer、JsonParser、XmlSerializer、HtmlSerializer、UonSerializer、UrlEncodingSerializer、SoapXmlSerializer)、Bean 注解(@Bean、@Swap)、REST 服务端(@Rest、BasicRestServlet、@RestGet/@RestPost)、REST 客户端(RestClient)与微服务入口(JettyMicroservice)。所有示例均真实编译运行,回显逐行照抄实测结果(环境:Ubuntu 22.04.5 LTS + JDK 17.0.20.1;REST 演示服务监听 10000 端口)。
一、API 概览
| API | 所在构件 | 用途 | 9.2.0 关键变化 |
|---|---|---|---|
| JsonSerializer / JsonParser | juneau-marshall | POJO ↔ JSON | Builder 移除 simple();@Bean 注解迁至 org.apache.juneau.annotation 包 |
| XmlSerializer / XmlParser | juneau-marshall | POJO ↔ XML | 媒体类型为 text/xml |
| HtmlSerializer | juneau-marshall | POJO → HTML 表格 | 服务端默认 */* 返回 HTML 页面 |
| UonSerializer / UrlEncodingSerializer | juneau-marshall | POJO → UON / 表单编码 | — |
| SoapXmlSerializer | juneau-marshall | POJO → SOAP Envelope | — |
| @Bean / @Swap | juneau-marshall | Bean 定制 | properties 为 String;@Body 更名 @Content(rest 系) |
| RestServlet / @Rest | juneau-rest-server | REST 服务端 | @Rest 在 org.apache.juneau.rest.annotation 包 |
| RestClient | juneau-rest-client | REST 客户端 | 需 .json() 注册解析器才能 as() 反序列化 |
| JettyMicroservice | juneau-microservice-jetty | 可执行微服务 | 内嵌 Jetty 12.1.5 |
⚠️ 版本事实:9.2.0 起聚合包
juneau-all停发(最后 9.1.0),RDF 构件juneau-marshall-rdf停更(最后 9.0-B1),本文全部示例使用juneau-marshall/juneau-rest-server/juneau-rest-client/juneau-microservice-jetty的 9.2.0。
二、环境准备与依赖引入
Maven 坐标(全部实测解析成功):
<dependency>
<groupId>org.apache.juneau</groupId>
<artifactId>juneau-marshall</artifactId>
<version>9.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.juneau</groupId>
<artifactId>juneau-microservice-jetty</artifactId>
<version>9.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.juneau</groupId>
<artifactId>juneau-rest-client</artifactId>
<version>9.2.0</version>
</dependency>
依赖解析真实回显:
[INFO] org.apache.juneau:juneau-marshall:jar:9.2.0:compile
[INFO] org.apache.juneau:juneau-microservice-jetty:jar:9.2.0:compile
[INFO] org.apache.juneau:juneau-microservice-core:jar:9.2.0:compile
[INFO] org.apache.juneau:juneau-rest-server:jar:9.2.0:compile
[INFO] BUILD SUCCESS
本文 3–6 节的示例主类 MarshallDemo 放在 /opt/juneau-demo(pom 与编译运行步骤见《Ubuntu 22.04 安装 Apache Juneau 指南》);7–9 节的 REST 工程放在 /opt/juneau-rest。Bean 基类 Pet:
@Bean(typeName="Pet", sort=true)
public static class Pet {
private int id;
private String name;
private List<String> tags;
private double price;
private Status status;
private Date soldDate;
public enum Status { AVAILABLE, PENDING, SOLD }
public Pet() {
}
public Pet(int id, String name, List<String> tags, double price, Status status, Date soldDate) {
this.id = id;
this.name = name;
this.tags = tags;
this.price = price;
this.status = status;
this.soldDate = soldDate;
}
// 省略全部 getter/setter: getId/setName/getTags/getPrice/getStatus/getSoldDate 及对应 setter
}
⚠️ Bean 必须带公共无参构造器,否则解析报
ParseException: Class could not be instantiated(实测)。
三、JsonSerializer 与 JsonParser
3.1 JsonSerializer 构造与序列化
// 可读格式(缩进换行)
JsonSerializer j1 = JsonSerializer.create().ws().build();
// 紧凑 + 单引号
JsonSerializer j2 = JsonSerializer.create().sq().build();
// 输出 _type 字典类型
JsonSerializer jt = JsonSerializer.create().ws().addRootType().build();
Pet pet = new Pet(1001, "beibei", Arrays.asList("golden", "young"), 599.0,
Pet.Status.PENDING, new Date(0L));
String json = j1.serialize(pet);
实测回显:
=== [1] JsonSerializer 可读格式(ws) ===
{
"id": 1001,
"name": "beibei",
"price": 599.0,
"soldDate": "1970-01-01T00:00:00",
"status": "PENDING",
"tags": [
"golden",
"young"
]
}
=== [2] JsonSerializer 紧凑格式 + 单引号(sq) ===
{'id':1001,'name':'beibei','price':599.0,'soldDate':'1970-01-01T00:00:00','status':'PENDING','tags':['golden','young']}
Map/List 也能直接序列化:
=== [8] Map/List 序列化 ===
{
"count": 2,
"names": [
"beibei",
"lele"
]
}
3.2 JsonParser 反序列化(回读校验)
JsonParser parser = JsonParser.create().build();
String json = j1.serialize(pet);
Pet back = parser.parse(json, Pet.class);
System.out.println("回读 id=" + back.getId() + " name=" + back.getName()
+ " tags=" + back.getTags() + " price=" + back.getPrice()
+ " status=" + back.getStatus() + " soldDate=" + back.getSoldDate());
实测回显:
=== [10] JsonParser 反序列化(回读校验) ===
回读 id=1001 name=beibei tags=['golden','young'] price=599.0 status=PENDING soldDate=Thu Jan 01 00:00:00 GMT 1970
未知属性默认严格报错,宽容解析用 ignoreUnknownBeanProperties():
=== [1] 默认 parser 遇到未知属性 ===
异常: Unknown property 'unknown' encountered while trying to parse into class 'com.example.ConfigDemo$Pet'
At: line 1, column 19
=== [2] ignoreUnknownBeanProperties(true) ===
com.example.ConfigDemo$Pet@10a035a0
3.3 _type 字典类型
@Bean(typeName="Pet") + addRootType() 输出 _type,解析时按 _type 自动还原:
=== [12] _type 字典类型(@Bean typeName + addRootType) ===
{
"_type": "Pet",
"id": 1001,
"name": "beibei",
"price": 599.0,
"soldDate": "1970-01-01T00:00:00",
"status": "PENDING",
"tags": [
"golden",
"young"
]
}
按 _type 还原: id=1001 name=beibei
四、XML、SOAP 与 HTML 序列化/解析
4.1 XmlSerializer / XmlParser
XmlSerializer x1 = XmlSerializer.create().ns().ws().build();
XmlParser xp = XmlParser.create().build();
String xml = x1.serialize(pet);
Pet back2 = xp.parse(xml, Pet.class);
实测回显:
=== [3] XmlSerializer 命名空间格式 ===
<Pet>
<id>1001</id>
<name>beibei</name>
<price>599.0</price>
<soldDate>1970-01-01T00:00:00</soldDate>
<status>PENDING</status>
<tags>
<string>golden</string>
<string>young</string>
</tags>
</Pet>
=== [11] XmlParser 反序列化(回读校验) ===
回读 id=1001 name=beibei status=PENDING
4.2 SoapXmlSerializer
=== [7] SoapXmlSerializer ===
<?xml version="1.0" encoding="UTF-8"?><Envelope soap="http://www.w3.org/2003/05/soap-envelope"><Body><Pet><id>1001</id><name>beibei</name><price>599.0</price><soldDate>1970-01-01T00:00:00</soldDate><status>PENDING</status><tags><string>golden</string><string>young</string></tags></Pet></Body></Envelope>
4.3 HtmlSerializer
=== [4] HtmlSerializer ===
<table>
<tr>
<td>id</td>
<td>1001</td>
</tr>
<tr>
<td>name</td>
<td>beibei</td>
</tr>
<tr>
<td>price</td>
<td>599.0</td>
</tr>
<tr>
<td>soldDate</td>
<td>1970-01-01T00:00:00</td>
</tr>
<tr>
<td>status</td>
<td>PENDING</td>
</tr>
<tr>
<td>tags</td>
<td>
<ul>
<li>golden</li>
<li>young</li>
</ul>
</td>
</tr>
</table>
五、UON 与 URL-ENC 序列化
UonSerializer u1 = UonSerializer.create().build();
UrlEncodingSerializer ue1 = UrlEncodingSerializer.create().build();
实测回显:
=== [5] UonSerializer ===
(id=1001,name=beibei,price=599.0,soldDate=1970-01-01T00:00:00,status=PENDING,tags=@(golden,young))
=== [6] UrlEncodingSerializer ===
id=1001&name=beibei&price=599.0&soldDate=1970-01-01T00:00:00&status=PENDING&tags=@(golden,young)
六、Bean 注解:@Bean 与 @Swap
/** 自定义 Swap:姓名首字母保留、其余打码 */
public static class NameMaskSwap extends StringSwap<String> {
@Override
public String swap(BeanSession session, String o) {
return o.charAt(0) + "**";
}
}
@Bean(typeName="Owner")
public static class Owner {
@Swap(NameMaskSwap.class)
public String realName = "zhangsan";
public int level = 3;
}
实测回显:
=== [9] @Swap 注解(NameMaskSwap) ===
{
"level": 3,
"realName": "z**"
}
@Bean 常用参数(9.2.0 实测):typeName(字典类型)、sort(字母序输出)、properties(String 白名单,如 properties="id")。属性白名单实测:
=== [3] @Bean(properties="id") 限定序列化属性 ===
{"id":7}
⚠️ 9.2.0 的
@Bean/@Swap在org.apache.juneau.annotation包;properties是 String 类型而非数组。
💡 以上 3–6 节全部输出来自
/opt/juneau-demo工程MarshallDemo的一次连续运行(12 段),命令:java -Xms256m -Xmx256m -Xmn128m -Dfile.encoding=UTF-8 -jar target/juneau-demo-1.0.0.jar。
七、RestServlet REST 服务端 API
7.1 @Rest 资源声明与方法注解
@Rest(
title="Juneau 宠物商店示例服务",
path="/",
description="基于 juneau-microservice-jetty 的 REST 演示服务"
)
public class RootRest extends BasicRestServlet {
@RestGet(path="/hello", summary="返回问候字符串")
public String hello() {
return "Hello from Apache Juneau 9.2.0 REST";
}
@RestGet(path="/pet/{id}", summary="按 ID 查询宠物")
public Pet getPet(@Path("id") int id) {
return new Pet(id, "beibei", Arrays.asList("golden", "young"), 599.0, Pet.Status.PENDING);
}
@RestGet(path="/pets", summary="查询全部宠物")
public List<Pet> getPets() {
return Arrays.asList(
new Pet(1001, "beibei", Arrays.asList("golden", "young"), 599.0, Pet.Status.PENDING),
new Pet(1002, "lele", Arrays.asList("corgi"), 899.0, Pet.Status.AVAILABLE));
}
@RestGet(path="/greet", summary="查询参数问候")
public String greet(@Query(name="name", def="stranger") String name) {
return "Hello, " + name + "!";
}
@RestPost(path="/pet", summary="提交宠物信息并回显")
public Pet echoPet(@Content Pet pet) {
return pet;
}
}
要点:
BasicRestServlet默认挂载全部序列化器/解析器(支持类型见 7.3 的 406 回显);- 请求体注解是
@Content(9.2.0,旧版@Body已删除); @Query的def提供默认值。
7.2 curl 实测
| 请求 | 命令要点 | 真实回显 |
|---|---|---|
| GET /hello(默认 Accept) | curl -s -i http://127.0.0.1:10000/hello | HTTP/1.1 200 OK;Content-Type: text/html;charset=utf-8;正文为 HTML 页面包装,<string>Hello from Apache Juneau 9.2.0 REST</string> |
| GET /pet/1001(JSON) | curl -s -H "Accept: application/json" http://127.0.0.1:10000/pet/1001 | {"id":1001,"name":"beibei","price":599.0,"status":"PENDING","tags":["golden","young"]} |
| GET /pet/1001(XML) | curl -s -H "Accept: text/xml" http://127.0.0.1:10000/pet/1001 | <?xml version="1.0" encoding="UTF-8"?><Pet><id>1001</id><name>beibei</name><price>599.0</price><status>PENDING</status><tags><string>golden</string><string>young</string></tags></Pet> |
| GET /pet/1001(UON) | curl -s -H "Accept: text/uon" http://127.0.0.1:10000/pet/1001 | (id=1001,name=beibei,price=599.0,status=PENDING,tags=@(golden,young)) |
| GET /greet | curl -s -H "Accept: application/json" "http://127.0.0.1:10000/greet?name=juneau" | "Hello, juneau!" |
| GET /pets | curl -s -H "Accept: application/json" http://127.0.0.1:10000/pets | [{"id":1001,"name":"beibei","price":599.0,"status":"PENDING","tags":["golden","young"]},{"id":1002,"name":"lele","price":899.0,"status":"AVAILABLE","tags":["corgi"]}] |
| POST /pet | curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -d '{"id":2002,"name":"huanhuan","tags":["husky"],"price":1299.0,"status":"AVAILABLE"}' http://127.0.0.1:10000/pet | {"id":2002,"name":"huanhuan","price":1299.0,"status":"AVAILABLE","tags":["husky"]} |
GET /hello 完整响应头与页面骨架(真实回显):
HTTP/1.1 200 OK
Server: Jetty(12.1.5)
Date: Sun, 11 Oct 2026 12:43:54 GMT
Content-Type: text/html;charset=utf-8
Transfer-Encoding: chunked
<html><head><style>@import "/htdocs/themes/devops.css";div.data * {white-space:nowrap;} </style><script></script></head><body><header><h1>Juneau 宠物商店示例服务</h1> <h2>返回问候字符串</h2></header><nav><ol><li><a href="/">up</a></li><li><a href="/api">api</a></li><li><a href="/stats">stats</a></li></ol></nav><section><article><div class="outerdata"><div class="data" id="data"><string>Hello from Apache Juneau 9.2.0 REST</string></div></div></article></section></body></html>
7.3 内置端点:stats 与 api
BasicRestServlet 自带运行统计与 OpenAPI 文档端点:
### GET /stats(Accept: application/json,节选)
{"startTime":"2026-10-11T12:43:29.331681534Z","upTime":"0h:00m:54s","methodStats":[{"avgTime":0,"errors":0,"guid":3916378536949704445,"maxTime":0,"method":"public void org.apache.juneau.rest.servlet.BasicRestServlet.error()","minTime":0,"running":0,"runs":0,"totalTime":0},{"avgTime":0,"errors":0,"guid":-5856031140523421306,"maxTime":0,"method":"public com.example.Pet com.example.RootRest.getPet(int)","minTime":0,"running":0,"runs":5,"totalTime":0}, ...]}
### GET /api(Accept: application/json,节选)
{"openapi":"3.0.1","info":{"title":"Juneau 宠物商店示例服务","version":"9.2.0","description":"基于 juneau-microservice-jetty 的 REST 演示服务"},"paths":{"/hello":{"get":{"summary":"返回问候字符串", ...}}}}
7.4 错误响应实测
不支持媒体类型(406,正文回显支持清单):
HTTP 406: Not Acceptable
Unsupported media-type in request header 'Accept': 'application/xml'
Supported media-types: ['text/html','text/html+stripped','application/json','text/json','application/json5','text/json5','application/json+schema','text/json+schema','text/xml','text/uon','application/x-www-form-urlencoded','text/openapi','octal/msgpack','text/xml+soap','text/plain','text/csv']
未知路径(实测 9.2.0 返回 500 包裹 NotFound):
HTTP 500: Internal Server Error
jakarta.servlet.ServletException: Invalid method response: 200
Caused by (ServletException): Invalid method response: 200
Caused by (NotFound): Java method matching path '/notexist' not found on resource 'com.example.RootRest'.
八、RestClient REST 客户端 API
8.1 构造与调用
RestClient client = RestClient.create().json().build();
try {
// 1. GET 取 JSON 字符串
RestResponse r1 = client.get("http://127.0.0.1:10000/pet/1001")
.accept("application/json").run();
System.out.println("状态码=" + r1.getStatusCode()
+ " Content-Type=" + r1.getContentType().map(Object::toString).orElse(""));
System.out.println(r1.getContent().asString());
// 2. GET 响应直接反序列化为 Pet
RestResponse r2 = client.get("http://127.0.0.1:10000/pet/1001")
.accept("application/json").run();
Pet p = r2.getContent().as(Pet.class);
System.out.println("id=" + p.getId() + " name=" + p.getName()
+ " tags=" + p.getTags() + " status=" + p.getStatus());
// 3. POST 提交 POJO(自动序列化)
Pet newPet = new Pet(2003, "duoduo", Arrays.asList("border collie"), 1099.0, Pet.Status.PENDING);
RestResponse r3 = client.post("http://127.0.0.1:10000/pet", newPet)
.accept("application/json").run();
System.out.println("状态码=" + r3.getStatusCode());
System.out.println(r3.getContent().asString());
// 4. Accept: text/xml 内容协商
RestResponse r4 = client.get("http://127.0.0.1:10000/pet/1001")
.accept("text/xml").run();
System.out.println("状态码=" + r4.getStatusCode()
+ " Content-Type=" + r4.getContentType().map(Object::toString).orElse(""));
System.out.println(r4.getContent().asString());
// 5. 断言式校验
RestResponse r5 = client.get("http://127.0.0.1:10000/hello")
.accept("application/json").run()
.assertStatus(200)
.assertContent("\"Hello from Apache Juneau 9.2.0 REST\"");
System.out.println("断言通过, 状态码=" + r5.getStatusCode());
} finally {
client.close();
}
8.2 实测回显(完整)
=== [1] GET /pet/1001 取 JSON 字符串 ===
状态码=200 Content-Type=application/json
{"id":1001,"name":"beibei","price":599.0,"status":"PENDING","tags":["golden","young"]}
=== [2] GET 响应直接反序列化为 Pet ===
id=1001 name=beibei tags=[golden, young] status=PENDING
=== [3] POST 提交 POJO(自动序列化) ===
状态码=200
{"id":2003,"name":"duoduo","price":1099.0,"status":"PENDING","tags":["border collie"]}
=== [4] Accept: text/xml 内容协商 ===
状态码=200 Content-Type=text/xml;charset=utf-8
<?xml version="1.0" encoding="UTF-8"?><Pet><id>1001</id><name>beibei</name><price>599.0</price><status>PENDING</status><tags><string>golden</string><string>young</string></tags></Pet>
=== [5] 断言式校验(assertStatus/assertContent) ===
断言通过, 状态码=200
⚠️ 实测要点:
RestClient.create()不带.json()时,getContent().as(Pet.class)报Unsupported media-type in request header 'Content-Type': 'application/json'——Builder 必须注册对应解析器;- POST 传 POJO 时按默认 JSON 序列化发送,服务端
@Content Pet自动反序列化;assertContent比较响应原文,JSON 化的字符串带引号。
九、JettyMicroservice 微服务 API
9.1 入口类
package com.example;
import org.apache.juneau.microservice.jetty.*;
public class App {
public static void main(String[] args) throws Exception {
JettyMicroservice
.create()
.args(args)
.servlet(RootRest.class)
.build()
.start()
.join();
}
}
args(args)透传命令行参数(支持--cfg=/path/demo.cfg);servlet(RootRest.class)挂载 REST 资源;start().join()启动并阻塞主线程。
9.2 启动命令与日志
cd /opt/juneau-rest
nohup java -Xms256m -Xmx512m -Dfile.encoding=UTF-8 \
-jar target/juneau-rest-1.0.0.jar --cfg=/opt/juneau-rest/demo.cfg > /tmp/rest-run.log 2>&1 &
启动日志真实回显(完整):
2026-10-11 13:00:06.463:INFO :oejs.Server:main: jetty-12.1.5; built: 2025-12-03T22:18:24.732Z; git: 4905d09d7b6801e792d7b73946cf7b66e4be25d6; jvm 17.0.20.1+1-1-22.04-Ubuntu
2026-10-11 13:00:06.495:INFO :oejs.DefaultSessionIdManager:main: Session workerName=node0
2026-10-11 13:00:06.508:INFO :oejsh.ContextHandler:main: Started oeje11s.ServletContextHandler@1115ec15{ROOT,/,b=null,a=AVAILABLE,h=oeje11s.SessionHandler@82ea68c{STARTED}}
2026-10-11 13:00:07.044:INFO :oeje11s.ServletContextHandler:main: Started oeje11s.ServletContextHandler@1115ec15{ROOT,/,b=null,a=AVAILABLE,h=oeje11s.SessionHandler@82ea68c{STARTED}}
2026-10-11 13:00:07.044:INFO :oejsh.ContextHandler:main: Started oejsh.ContextHandler@677dbd89{ROOT,/,b=null,a=AVAILABLE,h=oeje11s.ServletContextHandler@1115ec15{ROOT,/,b=null,a=AVAILABLE,h=oeje11s.SessionHandler@82ea68c{STARTED}}}
2026-10-11 13:00:07.044:INFO :oejsh.ContextHandler:main: Started oejsh.ContextHandler@fff25f1{ROOT,/,b=null,a=AVAILABLE}
2026-10-11 13:00:07.047:INFO :oejs.RequestLogWriter:main: Opened /opt/juneau-rest/logs/jetty-requests.log
2026-10-11 13:00:07.056:INFO :oejs.AbstractConnector:main: Started oejs.ServerConnector@5a4bef8{HTTP/1.1, (http/1.1)}{0.0.0.0:10000}
2026-10-11 13:00:07.057:INFO :oejs.Server:main: Started oejs.Server@40bffbca{STARTING}[12.1.5,sto=0] @596ms
9.3 jetty.xml 与 demo.cfg
jetty.xml(端口变量)与 demo.cfg([Jetty] port/resolveVars/线程池、[Logging] 日志)完整内容见《Ubuntu 22.04 安装 Apache Juneau 指南》4.8,参数语义见《Apache Juneau 配置参数详解》五。
十、注意事项
- Bean 无参构造器:所有需反序列化的 Bean 必须保留公共无参构造器,否则
ParseException。 - 注解包名:9.2.0 的
@Bean/@Swap在org.apache.juneau.annotation;REST 注解在org.apache.juneau.rest.annotation;请求体用@Content而非旧版@Body。 - 媒体类型:XML 是
text/xml;application/xml会 406;curl 不带 Accept 默认返回 HTML 页面。 - RestClient 需注册解析器:
RestClient.create().json().build()后才能as()反序列化。 - 字符串断言带引号:
assertContent比较原文,JSON 响应中的字符串被引号包裹。 - 构件版本:9.2.0 没有
juneau-all与juneau-marshall-rdf,依赖写错直接解析失败。 - 运行中 jar 不可被覆盖:服务运行期间重新
mvn package会触发NoClassDefFoundError,先停服务再打包。
十一、常见问题(FAQ)
Q1:parse() 报 Class could not be instantiated?
Bean 缺无参构造器,补 public Pet() {}(实测补齐后消失)。
Q2:getContent().as(...) 报 Unsupported media-type?
RestClient 未注册解析器,构造时加 .json()(或对应格式方法)。
Q3:XML 请求 406?
Accept 改为 text/xml(支持清单见 7.4 的 406 回显)。
Q4:返回值 String 在 JSON 响应里带引号,怎么拿纯文本?
Accept: text/plain 走 PlainText 序列化器(对象仍输出 JSON 风格文本,字符串方法返回原文)。
Q5:微服务端口不对?
检查 demo.cfg [Jetty]/port 与 resolveVars = true;端口被占会自动回落(见配置参数详解 5.1)。
Q6:如何看服务端 OpenAPI 文档?
GET /api 返回 OpenAPI 3.0.1 JSON(见 7.3);HTML 页面导航栏亦有 /api、/stats 链接。
十二、参考链接
- Apache Juneau 官网:https://juneau.apache.org/
- Juneau 文档( REST):https://juneau.apache.org/docs/topics/
- Maven Central:juneau-marshall 9.2.0(https://repo1.maven.org/maven2/org/apache/juneau/juneau-marshall/9.2.0/)
- Maven Central:juneau-rest-client 9.2.0(https://repo1.maven.org/maven2/org/apache/juneau/juneau-rest-client/9.2.0/)
- 同专栏:《Ubuntu 22.04 安装 Apache Juneau 指南》《Apache Juneau 配置参数详解》