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