Apache OpenWebBeans 4.1.1 构建并运行示例应用指南
Apache OpenWebBeans 4.1.1 构建并运行示例应用指南(CDI SE 最小应用):从零创建 Maven 工程、完整 pom.xml 与 beans.xml、@ApplicationScoped/@Dependent/@Inject/事件观察示例源码、编译运行命令与真实回显(DI 生效、作用域代理、事件回显),含 JVM 堆限制验证与直接 jar 运行方式。
Apache OpenWebBeans 4.1.1 构建并运行示例应用指南
目标:从零构建并运行一个基于 Apache OpenWebBeans 4.1.1 的最小 CDI SE 应用。给出完整 pom.xml、beans.xml、示例 Java 源码(@ApplicationScoped/@Dependent/@Inject/事件观察者)、编译与运行命令及真实回显:依赖注入生效(call #1/#2/#3 计数递增)、@ApplicationScoped 作用域代理(
$$OwbNormalScopeProxy0)、事件回显([Observer] received event)。所有命令与输出均在 Ubuntu 22.04.5 + JDK 21 环境实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS
- Java:OpenJDK 21.0.12.1
- 构建工具:Apache Maven 3.6.3
- OpenWebBeans 版本:4.1.1(依赖 openwebbeans-se / openwebbeans-impl / openwebbeans-spi)
- CDI API:jakarta.enterprise.cdi-api 4.1.0 + jakarta.inject-api 2.0.1
- 示例工程目录:
/root/owb-demo/ - 演示账号:root
二、前置条件
已按《Ubuntu 22.04 安装 Apache OpenWebBeans 4.1.1 指南》安装 JDK 21 与 Maven,并完成环境变量切换:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
java -version
- Java 版本回显需为 21(
openjdk version "21.0.12.1" 2026-08-18)。
三、工程结构
标准 Maven 布局,一个 pom、一个 beans.xml 与五个 Java 类:
owb-demo/
├── pom.xml
└── src/main/
├── java/demo/
│ ├── OwbDemo.java # 主类:启动 SE 容器
│ ├── GreetingService.java # @ApplicationScoped 服务
│ ├── UserService.java # @Dependent,@Inject 注入 GreetingService
│ ├── GreetingEvent.java # 事件类
│ └── GreetingObserver.java # @Observes 事件观察者
└── resources/META-INF/
└── beans.xml # CDI 归档描述(bean-discovery-mode="all")
mkdir -p /root/owb-demo/src/main/java/demo /root/owb-demo/src/main/resources/META-INF
四、完整 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>owb-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<owb.version>4.1.1</owb.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-se</artifactId>
<version>${owb.version}</version>
</dependency>
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
<version>2.0.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<mainClass>demo.OwbDemo</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
💡 说明:
exec-maven-plugin用于直接运行主类(mvn exec:java),mainClass指向demo.OwbDemo。
五、beans.xml 与示例源码
5.1 beans.xml(CDI 归档描述)
放在 src/main/resources/META-INF/beans.xml,bean-discovery-mode="all" 表示扫描该归档内所有带 Bean 定义注解的类:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd"
version="4.0" bean-discovery-mode="all">
</beans>
5.2 GreetingService.java(@ApplicationScoped)
应用作用域 Bean:容器内全局单例,内部计数器跨调用持久(用于验证作用域):
package demo;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.concurrent.atomic.AtomicInteger;
@ApplicationScoped
public class GreetingService {
private final AtomicInteger counter = new AtomicInteger(0);
public String greet(String name) {
return "Hello, " + name + "! (call #" + counter.incrementAndGet() + ")";
}
}
5.3 UserService.java(@Dependent + @Inject)
依赖作用域 Bean,通过 @Inject 注入 GreetingService(用于验证依赖注入):
package demo;
import jakarta.enterprise.context.Dependent;
import jakarta.inject.Inject;
@Dependent
public class UserService {
@Inject
private GreetingService greetingService;
public String welcome(String user) {
return greetingService.greet(user);
}
}
5.4 GreetingEvent.java(事件类)
package demo;
public class GreetingEvent {
private final String message;
public GreetingEvent(String message) { this.message = message; }
public String getMessage() { return message; }
}
5.5 GreetingObserver.java(事件观察者)
用 @Observes 观察 GreetingEvent,容器收到事件时回调(用于验证 CDI 事件机制):
package demo;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
@ApplicationScoped
public class GreetingObserver {
public void onGreeting(@Observes GreetingEvent event) {
System.out.println("[Observer] received event: " + event.getMessage());
}
}
5.6 OwbDemo.java(主类)
通过 SeContainerInitializer.newInstance().initialize() 启动 SE 容器,演示查 Bean、依赖注入、作用域与事件回显:
package demo;
import jakarta.enterprise.inject.se.SeContainer;
import jakarta.enterprise.inject.se.SeContainerInitializer;
public class OwbDemo {
public static void main(String[] args) {
try (SeContainer container = SeContainerInitializer.newInstance().initialize()) {
System.out.println("CDI container started: " + container.isRunning());
UserService userService = container.select(UserService.class).get();
System.out.println(userService.welcome("Apache OpenWebBeans"));
System.out.println(userService.welcome("World"));
GreetingService greetingService = container.select(GreetingService.class).get();
System.out.println("GreetingService instance: " + greetingService.getClass().getName());
System.out.println(greetingService.greet("injected-demo"));
container.getBeanManager().getEvent().select(GreetingEvent.class).fire(new GreetingEvent("hello-from-fireEvent"));
}
System.out.println("CDI container stopped.");
}
}
💡 CDI 4.x API 适配要点:
- 容器启动/关闭用标准 CDI SE API:
SeContainerInitializer+SeContainer(try-with-resources 自动 close)。BeanManager.fireEvent(...)在 CDI 4.0 已移除,改从container.getBeanManager().getEvent().select(GreetingEvent.class).fire(...)发事件。
六、编译与运行
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
cd /root/owb-demo
# 编译
mvn clean compile
# 运行
mvn -q exec:java
限制示例进程堆内存(SE 容器与代理类驻留 JVM 堆,按规范约束在 256–512MB):
export MAVEN_OPTS="-Xms256m -Xmx512m"
mvn -q exec:java
验证生效堆内存(运行期间另开终端执行):
ps -ef | grep [j]ava | grep -oE "\-Xm[sx][0-9]+m" | sort -u
在 MAVEN_OPTS="-Xms256m -Xmx512m" 下实测回显:
-Xms256m
-Xmx512m
⚠️ 注意:
mvn exec:java在主 Maven 进程内执行主类,故堆参数由MAVEN_OPTS注入;若改用java -jar运行,直接把-Xms256m -Xmx512m写在java命令行即可(见第八节直接 jar 运行)。
七、运行结果解读
编译成功([INFO] BUILD SUCCESS),程序真实输出(退出码 0):
Oct 11, 2026 8:44:57 PM org.apache.webbeans.lifecycle.AbstractLifeCycle bootstrapApplication
INFO: OpenWebBeans Container is starting...
Oct 11, 2026 8:44:57 PM org.apache.webbeans.corespi.scanner.AbstractMetaDataDiscovery addWebBeansXmlLocation
INFO: added beans archive URL: file:/root/owb-demo/target/classes/META-INF/beans.xml
Oct 11, 2026 8:44:57 PM org.apache.webbeans.config.BeansDeployer validateInjectionPoints
INFO: All injection points were validated successfully.
Oct 11, 2026 8:44:57 PM org.apache.webbeans.lifecycle.AbstractLifeCycle bootstrapApplication
INFO: OpenWebBeans Container has started, it took [179] ms.
CDI container started: true
Hello, Apache OpenWebBeans! (call #1)
Hello, World! (call #2)
GreetingService instance: demo.GreetingService$$OwbNormalScopeProxy0
Hello, injected-demo! (call #3)
[Observer] received event: hello-from-fireEvent
CDI container stopped.
结果解读:
- 容器启动:
INFO: OpenWebBeans Container has started, it took [179] ms.表示 SE 容器启动成功(本次实测 179ms 启动耗时);CDI container started: true为程序内SeContainer.isRunning()回显。 - 依赖注入生效:
UserService通过@Inject注入的GreetingService正常被调用,输出call #1、call #2。 - @ApplicationScoped 作用域:打印
demo.GreetingService$$OwbNormalScopeProxy0——这是 OpenWebBeans 为应用作用域 Bean 生成的客户端代理类(代理将调用委托给容器内唯一实例)。计数器从 #1 到 #3 连续递增,证明两次container.select()与一次@Inject拿到的是同一实例。 - 事件回显:
container.getBeanManager().getEvent().select(GreetingEvent.class).fire(...)触发后,GreetingObserver.onGreeting(@Observes)被回调并打印[Observer] received event: hello-from-fireEvent,证明观察者注册与事件分发链路正常。 - 优雅关闭:
CDI container stopped.为 try-with-resources 关闭容器时的输出。
八、直接 jar 运行方式(手工 classpath)
不使用 Maven 时(如二进制分发包场景),用 javac 编译、java 直接运行。classpath 由 /opt/openwebbeans/ 下各 jar 组成:
CP="/opt/openwebbeans/openwebbeans-impl-4.1.1.jar:/opt/openwebbeans/openwebbeans-se-4.1.1.jar:/opt/openwebbeans/spi/openwebbeans-spi-4.1.1.jar:/opt/openwebbeans/api/*:/opt/openwebbeans/xbean-finder-shaded-5.0.0.jar:/opt/openwebbeans/xbean-asm9-shaded-5.0.0.jar"
mkdir -p /opt/owb-direct/classes
javac -encoding UTF-8 -cp "$CP" -d /opt/owb-direct/classes /root/owb-demo/src/main/java/demo/*.java
cp /root/owb-demo/src/main/resources/META-INF/beans.xml /opt/owb-direct/classes/META-INF/beans.xml
java -Xms256m -Xmx512m -cp "/opt/owb-direct/classes:$CP" demo.OwbDemo
实测回显(节选,与 Maven 运行一致):
INFO: OpenWebBeans Container has started, it took [223] ms.
CDI container started: true
Hello, Apache OpenWebBeans! (call #1)
Hello, World! (call #2)
GreetingService instance: demo.GreetingService$$OwbNormalScopeProxy0
Hello, injected-demo! (call #3)
[Observer] received event: hello-from-fireEvent
CDI container stopped.
💡 说明:手工 classpath 必须包含
openwebbeans-se-4.1.1.jar——它是SeContainerInitializer的 ServiceLoader 注册所在(缺它会报No valid CDI implementation found);api/*需含 cdi-api、annotation-api、inject-api、interceptor-api 四个 jar(补装方法见《安装指南》篇 4.4)。
九、注意事项
⚠️ 注意:
- 必须使用 JDK 21 编译运行(OWB 4.x 官方支持至 Java 21,最低 Java 11);JDK 8 无法运行 4.x。
openwebbeans-se不传递携带 CDI API 包,pom 必须显式声明jakarta.enterprise.cdi-api与jakarta.inject-api。beans.xml的bean-discovery-mode用all才能扫描到所有带注解类;annotated模式只扫描带 Bean 定义注解的类,none不扫描(见《配置参数详解》篇)。- SE 容器的 Bean 与代理类驻留 JVM 堆,用
MAVEN_OPTS="-Xms256m -Xmx512m"限制堆内存并以ps -ef验证生效。container.select(X.class)每次返回的是同一容器内实例(作用域保证);SeContainer关闭后所有 Bean 销毁。
十、常见问题
Q1:运行报 No valid CDI implementation found?
classpath 缺少 openwebbeans-se jar(其 ServiceLoader 注册 SeContainerInitializer 实现)。Maven 方案检查依赖,手工方案按第八节补全 classpath。
Q2:编译报 cannot find symbol: SeContainerInitializer / @Inject?
pom 缺少 jakarta.enterprise.cdi-api 或 jakarta.inject-api。按第四节补依赖后重新 mvn clean compile。
Q3:fireEvent 方法不存在?
CDI 4.0 移除了 BeanManager.fireEvent(...)。改用 container.getBeanManager().getEvent().select(GreetingEvent.class).fire(event)。
Q4:怎么确认堆内存限制生效?
运行期间执行 ps -ef | grep [j]ava | grep -oE "\-Xm[sx][0-9]+m",应看到 -Xms256m 与 -Xmx512m 两行(实测回显见第六节)。
Q5:观察者没收到事件?
检查观察方法参数是否为 @Observes GreetingEvent event(观察注解必须打在参数上),且观察者类可被扫描(bean-discovery-mode="all")。
十一、参考链接
- OpenWebBeans 官网:
https://openwebbeans.apache.org/ - OpenWebBeans README(二进制包内):
/opt/openwebbeans/README.txt - CDI 4.0 规范:
https://jakarta.ee/specifications/cdi/4.0/ - SeContainerInitializer API:
https://jakarta.ee/specifications/cdi/4.0/apidocs/jakarta/enterprise/inject/sea