Java EE CDI中属性为null时如何触发Producer重新注入
问题本质
CDI 规范中,应用作用域(@ApplicationScoped)Bean的字段注入仅在Bean初始化阶段执行一次,Producer方法只会在注入时被调用,生成的值会被缓存在Bean实例中,后续不会自动重新触发注入逻辑。观察到的第二次请求不会进入produceString方法属于符合规范的预期行为。
另外示例代码中host字段加了final修饰符,本身就不支持注入后修改,要实现值的动态更新首先要移除final修饰符(值包装类方案可保留final修饰)。
可行实现方案
不需要修改Bean为@RequestScoped,以下三种方案均符合CDI规范,可保留@ApplicationScoped的单例性能优势:
方案1:注入Provider实现延迟取值
这是改造成本最低的标准CDI用法,由于示例中produceString未指定作用域,默认是@Dependent,每次调用Provider.get()都会重新执行Producer方法拉取最新配置。
修改Service类代码即可:
@ApplicationScoped public class Service { @Property @Inject private Provider<String> hostProvider; public void callAnotherService() { String host = hostProvider.get(); // 每次调用实时走Producer拉取最新值 callRestMethod(host); } }
方案2:自定义拦截器统一处理null值重注入
如果不想修改原有字段的使用逻辑,可以通过拦截器在方法调用前自动检测带@Property注解的字段,值为null时自动触发重注入,适合多字段统一管控的场景。
- 定义拦截器绑定注解
@InterceptorBinding @Retention(RUNTIME) @Target({METHOD, TYPE}) public @interface RefreshConfigIfNull {}
- 实现拦截器逻辑
@Interceptor @RefreshConfigIfNull @Priority(Interceptor.Priority.APPLICATION) public class RefreshConfigInterceptor { @Inject private BeanManager beanManager; @AroundInvoke public Object aroundInvoke(InvocationContext ctx) throws Exception { Object target = ctx.getTarget(); for (Field field : target.getClass().getDeclaredFields()) { if (field.isAnnotationPresent(Property.class) && field.getType().equals(String.class)) { field.setAccessible(true); if (field.get(target) == null) { // 走CDI标准Bean解析流程,重新触发Producer逻辑 Set<Bean<?>> beans = beanManager.getBeans(field.getType(), field.getAnnotation(Property.class)); Bean<?> bean = beanManager.resolve(beans); CreationalContext<?> creationalContext = beanManager.createCreationalContext(bean); field.set(target, beanManager.getReference(bean, field.getType(), creationalContext)); } } } return ctx.proceed(); } }
- 在Service类或业务方法上添加
@RefreshConfigIfNull注解即可生效。
方案3:Producer返回配置包装类实现实时读取
从Producer侧改造,将直接返回配置值改为返回持有配置依赖的包装对象,每次调用取值方法时实时拉取配置,逻辑收敛性最好。
- 定义配置值包装类
public class ConfigValue { private final InjectionPoint ip; private final Config config; public ConfigValue(InjectionPoint ip, Config config) { this.ip = ip; this.config = config; } // 每次取值实时从配置服务拉取 public String get() { return config.getString(ip.getMember().getName()); } }
- 修改Producer逻辑
@ApplicationScoped public class PropertyProduces { @Inject private Config config; @Property @Produces public ConfigValue produceConfigValue(InjectionPoint ip) { // 初始化时仅返回包装对象,不提前拉取配置 return new ConfigValue(ip, config); } }
- 修改Service类注入逻辑
@ApplicationScoped public class Service { @Property private final ConfigValue host; // 包装类实例不变,可保留final修饰 public void callAnotherService() { callRestMethod(host.get()); // 每次调用实时拉取最新配置 } }
内容的提问来源于stack exchange,提问作者Ethan
相关产品推荐
相关产品推荐

