如何将命令行参数传给Weld SE容器以实现CDI托管依赖该参数的类?
可通过Weld SE托管命令行参数的实现方案
我有一个可通过命令行启动的应用,启动命令为 java -jar MyAssembledJarWithAllDep.jar -foo bar。
我正在使用Weld SE实现Jakarta EE CDI规范,同时用Apache Commons CLI工具解析命令行参数。
现有Maven依赖配置
<dependency> <groupId>jakarta.enterprise</groupId> <artifactId>jakarta.enterprise.cdi-api</artifactId> <version>3.0.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.jboss.weld.se</groupId> <artifactId>weld-se</artifactId> <version>2.4.6.Final</version> </dependency> <dependency> <groupId>commons-cli</groupId> <artifactId>commons-cli</artifactId> <version>1.4</version> </dependency>
当前的代码实现
Weld容器初始化代码
public static void main(String[] args) { Weld weld = new Weld(); WeldContainer container = weld.initialize(); container.select(MyRunnerClass.class).get().run(args); container.shutdown(); }
现有参数解析逻辑
public void run(String[] args) { CommandLineParser clp = new CommandLineParser(args); clp.parse(); }
待解决的核心问题
当前自行实例化CommandLineParser的方式无法纳入CDI托管,无法充分利用CDI的拦截、注入、生命周期管理能力,需要实现将命令行参数传递给Weld容器,让Weld可以自行创建依赖参数的CommandLineParser类实例。
落地实现步骤
核心思路是把命令行参数作为CDI托管bean暴露出去,让Weld能在容器初始化阶段拿到参数,后续所有需要参数、需要CLI解析器的注入点都直接从容器获取,不需要手动实例化对象。
- 第一步:定义自定义CDI限定符,用来标记命令行来源的参数,避免和其他同类型的bean产生注入冲突
代码示例:import jakarta.enterprise.inject.Qualifier; import java.lang.annotation.*; @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface CliArgs {} - 第二步:编写临时参数持有器,在main方法拿到命令行参数后先暂存,供Weld启动阶段读取。注意这个持有器仅做参数传递,不能在业务代码中直接调用。
代码示例:public class CliArgsHolder { private static String[] appArgs; public static void setArgs(String[] args) { appArgs = args; } public static String[] getArgs() { return appArgs; } } - 第三步:编写CDI扩展,在容器启动的bean发现阶段,把暂存的参数注册为可注入的bean,同时把解析好的
CommandLine对象也纳入容器托管。
首先写生产者类,负责生成可注入的参数和解析结果:
然后写CDI扩展类,让Weld启动时自动加载上面的生产者:import jakarta.enterprise.inject.Produces; import org.apache.commons.cli.*; public class CliBeanProducer { // 生产原始命令行参数,用自定义的@CliArgs限定符标记 @Produces @CliArgs public String[] produceRawArgs() { return CliArgsHolder.getArgs(); } // 生产解析完成的CommandLine对象,业务代码可以直接注入使用 @Produces public CommandLine produceParsedCommand(@CliArgs String[] rawArgs) throws ParseException { // 在这里定义你自己的CLI参数规则,比如示例中的-foo参数 Options cliOptions = new Options(); cliOptions.addOption("foo", true, "示例自定义参数"); return new DefaultParser().parse(cliOptions, rawArgs); } }
最后在项目的import jakarta.enterprise.event.Observes; import jakarta.enterprise.inject.spi.BeforeBeanDiscovery; import jakarta.enterprise.inject.spi.BeanManager; public class CliCdiExtension { public void onBeforeBeanDiscovery(@Observes BeforeBeanDiscovery event, BeanManager bm) { event.addAnnotatedType(bm.createAnnotatedType(CliBeanProducer.class)); } }src/main/resources/META-INF/services/目录下,创建名为jakarta.enterprise.inject.spi.Extension的文件,文件内容填上面扩展类的全限定名,Weld启动时会自动扫描加载。 - 第四步:修改main方法的初始化逻辑,不再手动给业务类传递参数,完全交给CDI容器管理依赖
修改后的main方法示例:public static void main(String[] args) { // 先把命令行参数暂存,等Weld启动时自动读取 CliArgsHolder.setArgs(args); Weld weld = new Weld(); WeldContainer container = weld.initialize(); // 直接从容器获取业务类实例,不需要手动传参 container.select(MyRunnerClass.class).get().run(); container.shutdown(); } - 第五步:业务代码中通过
@Inject直接获取解析好的CLI参数,无需手动创建解析器实例
业务类示例:import jakarta.inject.Inject; import org.apache.commons.cli.CommandLine; public class MyRunnerClass { // 直接注入容器托管的解析结果 @Inject CommandLine parsedCli; public void run() { // 直接读取参数值即可 String fooParamValue = parsedCli.getOptionValue("foo"); // 后续业务逻辑... } }
注意事项
- 这里存在一个依赖兼容问题:当前你引入的
weld-se 2.4.6.Final是基于javax命名空间的旧版本,和你引入的jakarta.enterprise.cdi-api 3.0.0(jakarta命名空间)存在类加载冲突,会导致扩展加载失败、注入报错,建议升级到Weld SE 4.x系列的匹配版本,对应Jakarta CDI 3.0的规范要求。 - 自定义的
@CliArgs限定符是必须的,因为String[]属于通用类型,如果没有限定符,Weld在注入时可能会找不到匹配的bean,或者误注入其他同类型的数组对象。 - 不要把静态的
CliArgsHolder当成全局变量在业务代码中直接使用,它的作用仅仅是在main方法和Weld扩展之间做临时的参数传递,所有业务逻辑中用到的CLI参数都要通过CDI注入的方式获取,保证依赖的可测试性和托管能力。
内容的提问来源于stack exchange,提问作者Wumba
相关产品推荐
相关产品推荐

