Ubuntu 22.04 安装 Apache Juneau 指南
在 Ubuntu 22.04 上搭建 JDK 17 + Maven 环境,引入 Apache Juneau 9.2.0 依赖,实测 POJO 多格式序列化与 REST 微服务的编译、启动、curl 验证全流程。
Ubuntu 22.04 安装 Apache Juneau 指南
目标:Apache Juneau 是 Java 侧 POJO 序列化/反序列化与 REST 微服务框架,并非可独立安装的二进制发行版,因此本文以「集成/环境搭建」形态呈现——从零安装 JDK 17 与 Maven 3.6.3,用 Maven 引入 juneau-marshall 9.2.0 与 juneau-microservice-jetty 9.2.0 依赖,真实编译运行序列化示例与 REST 微服务并采集真实回显。已在 Ubuntu 22.04.5 LTS(x86_64)实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS,Kernel 6.8.0-138-generic,架构 x86_64(amd64)
- OpenJDK 17:17.0.20.1(2026-08-18)
- Apache Maven 3.6.3(Maven home:
/usr/share/maven) - Apache Juneau:9.2.0(当前最新稳定版),涉及构件 juneau-marshall、juneau-microservice-jetty、juneau-rest-server、juneau-rest-client
- 内嵌 Jetty:12.1.5(juneau-microservice-jetty 传递依赖)
- 示例工作目录:
/opt/juneau-demo(序列化示例)、/opt/juneau-rest(REST 微服务) - REST 服务监听端口:10000(由 demo.cfg 中
Jetty/port指定) - 微服务日志目录:
/opt/juneau-rest/logs
二、前置条件
- 已具备 root 或 sudo 权限,可执行
apt-get install、wget。 - 服务器可联网访问 Maven 中央仓库(本文实测使用阿里云 Maven 镜像
https://maven.aliyun.com/repository/central,见 4.3)。 - 防火墙/安全组放行 10000 端口(REST 接口验证用)。
三、版本选型说明
Juneau 9.x 以 jar 形式按构件提供(Maven 依赖,无独立安装包):
| 构件 | Maven 坐标(groupId : artifactId) | 本次实测版本 | 说明 |
|---|---|---|---|
| 序列化核心 | org.apache.juneau : juneau-marshall | 9.2.0 | JSON/JSON5/XML/HTML/UON/URL-ENC/OpenAPI/MsgPack/SoapXml/PlainText/CSV 全部序列化与解析器 |
| REST 服务端 | org.apache.juneau : juneau-rest-server | 9.2.0 | @Rest/@RestGet/@RestPost 注解与 RestServlet 体系 |
| REST 客户端 | org.apache.juneau : juneau-rest-client | 9.2.0 | RestClient 流式调用与响应断言 |
| Jetty 微服务 | org.apache.juneau : juneau-microservice-jetty | 9.2.0 | 可执行 REST 微服务,内嵌 Jetty 12.1.5 |
版本事实(实测 Maven Central maven-metadata.xml):
- 最新稳定版为 9.2.0(juneau-rest-server 元数据
latest/release均为 9.2.0)。 - 聚合包
juneau-all最后版本为 9.1.0,9.2.0 起不再发布——9.2.0 工程请改用juneau-marshall。 - RDF 支持构件
juneau-marshall-rdf最后版本为 9.0-B1,9.2.0 中不含 RDF 序列化(实测 9.2.0 服务端支持媒体类型列表中亦无 RDF 类型)。
选型建议:
- 只做 POJO 序列化/解析:依赖
juneau-marshall单个构件即可。 - 需要可执行 REST 微服务:直接依赖
juneau-microservice-jetty(它已传递引入 juneau-rest-server、juneau-config 与 Jetty 12)。 - 调用 Juneau REST 服务:依赖
juneau-rest-client。
序列化核心依赖解析(实测 mvn dependency:resolve 输出):
[INFO] org.apache.juneau:juneau-marshall:jar:9.2.0:compile
[INFO] BUILD SUCCESS
REST 微服务关键依赖(实测 mvn dependency:resolve 输出节选):
[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] org.apache.juneau:juneau-config:jar:9.2.0:compile
[INFO] org.eclipse.jetty:jetty-server:jar:12.1.5:compile
[INFO] org.eclipse.jetty.ee11:jetty-ee11-servlet:jar:12.1.5:compile
四、部署步骤(集成与编译运行)
4.1 安装 JDK 17
Juneau 9.2.0 官方示例按 Java 17 构建,通过 apt 安装:
apt-get update -y
apt-get install -y openjdk-17-jdk
验证安装:
java -version
真实回显:
openjdk version "17.0.20.1" 2026-08-18
OpenJDK Runtime Environment (build 17.0.20.1+1-1-22.04-Ubuntu)
OpenJDK 64-Bit Server VM (build 17.0.20.1+1-1-22.04-Ubuntu, mixed mode, sharing)
确认 JDK 目录:
ls /usr/lib/jvm/
真实回显:
default-java java-1.11.0-openjdk-amd64 java-1.17.0-openjdk-amd64 java-11-openjdk-amd64 java-17-openjdk-amd64 openjdk-17
4.2 安装 Maven
apt-get install -y maven
验证版本:
mvn -version
真实回显:
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 17.0.20.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: en_US, platform encoding: ANSI_X3.4-1968
OS name: "linux", version: "6.8.0-138-generic", arch: "amd64", family: "unix"
4.3 配置 Maven 镜像加速
下载依赖耗时明显时,配置阿里云中央仓库镜像以加速(实测节点即走该镜像):
mkdir -p /root/.m2
在 /root/.m2/settings.xml 中写入镜像配置:
<settings>
<mirrors>
<mirror>
<id>aliyun</id>
<mirrorOf>central</mirrorOf>
<name>Aliyun Maven Central Mirror</name>
<url>https://maven.aliyun.com/repository/central</url>
</mirror>
</mirrors>
</settings>
4.4 创建序列化演示工程并引入依赖
建立目录结构:
mkdir -p /opt/juneau-demo/src/main/java/com/example
创建工程描述文件 /opt/juneau-demo/pom.xml,引入 juneau-marshall:9.2.0,并用 maven-shade-plugin 打可执行 fat-jar:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>juneau-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.juneau</groupId>
<artifactId>juneau-marshall</artifactId>
<version>9.2.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.2</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.MarshallDemo</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
编写主类 /opt/juneau-demo/src/main/java/com/example/MarshallDemo.java(含 @Bean/@Swap 注解的 Pet/Owner Bean 与 12 段序列化/反序列化演示,完整源码与逐段回显见《Apache Juneau 常用 API 与示例详解》)。
4.5 解析依赖(验证真实版本)
cd /opt/juneau-demo
mvn dependency:resolve -DincludeArtifactIds=juneau-marshall
真实回显(关键行):
[INFO] --- maven-dependency-plugin:2.8:resolve (default-cli) @ juneau-demo ---
[INFO] org.apache.juneau:juneau-marshall:jar:9.2.0:compile
[INFO] BUILD SUCCESS
4.6 编译打包
cd /opt/juneau-demo
mvn package
真实回显(关键行):
[INFO] --- maven-shade-plugin:3.5.2:shade (default) @ juneau-demo ---
[INFO] Including org.apache.juneau:juneau-marshall:jar:9.2.0 in the shaded jar.
[INFO] Including org.apache.juneau:juneau-commons:jar:9.2.0 in the shaded jar.
[INFO] Including org.apache.httpcomponents:httpcore:jar:4.4.16 in the shaded jar.
[INFO] Replacing /opt/juneau-demo/target/juneau-demo-1.0.0.jar with /opt/juneau-demo/target/juneau-demo-1.0.0-shaded.jar
[INFO] BUILD SUCCESS
产物:/opt/juneau-demo/target/juneau-demo-1.0.0.jar(实测 2474590 字节)。
4.7 运行序列化示例并验证
cd /opt/juneau-demo
java -Xms256m -Xmx256m -Xmn128m -Dfile.encoding=UTF-8 -jar target/juneau-demo-1.0.0.jar
真实回显(完整):
=== [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']}
=== [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>
=== [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>
=== [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)
=== [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>
=== [8] Map/List 序列化 ===
{
"count": 2,
"names": [
"beibei",
"lele"
]
}
=== [9] @Swap 注解(NameMaskSwap) ===
{
"level": 3,
"realName": "z**"
}
=== [10] JsonParser 反序列化(回读校验) ===
回读 id=1001 name=beibei tags=['golden','young'] price=599.0 status=PENDING soldDate=Thu Jan 01 00:00:00 GMT 1970
=== [11] XmlParser 反序列化(回读校验) ===
回读 id=1001 name=beibei status=PENDING
=== [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
💡 说明:
java.util.Date被 Juneau 内置交换器默认转成1970-01-01T00:00:00形式;缩进为 Tab;第 9 段验证@Swap生效(zhangsan→z**)。
4.8 创建 REST 微服务工程
建立目录:
mkdir -p /opt/juneau-rest/src/main/java/com/example
创建 /opt/juneau-rest/pom.xml,引入 juneau-microservice-jetty:9.2.0 与 juneau-rest-client:9.2.0:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>juneau-rest</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.2</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.App</mainClass>
</transformer>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
创建入口类 /opt/juneau-rest/src/main/java/com/example/App.java:
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();
}
}
创建 REST 资源类 /opt/juneau-rest/src/main/java/com/example/RootRest.java:
package com.example;
import java.util.*;
import org.apache.juneau.http.annotation.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.servlet.*;
@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;
}
}
创建数据 Bean /opt/juneau-rest/src/main/java/com/example/Pet.java:
package com.example;
import java.util.*;
import org.apache.juneau.annotation.Bean;
@Bean(typeName="Pet", sort=true)
public class Pet {
private int id;
private String name;
private List<String> tags;
private double price;
public enum Status { AVAILABLE, PENDING, SOLD }
private Status status;
public Pet() {
}
public Pet(int id, String name, List<String> tags, double price, Status status) {
this.id = id;
this.name = name;
this.tags = tags;
this.price = price;
this.status = status;
}
public int getId() { return id; }
public String getName() { return name; }
public List<String> getTags() { return tags; }
public double getPrice() { return price; }
public Status getStatus() { return status; }
public void setId(int id) { this.id = id; }
public void setName(String name) { this.name = name; }
public void setTags(List<String> tags) { this.tags = tags; }
public void setPrice(double price) { this.price = price; }
public void setStatus(Status status) { this.status = status; }
}
创建 Jetty 配置 /opt/juneau-rest/jetty.xml(端口变量 $S{availablePort,10000},可用端口由微服务写入系统属性):
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_10_0.dtd">
<Configure id="ExampleServer" class="org.eclipse.jetty.server.Server">
<Set name="connectors">
<Array type="org.eclipse.jetty.server.Connector">
<Item>
<New class="org.eclipse.jetty.server.ServerConnector">
<Arg>
<Ref refid="ExampleServer" />
</Arg>
<Set name="port">$S{availablePort,10000}</Set>
</New>
</Item>
</Array>
</Set>
<!-- Juneau adds its servlets in this ServletContextHandler -->
<New id="context" class="org.eclipse.jetty.ee11.servlet.ServletContextHandler">
<Set name="contextPath">/</Set>
<Set name="sessionHandler">
<New class="org.eclipse.jetty.ee11.servlet.SessionHandler" />
</Set>
</New>
<!-- Save ServletContextHandler for Juneau -->
<Call name="setAttribute">
<Arg>ServletContextHandler</Arg>
<Arg><Ref refid="context" /></Arg>
</Call>
<Set name="handler">
<New id="collection" class="org.eclipse.jetty.server.handler.ContextHandlerCollection">
<Arg>
<Array type="org.eclipse.jetty.server.handler.ContextHandler">
<Item>
<New class="org.eclipse.jetty.server.handler.ContextHandler">
<!-- This is setHandler(Supplier) -->
<Set name="handler"><Ref refid="context" /></Set>
</New>
</Item>
<Item>
<New class="org.eclipse.jetty.server.handler.ContextHandler">
<New class="org.eclipse.jetty.server.handler.DefaultHandler" />
</New>
</Item>
</Array>
</Arg>
</New>
</Set>
<Set name="requestLog">
<New id="RequestLogImpl" class="org.eclipse.jetty.server.CustomRequestLog">
<!-- Param 0: org.eclipse.jetty.server.RequestLogWriter -->
<Arg>
<New class="org.eclipse.jetty.server.RequestLogWriter">
<Set name="append">false</Set>
<Set name="filename"><Property name="jetty.logs" default="$S{Logging/logDir,logs}"/>/jetty-requests.log</Set>
<Set name="filenameDateFormat">yyyy_MM_dd</Set>
<Set name="retainDays">90</Set>
<Set name="timeZone">GMT</Set>
</New>
</Arg>
<!-- Param 1: String -->
<Arg>
<Get class="org.eclipse.jetty.server.CustomRequestLog" name="EXTENDED_NCSA_FORMAT" />
</Arg>
</New>
</Set>
<Get name="ThreadPool">
<Set name="minThreads" type="int">10</Set>
<Set name="maxThreads" type="int">100</Set>
<Set name="idleTimeout" type="int">60000</Set>
<Set name="detailedDump">true</Set>
</Get>
</Configure>
创建微服务配置 /opt/juneau-rest/demo.cfg:
# =============================================================================
# Juneau 微服务演示配置文件
# =============================================================================
[Logging]
logDir = logs
logFile = juneau-rest_%G_%M-%N.log
level = INFO
[Jetty]
port = 10000
resolveVars = true
maxThreads = 100
minThreads = 10
idleTimeout = 60000
⚠️ 注意:
resolveVars = true必须保留——它控制 jetty.xml 中$S{...}变量是否被解析,缺失时启动直接失败(见七、常见问题 Q5)。
4.9 编译并启动 REST 微服务
cd /opt/juneau-rest
mvn package
真实回显(关键行):
[INFO] Building jar: /opt/juneau-rest/target/juneau-rest-1.0.0.jar
[INFO] Including org.apache.juneau:juneau-microservice-jetty:jar:9.2.0 in the shaded jar.
[INFO] Including org.apache.juneau:juneau-microservice-core:jar:9.2.0 in the shaded jar.
[INFO] Including org.apache.juneau:juneau-rest-server:jar:9.2.0 in the shaded jar.
[INFO] BUILD SUCCESS
产物:/opt/juneau-rest/target/juneau-rest-1.0.0.jar(实测 9119409 字节)。
后台启动(堆内存限制 256–512MB):
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
看到 Started oejs.ServerConnector ... {0.0.0.0:10000} 即表示服务已在 10000 端口监听。
4.10 curl 验证 REST 接口
GET /hello(带响应头):
curl -s -i http://127.0.0.1:10000/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>
💡 说明:curl 不带
Accept头时(等价*/*),Juneau 默认返回带页面包装的 HTML;想拿纯数据需显式指定Accept。
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,注意是 text/xml):
curl -s -i -H "Accept: text/xml" http://127.0.0.1:10000/pet/1001
真实回显:
HTTP/1.1 200 OK
Server: Jetty(12.1.5)
Date: Sun, 11 Oct 2026 12:44:23 GMT
Content-Type: text/xml;charset=utf-8
Transfer-Encoding: chunked
<?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 /greet 查询参数:
curl -s -H "Accept: application/json" "http://127.0.0.1:10000/greet?name=juneau"
真实回显:
"Hello, juneau!"
POST /pet(JSON 提交回显):
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"]}
运行统计端点(/stats,JSON 节选):
curl -s -H "Accept: application/json" http://127.0.0.1:10000/stats
真实回显(节选):
{"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}, ...]}
访问日志(/opt/juneau-rest/logs/jetty-requests.log)真实回显:
127.0.0.1 - - [11/Oct/2026:12:53:09 +0000] "GET /hello HTTP/1.1" 200 37 "" "curl/7.81.0"
4.11 systemd 托管(生产推荐)
⚠️ 本节为真实服务器上的常驻托管写法,要求宿主机带 systemd(如 Ubuntu 22.04 真机)。以下命令在 systemd 机器上执行,未附回显(托管方式与
nohup等效,验证以 4.10 的 curl 为准)。
创建 /etc/systemd/system/juneau-rest.service:
[Unit]
Description=Apache Juneau REST Microservice
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/juneau-rest
ExecStart=/usr/bin/java -Xms256m -Xmx512m -Dfile.encoding=UTF-8 -jar /opt/juneau-rest/target/juneau-rest-1.0.0.jar --cfg=/opt/juneau-rest/demo.cfg
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
逐条执行(每条命令仅针对一个单元):
# 重载单元定义
systemctl daemon-reload
# 设置开机自启
systemctl enable juneau-rest
# 启动服务
systemctl start juneau-rest
# 查看运行状态
systemctl is-active juneau-rest
⚠️ 若 10000 端口已被 nohup 方式启动的旧进程占用,先结束旧进程再
systemctl start juneau-rest,否则服务会落在其它可用端口(见七、常见问题 Q6)。
五、配置说明
- JVM 堆内存:限定 256–512MB。
nohup方式直接写在启动命令;systemd 方式写在ExecStart行。实测生效验证(jps -v):
8846 juneau-rest-1.0.0.jar -Xms256m -Xmx512m -Dfile.encoding=UTF-8
- 微服务配置(demo.cfg):
[Jetty]段的port(监听端口)、resolveVars(jetty.xml 变量解析开关)、maxThreads/minThreads/idleTimeout(线程池);[Logging]段的logDir/logFile/level。完整参数语义见《Apache Juneau 配置参数详解》。 - Jetty 配置(jetty.xml):放在工作目录(与启动命令的当前目录一致)或 classpath 根下,Juneau 自动加载;
$S{availablePort,10000}引用微服务挑选出的可用端口。 - 构建配置:
maven-shade-plugin打 fat-jar;fat-jar 已合并全部依赖,java -jar直接运行。 - 镜像加速:
/root/.m2/settings.xml指向阿里云中央仓库镜像(见 4.3)。 - REST 资源注解:
@Rest声明资源与标题,@RestGet/@RestPost声明方法,@Path/@Query/@Content绑定入参;详见《Apache Juneau 配置参数详解》。
六、注意事项
- Bean 必须有公共无参构造器:否则反序列化报
ParseException: Class could not be instantiated(见七、常见问题 Q4)。 - 9.2.0 的注解包名:
@Bean/@Swap在org.apache.juneau.annotation包(旧版本在org.apache.juneau根包);POST 请求体注解是@Content(旧版本为@Body,9.2.0 已无该注解)。 - XML 媒体类型是
text/xml:请求Accept: application/xml会得到 406,并在报错信息中列出全部支持类型(见七、常见问题 Q7)。 - 中文与编码:启动命令加
-Dfile.encoding=UTF-8;响应默认charset=utf-8。 juneau-all与 RDF 构件在 9.2.0 不存在:不要按旧教程引juneau-all:9.2.0或juneau-marshall-rdf:9.2.0(见七、常见问题 Q1)。- fat-jar 运行中不可被重新打包覆盖:服务运行期间执行
mvn package替换同名 jar,会让运行中的 JVM 在懒加载新类时报NoClassDefFoundError(见七、常见问题 Q3)。
七、常见问题
Q1:依赖报 Could not find artifact org.apache.juneau:juneau-all:jar:9.2.0
juneau-all 聚合包 9.2.0 起停发(实测 Maven Central 最后版本 9.1.0)。把依赖改为 juneau-marshall:9.2.0 即可,它已合并全部序列化/解析能力。
Q2:Maven 下载依赖极慢或失败
配置 4.3 的阿里云镜像;仍失败时先 mvn -U dependency:resolve 强制刷新。注意阿里云镜像对个别新构件有同步延迟,可临时改用官方仓库 https://repo1.maven.org/maven2。
Q3:运行中服务报 NoClassDefFoundError: org/apache/juneau/StringRanges
fat-jar 被运行中进程外的重新打包覆盖,导致 JVM 懒加载类时读到了不一致的 jar。规范做法:先停止进程,再 mvn package,最后启动。
Q4:反序列化报 ParseException: Class '...Pet' could not be instantiated. Reason: 'null'
Bean 缺公共无参构造器。为 Bean 补一个 public Pet() {} 构造器即可(实测该报错在补齐后消失)。
Q5:启动报 NumberFormatException: For input string: "$S{availablePort,10000}"
jetty.xml 中的 $S{...} 变量未被解析——demo.cfg 的 [Jetty] 段缺 resolveVars = true,补上后重启即可(实测报错消失)。
Q6:配置的 10000 端口被占,服务却起来了,监听在其它端口
Jetty/port 指定的端口不可用时,Juneau 会自动挑选一个可用端口并写回系统属性 availablePort(实测端口被占时监听到了 36265)。先释放端口(或改 Jetty/port),再重启;以启动日志 Started oejs.ServerConnector ... {0.0.0.0:端口} 为准。
Q7:请求 Accept: application/xml 返回 HTTP 406: Not Acceptable
Juneau 9.2.0 的 XML 媒体类型是 text/xml。把 Accept 改为 text/xml 即可;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']
Q8:访问不存在的路径返回 HTTP 500 而非 404
实测 9.2.0 中未匹配到任何方法的路径抛 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'.
按报错正文里的 NotFound 定位即可,这是 9.2.0 的真实行为。
八、卸载方式
Juneau 是依赖库,卸载即清理工程与本地缓存:
# 停止微服务(nohup 方式启动的进程)
pkill -f juneau-rest-1.0.0.jar
# systemd 托管方式则逐条执行
systemctl stop juneau-rest
systemctl disable juneau-rest
# 删除示例工程目录
rm -rf /opt/juneau-demo /opt/juneau-rest
# 清理本地 Maven 仓库中 Juneau 相关缓存
rm -rf /root/.m2/repository/org/apache/juneau
如不再需要 JDK/Maven 环境:
apt-get remove -y openjdk-17-jdk maven
九、参考链接
- Apache Juneau 官网:https://juneau.apache.org/
- Juneau 文档(_microservice-jetty Basics):https://juneau.apache.org/docs/topics/JuneauMicroserviceJettyBasics
- Maven Central:juneau-marshall(https://repo1.maven.org/maven2/org/apache/juneau/juneau-marshall/)
- Maven Central:juneau-microservice-jetty(https://repo1.maven.org/maven2/org/apache/juneau/juneau-microservice-jetty/)
- 阿里云 Maven 镜像:https://developer.aliyun.com/mirror/maven