You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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时自动触发重注入,适合多字段统一管控的场景。

  1. 定义拦截器绑定注解
@InterceptorBinding
@Retention(RUNTIME)
@Target({METHOD, TYPE})
public @interface RefreshConfigIfNull {}
  1. 实现拦截器逻辑
@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();
    }
}
  1. 在Service类或业务方法上添加@RefreshConfigIfNull注解即可生效。

方案3:Producer返回配置包装类实现实时读取

从Producer侧改造,将直接返回配置值改为返回持有配置依赖的包装对象,每次调用取值方法时实时拉取配置,逻辑收敛性最好。

  1. 定义配置值包装类
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());
    }
}
  1. 修改Producer逻辑
@ApplicationScoped
public class PropertyProduces {
    @Inject
    private Config config;

    @Property
    @Produces
    public ConfigValue produceConfigValue(InjectionPoint ip) {
        // 初始化时仅返回包装对象,不提前拉取配置
        return new ConfigValue(ip, config);
    }
}
  1. 修改Service类注入逻辑
@ApplicationScoped
public class Service {
    @Property
    private final ConfigValue host; // 包装类实例不变,可保留final修饰

    public void callAnotherService() {
        callRestMethod(host.get()); // 每次调用实时拉取最新配置
    }
}

内容的提问来源于stack exchange,提问作者Ethan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 23:27:35