bigywater.tech
Java · 374

CDI 依赖注入:作用域与代理对象

CDI 的 @ApplicationScoped 背后是一个代理对象。这篇用真实容器看代理什么时候创建、什么类型的类不能被代理、拦截器在哪些调用上起作用,以及歧义与循环依赖怎样在启动时暴露。

null
直接读 @ApplicationScoped bean 代理对象上的字段(注入发生在真实实例上,不在代理上)
同类内部调用不拦截
a() 内部调用同类的 @Logged 方法 b()、this.a()、包可见 pkg() 都没有触发拦截器
77 ns
经 @ApplicationScoped 代理调用一次 add 的耗时(直接调用约 0.31 ns)

作用域与代理

实测 · 本机执行输出
  [scope]
  启动与取得 Client 之后(Client 本身是 @ApplicationScoped,拿到的是代理,还没构造真实实例):
        容器已启动,尚未取得任何 bean
        取得 Client(代理类 Main$Client$Proxy$_$$_WeldClientProxy)
  直接读代理对象上的字段 cl.app / cl.dep:null / null(代理自己的字段没有被注入,是 null)
  调用 cl.fields()(此时才创建真实的 Client 实例):app=true dep=true
    构造日志:
        new Client()
        new Dep()#1
        new Sing()#1
        Client @PostConstruct(此时 app 类 = Main$App$Proxy$_$$_WeldClientProxy)
  Client 里注入的各字段的运行时类:app = Main$App$Proxy$_$$_WeldClientProxy;dep = Dep;sing = Sing;req = Main$Req$Proxy$_$$_WeldClientProxy
  是否是代理(类名含 Proxy):app true;dep false;sing false;req true
  第 1 次 cl.app().who() = App@1500b2f3
  第 2 次 cl.app().who() = App@1500b2f3
    构造日志(ApplicationScoped 在第一次调用方法时才构造):
        new App()#1
  cl.dep().who() 两次:Dep@7eecb5b8 / Dep@7eecb5b8(@Dependent 随宿主创建,一个注入点一个实例)
  cl.sing().who():Sing@126253fd;c.select(Sing.class).get().who():Sing@126253fd
  两次 c.select(Client.class).get() 是同一个代理对象? true;两次 c.select(App.class).get().who() 相同(背后同一个实例)? true
  两次 c.select(Dep.class).get() 是同一个对象? false(每次 get 新建一个 @Dependent)
    构造日志:
        new Dep()#2
        new Dep()#3
  Instance<Dep>.get() 三次的 who():Dep@7bbc8656 / Dep@6933b6c6 / Dep@7d3e8655
    构造日志:
        new Dep()#4
        new Dep()#5
        new Dep()#6
  请求作用域(没有激活请求上下文)调用 cl.req().who():ContextNotActiveException: WELD-001303: No active contexts for scope type jakarta.enterprise.context.RequestScoped
  激活请求上下文后 cl.req().who() 两次:Req@2f953efd / Req@2f953efd
    日志(deactivate 时销毁 Req):
        new Req()#1
        Req @PreDestroy
  再激活一次的 cl.req().who():Req@f68f0dc(新实例)
  容器关闭之后:
        Dep @PreDestroy Dep@3e0e1046
        Dep @PreDestroy Dep@7dc19a70
        Dep @PreDestroy Dep@7eecb5b8
        Dep @PreDestroy Dep@7bbc8656
        Dep @PreDestroy Dep@6933b6c6
        Dep @PreDestroy Dep@7d3e8655

什么类不能被代理

实测 · 本机执行输出
  [proxy]
      final 类的 @ApplicationScoped        → UnproxyableResolutionException: WELD-001437: Bean type class Main$FinalBean is not proxyable because it is final - <unknownjakarta.enterprise.inject.spi.Bean instance>.
      带 public final 方法的 @ApplicationScoped → UnproxyableResolutionException: WELD-001480: Bean type class Main$FinalMethod is not proxyable because it contains a final method public final java.lang.String Main$FinalMethod.x() - <unknownjakarta.enterprise.inject.spi.Bean instance>.
      没有无参构造器的 @ApplicationScoped        → UnsatisfiedResolutionException: WELD-001334: Unsatisfied dependencies for type NoDefaultCtor with qualifiers  
      只有 private 无参构造器的 @ApplicationScoped → 启动成功;取得的类 Main$PrivateCtor$Proxy$_$$_WeldClientProxy(代理)
      带 public 实例字段的 @ApplicationScoped  → DefinitionException: WELD-000075: Normal scoped managed bean implementation class has a public field:  [EnhancedAnnotatedFieldImpl] public Main$WithField.name
      @Dependent 的类带 final 方法(注入点注入)     → 启动成功;取得的类 UseFinalDep
  带包可见方法的 @ApplicationScoped:启动成功;b.y()(内部调用包可见的 x())= pkg

