Apache OpenWebBeans 4.1.1 构建并运行示例应用指南

Apache OpenWebBeans 4.1.1 构建并运行示例应用指南(CDI SE 最小应用):从零创建 Maven 工程、完整 pom.xml 与 beans.xml、@ApplicationScoped/@Dependent/@Inject/事件观察示例源码、编译运行命令与真实回显(DI 生效、作用域代理、事件回显),含 JVM 堆限制验证与直接 jar 运行方式。

Apache OpenWebBeansApache OpenWebBeans2026/10/12

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)。

九、注意事项

⚠️ 注意:

  1. 必须使用 JDK 21 编译运行(OWB 4.x 官方支持至 Java 21,最低 Java 11);JDK 8 无法运行 4.x。
  2. openwebbeans-se 不传递携带 CDI API 包,pom 必须显式声明 jakarta.enterprise.cdi-api 与 jakarta.inject-api。
  3. beans.xml 的 bean-discovery-mode 用 all 才能扫描到所有带注解类;annotated 模式只扫描带 Bean 定义注解的类,none 不扫描(见《配置参数详解》篇)。
  4. SE 容器的 Bean 与代理类驻留 JVM 堆,用 MAVEN_OPTS="-Xms256m -Xmx512m" 限制堆内存并以 ps -ef 验证生效。
  5. 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