继上一篇文章《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对象,而是一个代理对象,示例代码如下:

@Namedpublic class UserController {    // 这个不是真正的UserService对象,而是一个代理    @Inject    private UserService userService;}@Namedpublic 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注解,那么到底差距在哪里呢?)