CDI和Spring的主要差异

原文发表于2020-12-18。

继上一篇文章《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/spec/2.0/cdi-spec.html#client_proxies 。

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是如此:

  1. 对于几种标准scope,注入的是client proxy而非真实的bean,注入后仍会动态跟踪上下文的变动
  2. lazy creation
  3. 无论UserController是哪一种标准scope,都只有在真的用到某个依赖项时才知道其是否有效
  4. scope较大的bean允许依赖scope较小的bean
  5. 标准scope的bean都允许循环依赖(利用代理来做实时解环处理)

Spring显然并非如此:

  1. 默认注入的是真实的bean而非client proxy,注入后就不再跟踪上下文的变动
  2. 默认eager creation,可用@Lazy注解配置为lazy creation
  3. 如果UserController是singleton scope(默认值),那么在应用启动时就会校验它所要注入的依赖是否有效
  4. scope较大的bean不允许依赖scope较小的bean
  5. 只有singleton scope的bean允许循环依赖(应用启动时用两阶段初始化来做解环处理)

Spring的风格颇有函数式编程的不可变性,而CDI自动启用的可变性代理则显得有些多余。事实上市场也选择了Spring。(其实现在的CDI比Spring还要轻量级,new一个就能用,包括单元测试环境,而Spring却需要@SpringBootTest注解,那么究竟差距在哪里呢?)

posted @ 2020-12-25 18:35  计算法  阅读(195)  评论(0编辑  收藏  举报