Apache Geronimo 3.0.1 快速入门与集成示例
在 Geronimo 3.0.1 上 10 分钟跑通第一个 WAR 的手工构建部署全流程,覆盖 javac/jar 打包与 Maven 工程两种集成方式、模块启停与 redeploy 实测、Derby 数据源从连接池到 @Resource 注入的端到端示例(rows 8→9→10 持久化验证),以及 major.minor 52、redeploy NPE、清缓存启动失败等实测坑,全部回显实测采集。
Apache Geronimo 3.0.1 快速入门与集成示例
目标:在 Apache Geronimo 3.0.1 上从零构建第一个 WAR 并完成部署验证,覆盖 javac/jar 手工打包与 Maven 工程两种集成方式、模块启停与重部署实测、Derby 数据源从连接池到 Web 注入的端到端示例,以及高频踩坑的实测现场;全部命令与回显均已在 Ubuntu 22.04(x86_64)+ Zulu JDK 7(1.7.0_352)实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- JDK:Zulu 7.56.0.11(OpenJDK 1.7.0_352),
JAVA_HOME=/opt/zulu7.56.0.11-ca-jdk7.0.352-linux_x64 - 服务器:Apache Geronimo 3.0.1 完整包(内嵌 Tomcat 7.0.39.1),安装目录
G=/opt/geronimo-tomcat7-javaee6-3.0.1 - 默认管理员账号:
system / manager(第三节的连接口令为q1w2e3r4,原因见 3.3 节说明) - HTTP 端口:8080;deploy 工具连接端口:1099
- 示例工作目录:
/opt/war2(手工 WAR)、/opt/war-src(Maven 工程)、/opt/dbplan(数据源 plan)
JDK 基线确认:
export JAVA_HOME=/opt/zulu7.56.0.11-ca-jdk7.0.352-linux_x64
export PATH=$JAVA_HOME/bin:$PATH
java -version
openjdk version "1.7.0_352"
OpenJDK Runtime Environment (Zulu 7.56.0.11-CA-linux64) (build 1.7.0_352-b01)
OpenJDK 64-Bit Server VM (Zulu 7.56.0.11-CA-linux64) (build 24.352-b01, mixed mode)
⚠️ 注意:Geronimo 3.0.1 服务器与其命令行工具都只能在 JDK 6/7 下运行,JDK 8 直接
Main not found(失败链见 5.4 节与第 1 篇 FAQ);本文编译目标也固定 1.7,避免字节码版本超纲。
二、10 分钟最小可用
本节从空目录到浏览器可见共 4 步:写 Servlet(2 分钟)→ 编译打包(1 分钟)→ 部署(1 分钟)→ 验证(1 分钟)。
2.1 准备目录与 Servlet 源码
mkdir -p /opt/war2/src/demo /opt/war2/build/WEB-INF/classes
/opt/war2/src/demo/HelloServlet.java 全文:
package demo;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/plain;charset=UTF-8");
resp.getWriter().println("Hello from Geronimo 3.0.1");
resp.getWriter().println("serverInfo=" + getServletContext().getServerInfo());
resp.getWriter().println("pathInfo=" + req.getPathInfo() + ", remote=" + req.getRemoteAddr());
}
}
/opt/war2/web.xml 全文:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>geronimo-demo</display-name>
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>demo.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
2.2 编译与打包
用 Zulu JDK 7 编译(Servlet API 直接引用服务器仓库里的规范包,无需下载依赖):
G=/opt/geronimo-tomcat7-javaee6-3.0.1
SERVLET_JAR=$G/repository/org/apache/geronimo/specs/geronimo-servlet_3.0_spec/1.0/geronimo-servlet_3.0_spec-1.0.jar
cd /opt/war2
javac -source 1.7 -target 1.7 -cp "$SERVLET_JAR" -d build/WEB-INF/classes src/demo/HelloServlet.java
echo JAVAC_EXIT=$?
cp web.xml build/WEB-INF/web.xml
jar -cf geronimo-demo2.war -C build .
echo JAR_EXIT=$?
jar -tf geronimo-demo2.war
JAVAC_EXIT=0
JAR_EXIT=0
META-INF/
META-INF/MANIFEST.MF
WEB-INF/
WEB-INF/classes/
WEB-INF/classes/demo/
WEB-INF/classes/demo/HelloServlet.class
WEB-INF/web.xml
⚠️ 注意:
-source 1.7 -target 1.7必须显式指定。若在装有 JDK 8 的机器上省略这两个参数,javac 默认按 8 编译,部署时会被 Geronimo 拒绝(现场见 5.1 节)。
2.3 部署到服务器
cd $G
./bin/deploy --user system --password manager deploy /opt/war2/geronimo-demo2.war
Deployed default/geronimo-demo2/1792056978362/war @ /geronimo-demo2
回显里的 moduleId 三段各有含义:default 是无 Geronimo 部署 plan 时的默认 groupId,geronimo-demo2 取自 war 文件名,1792056978362 是部署实例 ID(毫秒时间戳)——同一 war 每次部署生成新实例。@ /geronimo-demo2 是自动推断的 context-root(默认等于 war 文件名)。
💡 技术拓展:想固定 groupId/artifactId/context-root,就在 war 的
WEB-INF/里放一份geronimo-web.xml(写法见 4.2 节);无 plan 的部署并非不能上线,只是模块身份由推断产生。
2.4 验证与模块启停
curl -s --max-time 10 http://localhost:8080/geronimo-demo2/hello
Hello from Geronimo 3.0.1
serverInfo=Apache Geronimo(Embedded Tomcat 7.0.39.1)
pathInfo=null, remote=127.0.0.1
serverInfo= 一行确认请求确实落在 Geronimo 内嵌的 Tomcat 7.0.39.1 上。
用 deploy 工具做模块级启停(先停后起):
./bin/deploy --user system --password manager stop default/geronimo-demo2/1792056978362/war
curl -s -o /dev/null -w "after-stop http=%{http_code}\n" --max-time 10 http://localhost:8080/geronimo-demo2/hello
Stopped default/geronimo-demo2/1792056978362/war
after-stop http=404
./bin/deploy --user system --password manager start default/geronimo-demo2/1792056978362/war
curl -s -o /dev/null -w "after-start http=%{http_code}\n" --max-time 10 http://localhost:8080/geronimo-demo2/hello
Started default/geronimo-demo2/1792056978362/war @ /geronimo-demo2
after-start http=200
至此最小闭环完成:构建 → 部署 → 访问 → 停/起模块。
三、Maven 工程集成
3.1 工程结构与 pom.xml
标准 Maven WAR 工程布局:
/opt/war-src
├── pom.xml
└── src/main/java/demo/TimeServlet.java
/opt/war-src/pom.xml 全文:
<?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>demo</groupId>
<artifactId>geronimo-demo</artifactId>
<version>1.0.0</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>geronimo-demo</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
</plugin>
</plugins>
</build>
</project>
关键点:Servlet API 用 provided 作用域(运行期由 Geronimo 提供);编译目标 1.7 与 2.2 节一致。
3.2 构建:Maven 自身要 JDK8,编译目标仍是 1.7
实测 Maven(3.9 线)自身需要 JDK 8 运行,在 Zulu 7 下直接报类版本错误:
cd /opt/war-src
JAVA_HOME=/opt/zulu7.56.0.11-ca-jdk7.0.352-linux_x64 mvn -B clean package 2>&1 | grep -m1 Unsupported
Exception in thread "main" java.lang.UnsupportedClassVersionError: com/google/inject/Module : Unsupported major.minor version 52.0
改用 JDK 8 运行 Maven(pom 里的 maven.compiler.source/target=1.7 保证产物仍是 1.7 字节码,可被 Geronimo 接受):
JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 mvn -B clean package 2>&1 | tail -4
ls -la target/geronimo-demo.war
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.429 s
[INFO] Finished at: 2026-10-15T10:13:50Z
[INFO] ------------------------------------------------------------------------
-rw-r--r-- 1 root root 2790 Oct 15 10:13 target/geronimo-demo.war
💡 技术拓展:同一台机器上「Maven 用 JDK8 跑、deploy 客户端用 Zulu7 跑」并不冲突,两者是独立进程;
JAVA_HOME在每条命令前临时指定即可,不要写死在一个终端会话里互相覆盖。
3.3 部署与验证
cd $G
./bin/deploy --user system --password q1w2e3r4 deploy /opt/war-src/target/geronimo-demo.war
Deployed default/geronimo-demo/1792060036012/war @ /geronimo-demo
⚠️ 注意:本节连接口令是
q1w2e3r4而非默认的manager——在本文实测流程中间,服务器口令已按第 5 篇第五章的加密流程改为该值(encrypt生成密文并覆盖var/security/users.properties,免重启生效)。若你按本文顺序首次操作,用默认manager即可。
该 Servlet 带 @WebServlet("/time") 注解(无需 web.xml),连续访问两次验证实例状态与计数器:
curl -s --max-time 10 http://localhost:8080/geronimo-demo/time
curl -s --max-time 10 http://localhost:8080/geronimo-demo/time
<html><head><title>Geronimo Demo</title></head><body>
<h1>Hello Apache Geronimo 3.0.1</h1>
<p>server time: Thu Oct 15 10:27:19 GMT 2026</p>
<p>request count: 1</p>
<p>free memory: 116 MB</p>
<p>total memory: 314 MB</p>
<p>servlet path: /time</p>
</body></html>
<html><head><title>Geronimo Demo</title></head><body>
<h1>Hello Apache Geronimo 3.0.1</h1>
<p>server time: Thu Oct 15 10:27:19 GMT 2026</p>
<p>request count: 2</p>
<p>free memory: 111 MB</p>
<p>total memory: 314 MB</p>
<p>servlet path: /time</p>
</body></html>
request count 从 1 递增到 2,说明单例 Servlet 的实例字段跨请求存活,应用运行正常。
3.4 更新应用:redeploy 实测
改代码重打包后用 redeploy 就地替换(moduleId 自动猜测):
./bin/deploy --user system --password manager redeploy /opt/war2/geronimo-demo2.war
No ModuleID or TargetModuleID provided. Attempting to guess based
on the content of the archive.
Unable to locate Geronimo deployment plan in archive. Calculating
default ModuleID from archive name.
Attempting to use ModuleID 'default/geronimo-demo2//'
Redeployed default/geronimo-demo2/1792056982551/war
2026-10-15 09:36:23,006 ERROR [DeployTool] Error:
org.apache.geronimo.common.DeploymentException: Operation failed: restart of default/geronimo-demo2/1792056982551/war failed
Error starting configuration gbean default/geronimo-demo2/1792056982551/war
reason: bundleContext is null in configurationData: default/geronimo-demo2/1792056982551/war
java.lang.NullPointerException: bundleContext is null in configurationData: default/geronimo-demo2/1792056982551/war
at org.apache.geronimo.kernel.config.ConfigurationData.getBundleContext(ConfigurationData.java:218)
at org.apache.geronimo.kernel.config.Configuration.<init>(Configuration.java:203)
实测结论(重要,别被 ERROR 吓住):
- 新版本会以新实例 ID(1792056982551)注册进服务器;
- 重启环节在 3.0.1 上必现
bundleContext is null空指针; - 旧实例并未被破坏——报错后 curl 应用仍返回 200 与正确内容;
- 新实例处于未启动状态留在清单里,用
list-modules核对后可start或undeploy。
生产建议:3.0.1 的应用更新优先用 undeploy + deploy 两步走,行为干净可预期;redeploy 仅在能接受上述噪音时使用。
四、数据源集成示例
4.1 部署 Derby 连接池
Geronimo 配数据源 = RAR + 部署 plan。Derby 嵌入版 RAR 已内置在仓库:
ls -1 $G/repository/org/tranql/tranql-connector-derby-embed-local/1.8/
tranql-connector-derby-embed-local-1.8.rar
连接池 plan /opt/dbplan/demo-derby-pool-plan.xml 全文(实测可部署的完整写法):
<?xml version="1.0" encoding="UTF-8"?>
<connector xmlns="http://geronimo.apache.org/xml/ns/j2ee/connector-1.2">
<dep:environment xmlns:dep="http://geronimo.apache.org/xmlns/deployment-1.2">
<dep:moduleId>
<dep:groupId>demo</dep:groupId>
<dep:artifactId>demo-derby-pool</dep:artifactId>
<dep:version>1.0</dep:version>
<dep:type>car</dep:type>
</dep:moduleId>
<dep:dependencies>
<dep:dependency>
<dep:groupId>org.tranql</dep:groupId>
<dep:artifactId>tranql-connector-derby-embed-local</dep:artifactId>
<dep:version>1.8</dep:version>
<dep:type>rar</dep:type>
</dep:dependency>
</dep:dependencies>
</dep:environment>
<resourceadapter>
<resourceadapter-instance>
<resourceadapter-name>demo-derby-pool</resourceadapter-name>
<workmanager>
<gbean-link>DefaultWorkManager</gbean-link>
</workmanager>
</resourceadapter-instance>
<outbound-resourceadapter>
<connection-definition>
<connectionfactory-interface>javax.sql.DataSource</connectionfactory-interface>
<connectiondefinition-instance>
<name>jdbc/DemoDS</name>
<config-property-setting name="DatabaseName">demo-db</config-property-setting>
<config-property-setting name="CreateDatabase">true</config-property-setting>
<config-property-setting name="UserName"></config-property-setting>
<config-property-setting name="Password"></config-property-setting>
<connectionmanager>
<single-pool>
<max-size>10</max-size>
<blocking-timeout-milliseconds>5000</blocking-timeout-milliseconds>
<idle-timeout-minutes>15</idle-timeout-minutes>
<match-one/>
</single-pool>
</connectionmanager>
</connectiondefinition-instance>
</connection-definition>
</outbound-resourceadapter>
</resourceadapter>
</connector>
三个不能写错的点(错一个就部署失败,完整六个坑的报错现场见第 5 篇第四章):根元素命名空间必须是 http://geronimo.apache.org/xml/ns/j2ee/connector-1.2;workmanager 是 schema 强制元素;DatabaseName/CreateDatabase 必须放在 connectiondefinition-instance 里。
RAR 与 plan 联合部署:
./bin/deploy --user system --password manager deploy $G/repository/org/tranql/tranql-connector-derby-embed-local/1.8/tranql-connector-derby-embed-local-1.8.rar /opt/dbplan/demo-derby-pool-plan.xml
Deployed demo/demo-derby-pool/1.0/car
./bin/deploy --user system --password manager list-modules | grep demo-derby-pool
+ demo/demo-derby-pool/1.0/car
4.2 Web 应用引用数据源
Servlet 通过 @Resource 注入连接池(/opt/dbsrc/src/demo/DbDemoServlet.java 核心部分):
@Resource(name = "jdbc/DemoDS")
private DataSource ds;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/plain;charset=UTF-8");
try {
Connection conn = ds.getConnection();
try {
Statement st = conn.createStatement();
try {
st.executeUpdate("CREATE TABLE demo_messages (id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, msg VARCHAR(100))");
} catch (Exception e) {
// 表已存在则忽略
}
st.executeUpdate("INSERT INTO demo_messages (msg) VALUES ('hello-derby')");
ResultSet rs = st.executeQuery("SELECT COUNT(*) FROM demo_messages");
rs.next();
resp.getWriter().println("datasource=" + ds.getClass().getName());
resp.getWriter().println("rows=" + rs.getInt(1));
rs.close();
st.close();
} finally {
conn.close();
}
} catch (Exception e) {
throw new ServletException(e);
}
}
web.xml 中声明引用:
<resource-ref>
<res-ref-name>jdbc/DemoDS</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
</resource-ref>
WEB-INF/geronimo-web.xml 全文(声明模块身份、依赖连接池模块、context-root 与资源映射):
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://geronimo.apache.org/xml/ns/j2ee/web/tomcat-2.0.1">
<environment xmlns:dep="http://geronimo.apache.org/xmlns/deployment-1.2">
<dep:moduleId>
<dep:groupId>demo</dep:groupId>
<dep:artifactId>dbdemo</dep:artifactId>
<dep:version>1.0</dep:version>
<dep:type>war</dep:type>
</dep:moduleId>
<dep:dependencies>
<dep:dependency>
<dep:groupId>demo</dep:groupId>
<dep:artifactId>demo-derby-pool</dep:artifactId>
<dep:version>1.0</dep:version>
<dep:type>car</dep:type>
</dep:dependency>
</dep:dependencies>
</environment>
<context-root>/dbdemo</context-root>
<resource-ref>
<ref-name>jdbc/DemoDS</ref-name>
<resource-link>jdbc/DemoDS</resource-link>
</resource-ref>
</web-app>
4.3 端到端验证与持久化
./bin/deploy --user system --password manager deploy /opt/dbwar/dbdemo.war
Deployed demo/dbdemo/1.0/war @ /dbdemo
curl -s --max-time 15 http://localhost:8080/dbdemo/db; echo
curl -s --max-time 15 http://localhost:8080/dbdemo/db; echo
datasource=org.tranql.connector.jdbc.TranqlDataSource
rows=8
datasource=org.tranql.connector.jdbc.TranqlDataSource
rows=9
datasource= 证实注入的是 TranQL 连接池代理;rows 递增说明每次请求真实插入了一行。持久化实测:把两个模块 undeploy 后再完整重部署,第三次访问:
datasource=org.tranql.connector.jdbc.TranqlDataSource
rows=10
应用模块的卸载重装不影响 Derby 数据文件($G/var/derby/demo-db),行数继续累加——数据生命周期独立于应用部署。
五、常见坑与排障
5.1 Unsupported major.minor version 52.0
用 JDK 8 的 javac 编译(未加 -source/-target 1.7)后部署,实测报错:
org.apache.geronimo.common.DeploymentException: Unable to deploy geronimo-demo.war: demo/HelloServlet : Unsupported major.minor version 52.0
52.0 即 Java 8 字节码。修正:统一用 Zulu 7 编译(javac -source 1.7 -target 1.7),或 Maven 工程 maven.compiler.source/target=1.7(见 3.1 节)。
5.2 deploy 选项写在子命令之后
./bin/deploy deploy demo.war --user system --password manager
Too many arguments
全局选项必须放在子命令之前(--user/--password 在 deploy 子命令左边)。连接类选项与凭据排障详见第 4 篇第四章。
5.3 清缓存启动导致自部署应用加载失败
OSGi 缓存损坏时可用 ./bin/geronimo start -c 清缓存重启,但实测对已部署应用有副作用:重启过程加载到自部署的 war 时直接中止启动:
Module 69/69 default/geronimo-demo/1792057808094/war Server Startup failed
org.apache.geronimo.kernel.config.LifecycleException: load of default/geronimo-demo/1792057808094/war failed
Caused by: org.osgi.framework.BundleException: The bundle "default.geronimo-demo_0.0.0.1792057808094 [380]" could not be resolved. Reason: Missing Constraint: Import-Package: org.osgi.service.prefs; version="[1.1.1,1.1.1]"
启动中止后 Tomcat 等先启动的模块仍在服务(8080 返回 403),但 1099 命名服务未激活,所有 deploy 命令报 Connection refused to host: localhost。实测处置:停服后把该模块的仓库目录整体删除(rm -rf $G/repository/default/geronimo-demo),再普通启动,随后重新部署应用。教训:-c 清缓存前先清点服务器上已部署的应用,能 undeploy 的先 undeploy。
5.4 JDK8 环境下服务器与客户端全部失效
JAVA_HOME 指向 JDK 8 时,服务器前台启动的实测输出:
Using JRE_HOME: /usr/lib/jvm/java-8-openjdk-amd64/jre
OpenJDK 64-Bit Server VM warning: ignoring option MaxPermSize=128m; support was removed in 8.0
Main not found
var/cache/ 下引导日志的根因关键行:
ERROR: Bundle org.ops4j.pax.url.wrap [3] Error starting file:bundles/org.ops4j.pax.url.wrap_1.1.2.jar (org.osgi.framework.BundleException: Activator start error in bundle org.ops4j.pax.url.wrap [3].)
java.lang.NoClassDefFoundError: com/sun/jarbuilder/JarBuilder
pax-url wrap 依赖 JDK 内部 com.sun.jarbuilder(JDK 7 提供、JDK 8 移除),bundle 激活失败导致 Main 服务未注册。deploy 客户端同样只输出四行 Using 后 Main not found。修正只有一个:一切 Geronimo 自带工具的 JAVA_HOME 指向 JDK 6/7。
5.5 模块身份的推断规则
无 plan 的 WAR:groupId 固定 default、artifactId=war 文件名、实例 ID=部署时刻毫秒时间戳、context-root=war 文件名。由此推导两条实用结论:同一 war 反复部署会积累多个实例(用带时间戳的完整 moduleId 精确操作);要固定身份就必须提供 geronimo-web.xml(4.2 节写法)。
六、参考链接
- Geronimo 3.0.1 归档下载:
https://archive.apache.org/dist/geronimo/3.0.1/ - Servlet API 规范包(服务器内置):
$G/repository/org/apache/geronimo/specs/geronimo-servlet_3.0_spec/1.0/ - Derby 连接池 RAR(服务器内置):
$G/repository/org/tranql/tranql-connector-derby-embed-local/1.8/ - Apache Geronimo 官方文档主页:
http://geronimo.apache.org/documentation.html - 同专栏系列篇:《Ubuntu 22.04 安装 Apache Geronimo 指南》《CentOS 8 安装 Apache Geronimo 指南》《Apache Geronimo 3.0.1 常用命令与部署工具详解》《Apache Geronimo 3.0.1 数据源与安全配置进阶详解》