继上一篇文章《Java元注解meta-annotation与依赖注入》,我又探索了两大依赖注入技术体系CDI和Spring的关系。Spring实现了CDI规范吗?相信你们也会有这种问题。html
事实上有两个规范,一个JSR-299 CDI规范,一个JSR-330 DI规范,绕吧,是挺绕。Spring好像只实现了DI规范,而没有实现CDI规范。那么CDI和DI、CDI和Spring都有什么差别呢?java
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方面是不一样的。session
此次主要讲一个我认为很大的差别,即CDI的Client Proxy机制,见文档https://docs.jboss.org/cdi/sp... 。app
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是如此:单元测试
Spring显然并不是如此:测试
Spring的风格很有函数式编程的不可变性,而CDI自动启用的可变性代理则显得有些多余。事实上市场也选择了Spring。(其实如今的CDI比Spring还要轻量级,new一个就能用,包括单元测试环境,而Spring却须要@SpringBootTest注解,那么究竟差距在哪里呢?)