CDI 1.2如何在指定JNDI资源存在时条件生成或覆盖Bean
解决方案
核心问题原因
@Resource 注解的资源注入由Java EE容器在CDI Bean初始化前置阶段处理,一旦指定JNDI名称不存在,容器会直接抛出启动异常,不会进入后续生产者逻辑,所以原方案无法达到条件降级的效果。
方案一:生产者内手动JNDI查找(最简实现)
无需额外扩展,仅修改生产者逻辑即可实现需求:
- 放弃
@Resource注入,在生产者方法内手动执行JNDI查找,自行捕获资源不存在的异常 - 给JNDI实现的生产者设置更高优先级,确保存在时优先于默认内存实现注入
- 默认内存实现加
@Default注解作为兜底实现
调整后的代码
基础实现类
interface MyService{ void doSomething(); } // 默认内存实现,加@Default标识为兜底实现 @ApplicationScoped @Default public class InMemory implements MyService{ Map<String, String> persistence = new HashMap<>(); public void doSomething(){ persistence.put("now", new Date());} } // JNDI实现保持不变,修正原代码语法错误 public class JndiPersistence implements MyService{ Cache<String, String> persistence; public JndiPersistence(Cache persistence){ this.persistence = persistence;} public void doSomething(){ persistence.put("now", new Date());} } // 客户端类无需修改 public class DataManager(){ private MyService myservice; @Inject public DataManager( MyService myservice ){ this.myservice = myservice; } public void manageMyData(){ myservice.doSomething(); } }
调整后的生产者代码
import javax.enterprise.inject.Alternative; import javax.annotation.Priority; import javax.naming.InitialContext; import javax.naming.NamingException; public class JndiProducer{ @Produces @ApplicationScoped @Alternative // 标识为可选实现 @Priority(2000) // 数值越大优先级越高,默认普通Bean优先级为1000,保证本实现优先被选择 public MyService jndiBean(){ try { // 手动查找JNDI资源 Cache cache = (Cache) new InitialContext().lookup("java:comp/env/persistenceCache"); return new JndiPersistence(cache); } catch (NamingException e) { // JNDI资源不存在,抛出运行时异常,CDI会自动忽略该生产者,降级使用默认内存实现 throw new RuntimeException("JNDI cache not found, fallback to in-memory implementation", e); } } }
方案二:CDI Portable Extension(更规范的条件注册)
如果需要绑定多个Bean到同一个存在性条件,或者需要减少运行时判断开销,可以通过CDI扩展实现启动阶段条件注册:
- 自定义CDI扩展,在容器启动阶段执行一次JNDI查找
- 若JNDI资源不存在,直接禁用JndiPersistence的Bean注册,容器会自动使用默认内存实现
扩展代码实现
import javax.enterprise.event.Observes; import javax.enterprise.inject.spi.Extension; import javax.enterprise.inject.spi.ProcessAnnotatedType; import javax.naming.InitialContext; import javax.naming.NamingException; public class JndiResourceCheckExtension implements Extension { private boolean jndiCacheExists = false; // 容器启动阶段仅执行一次JNDI存在性检查 public JndiResourceCheckExtension() { try { new InitialContext().lookup("java:comp/env/persistenceCache"); jndiCacheExists = true; } catch (NamingException e) { jndiCacheExists = false; } } // JNDI不存在时直接注销JndiPersistence的Bean定义 public <T> void processAnnotatedType(@Observes ProcessAnnotatedType<T> pat) { if (pat.getAnnotatedType().getJavaClass().equals(JndiPersistence.class) && !jndiCacheExists) { pat.veto(); } } }
启用扩展
在项目META-INF/services/javax.enterprise.inject.spi.Extension文件中添加扩展类的全限定名即可生效,之后给JndiPersistence类添加@ApplicationScoped和@Priority(2000)注解,不需要额外生产者即可实现需求。
方案对比
- 方案一代码量少,无需额外配置,适合单Bean条件注册场景
- 方案二逻辑更清晰,仅启动时执行一次资源检查,适合多Bean绑定同一条件的场景
内容的提问来源于stack exchange,提问作者Eric B.
相关产品推荐
相关产品推荐

