共计 2063 个字符,预计需要花费 6 分钟才能阅读完成。
继上一篇文章《Java 元注解 meta-annotation 与依赖注入》,我又摸索了两大依赖注入技术体系 CDI 和 Spring 的关系。Spring 实现了 CDI 标准吗?置信大家也会有这种问题。
事实上有两个标准,一个 JSR-299 CDI 标准,一个 JSR-330 DI 标准,绕吧,是挺绕。Spring 如同只实现了 DI 标准,而没有实现 CDI 标准。那么 CDI 和 DI、CDI 和 Spring 都有什么差别呢?
CDI 和 DI 的差别,次要体现在 DI 次要定义了 5 个注解(@Inject, @Named, @Qualifier, @Scope, @Singleton)及语义,而 CDI 则次要定义了 scope, interceptor, bean lifecycle callback, EL integration, Java EE integration 等 API、SPI 和语义,DI 不特定于 Java EE,而 CDI 特定于 Java EE。
CDI 和 Spring 的差别,例如,CDI 定义了几个规范 scope(request, session, application, conversation),容许自定义其余 scope 但只能当作“pseudo-scope”,而没有 Spring 的 prototype 和 singleton。而 Spring 不只有 prototype 和 singleton,又在 Web MVC 模块里定义了几个 scope(request, session, globalSession, application),因而单方在 scope 方面是不同的。
这次次要讲一个我认为很大的差别,即 CDI 的 Client Proxy 机制,见文档 https://docs.jboss.org/cdi/sp…。
Client Proxy 机制的特点是,如果为一个 UserController 注入了一个 UserService bean,这个 UserService 实际上不是真正的 UserService 对象,而是一个代理对象,示例代码如下:
@Named
public class UserController {
// 这个不是真正的 UserService 对象,而是一个代理
@Inject
private UserService userService;
}
@Named
public class UserService {}
为什么要这么代理一下呢?代理的作用是动态化,当 UserController 被实例化时,它只持有一个 UserService 代理的援用,此时 UserService 无需从上下文查找,甚至无需实例化(CDI 的 bean 都是 lazy creation 的),只有当 UserController 在其办法中真的要应用 UserService 时,才到上下文中查找 UserService,若没有就实例化一个。这一层形象容许 scope 较大的 bean 援用 scope 较小的 bean,例如 UserController 能够是 application scope,而 UserService 能够是 request scope,只有 UserController 在真的应用 UserService 时恰好进入了 request scope 就能够。
Seam 框架(CDI 标准的起源之一)甚至还反对双向注入注出,即如果 UserController 的 userService field 被批改成另一个值(指向一个新的 UserService 实例),那么当 UserController 的办法执行完结后,这个新值会被写回上下文,使得上下文中的 UserService 实例被替换。这一机制也是利用了代理,Seam 给所有的 client proxy 都加了 AOP interceptor 来实现以上机制。
做个有条理的比照,CDI 是如此:
- 对于几种规范 scope,注入的是 client proxy 而非实在的 bean,注入后仍会动静跟踪上下文的变动
- lazy creation
- 无论 UserController 是哪一种规范 scope,都只有在真的用到某个依赖项时才晓得其是否无效
- scope 较大的 bean 容许依赖 scope 较小的 bean
- 规范 scope 的 bean 都容许循环依赖(利用代理来做实时解环解决)
Spring 显然并非如此:
- 默认注入的是实在的 bean 而非 client proxy,注入后就不再跟踪上下文的变动
- 默认 eager creation,可用 @Lazy 注解配置为 lazy creation
- 如果 UserController 是 singleton scope(默认值),那么在利用启动时就会校验它所要注入的依赖是否无效
- scope 较大的 bean 不容许依赖 scope 较小的 bean
- 只有 singleton scope 的 bean 容许循环依赖(利用启动时用两阶段初始化来做解环解决)
Spring 的格调颇有函数式编程的不可变性,而 CDI 主动启用的可变性代理则显得有些多余。事实上市场也抉择了 Spring。(其实当初的 CDI 比 Spring 还要轻量级,new 一个就能用,包含单元测试环境,而 Spring 却须要 @SpringBootTest 注解,那么到底差距在哪里呢?)