拦截器与自调用

实测 · 本机执行输出
  [intercept]
  外部调用 b.a()(a 内部又调用 b()):a→b
    拦截日志:
          [拦截] a()
  外部调用 b.plain()(无注解,内部 this.a()):plain→a→b
    拦截日志:
  外部调用 b.callPkg()(内部调用包可见方法 pkg(),pkg 带 @Logged):pkg
    拦截日志:
  b 的运行时类:Main$Biz$Proxy$_$$_WeldClientProxy

循环依赖

实测 · 本机执行输出
  [cycle]
  @ApplicationScoped 的 CA ↔ CB 循环依赖:CA→Main$CB$Proxy$_$$_WeldClientProxy;CB→Main$CA$Proxy$_$$_WeldClientProxy
  @Dependent 的 DA ↔ DB 循环依赖:DeploymentException: WELD-001443: Pseudo scoped bean has circular dependencies. Dependency path: 
  @Dependent 的 DA ↔ DB,另有 DC 同时注入两者:DeploymentException: WELD-001443: Pseudo scoped bean has circular dependencies. Dependency path:

歧义、缺失与限定符

实测 · 本机执行输出
  [resolve]
  两个实现、没有限定符(EnGreeter + ZhGreeter)注入 Greeter:DeploymentException: WELD-001409: Ambiguous dependencies for type Greeter with qualifiers @Default
  没有任何实现注入 Runnable:DeploymentException: WELD-001408: Unsatisfied dependencies for type Runnable with qualifiers @Default
  c.select(Greeter.class):isAmbiguous = true;isUnsatisfied = false;get() → AmbiguousResolutionException: WELD-001335: Ambiguous dependencies for type Greeter with qualifiers 
  遍历 Instance:[hello, 你好]
  select(@Lang("fr")):bonjour
  select(@Lang("xx")) 不存在:UnsatisfiedResolutionException: WELD-001334: Unsatisfied dependencies for type Greeter with qualifiers @Lang WELD-001475: The following beans match by type, but none have matching qualifiers:
  带限定符的实现还满足无限定符的注入点吗(@Default)? c.select(Greeter.class, Default.Literal.INSTANCE) 的实现数:2(只有 EnGreeter 与 ZhGreeter;@Lang 的实现没有 @Default)
  @Alternative 的实现(没有启用)是否参与解析:启动成功;注入的是 hello
  用 Weld.alternatives(AltGreeter) 启用后:注入的是 alt
  @Inject @Lang("fr") Greeter:bonjour

生产者与销毁

实测 · 本机执行输出
  [producer]
  c1 与 c2 是否同一个 Conn(@Dependent 生产者每个注入点一个):false;id 1 / 2
  @ApplicationScoped 生产者生产的 Shared 注入后的类:Main$Shared$Proxy$_$$_WeldClientProxy;v() = shared
  容器关闭时(@Disposes 的 Conn.close 是否执行):
        open Conn#1
        open Conn#2
        取得 UseProd 之后
        produce Shared
        close Conn#1
        close Conn#2
  @ApplicationScoped 生产者生产 final 的 String 类型:DeploymentException: WELD-001410: The injection point has non-proxyable dependencies: [BackedAnnotatedField] @Inject @Named Main$UseFinalProd.s

代理的成本

实测 · 本机执行输出
  [cost]
  2000 万次 add(a, b):经 @ApplicationScoped 代理 76.56 ns / 次;直接对象 0.31 ns / 次(sink=true)
  c.select(Calc.class).get() 取一个 @Dependent bean 1.09 µs / 次
  c.select(Calc.class).get() 取 @ApplicationScoped 代理 0.83 µs / 次
  Weld SE 启动(6 个 bean 类,不扫描)20 ms

分步演示

CDI
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64,JDK 21.0.12;Weld SE 5.1.3.Final(CDI 4.0),disableDiscovery + 手工注册 bean 类;javac 带 -parameters
    • 没有验证:Quarkus ArC、OpenWebBeans 等其它实现的行为差异(自调用拦截、私有构造器代理)、@SessionScoped / @ConversationScoped、事件 @Observes 的顺序与异步、Instance 的 destroy() 对 @Dependent 的显式销毁、beans.xml 的 bean-discovery-mode 与注解扫描、CDI Lite(Quarkus 的构建时 bean 发现)
    京ICP备2026067502号-1京公网安备11010502063487号