f10@t's blog

闲聊一下Java中的DI和CDI

字数统计: 2k阅读时长: 8 min
2021/08/18

emm,更具体地来说其实是闲聊JSR-299与JSR-330,以及进一步澄清Spring的DI和Quarkus的CDI的概念。能疑惑这个问题只能说明我对Java的历史不够了解啊,只是很肤浅的去使用而没有思考其来源。

思考这个问题是因为我接触了Quarkus这个Java框架,大家一说到区块链一说到容器化可能都会去想go,而不是资源占有大且重量级的Java,这个框架就是用来解决Spring系列在这方面繁重的问题的,具体我会在Quarkus系列中详细叙述该框架的优点。

今天这篇文章主要内容包括这些:

  • 澄清一下DI(Dependency Injection)和CDI(Context and Dependency Injection)的关系与区别,顺带记录一下我自己思考的过程;
  • 澄清一下记Spring的ioc机制和这二者的关系(其实这俩没关系。。)
  • 简言一下Quarkus的特点

我在疑惑什么?

我为什么闲了发现这个问题呢?大家平常都用IDEA写代码,IDEA为我们提供了大量的、巨好用的插件,你好比说你的项目是Spring-Boot的,你可能会看到这样的组件:

image-20210821170658738

组件中我们可以清晰的看到我们定义的bean

而如果在Quarkus项目中,他是这个样子的:

image-20210821171622638

然后我就好奇了,CDI?CDI是啥意思?我初步的猜测是以为:Spring和Quarkus都对ioc这个理论进行了实现,在Quarkus中叫CDI。

然后我一查,我发现憨憨就是我了。

CDI和DI的关系和区别?

CDI的全称叫Context and Dependency Injection,即上下文与依赖注入,DI的全程叫Dependency Injection

ok,多了个Context,有啥大区别啊?然后我就可劲的翻百度,国内貌似没有人像我一样问这个憨憨的问题,ok翻谷歌,哎就翻出来了。

首先要知道什么是JSR,JSR全称是Java Specification Request,是不是有点RFC那味了?差不多其实,JSR大家都可以提交,都可以提出一个自己构思的没有出现过的Java新特性,然后交由JCP(Java Community Process)来进行审核,最终如果通过了审核,那么就会被加入新的Java版本中作为新特性,JCP的职责如下:

The Java Community Process (JCP) Program is the process by which the international Java community standardizes and ratifies the specifications for Java technologies.

而我们的CDI和DI,恰恰就是在Java EE6中的新特性,二者分别定义域JSR 299以及JSR 330:

image-20210821172543081
image-20210821172554156

按照时间线,最早提出的其实是DI,该提案由Bob Lee和Rod Johnson在2015年1月9号提出,实现了ioc这一核心概念,如果你想要了解ioc,可以参考我之前的文章:JavaEE基础-Spring学习 - IOC机制 · float's blog (gitee.io)

而这些新的特性,都被包含在了javax.inject这个包中:

image-20210821173003704

我们可以看一下这个包都包含了什么:

image-20210821173022697

嗷,一共有六个注解,我把他们的定义列一个表看看,英文是源代码注释,中文是我自己补充的

注解名称 定义
@Inject Identifies injectable constructors, methods, and fields. May apply to static as well as instance members. An injectable member may have any access modifier (private, package-private, protected, public).
@Named 给被@Inject指定的bean设定名称,比如@Inject @Named('driver') Seat driverSet;
@Provider Provides instances of T. Typically implemented by an injector. For any type T that can be injected, you can also inject Provider. 类似一个工厂,使用这个注解而不是@Inject可以带来一些特性(比如懒加载、多例、对实例的来源范围进行抽象、打破循环依赖)
@Qualifier Identifies qualifier annotations. Anyone can define a new qualifier.
@Scope Identifies scope annotations. A scope annotation applies to a class containing an injectable constructor and governs how the injector reuses instances of the type. By default, if no scope annotation is present, the injector creates an instance (by injecting the type's constructor), uses the instance for one injection, and then forgets it. If a scope annotation is present, the injector may retain the instance for possible reuse in a later injection. 你可以用这玩意儿定义自己的范围注解,比如典型的注解@Singleton就组合了@Scope
@Singleton Identifies a type that the injector only instantiates once. Not inherited.

当我看到这六个注解的时候,我的第一反应:这咋和Spring的@Autowired、@Bean之类的那么像呢我们把这个问题放到下一章讨论。

ok,DI我懂了,由JSR 330提出,就是上面那六个注解,那CDI是啥?不都有JSR 330了么?要JSR 299干啥?

其实我这么说你想想,上面这几个注解是不是可以帮你很好的注入Java Bean吧?那问题来了,这些Java Bean所存在的上下文(Context)你怎么获取呢?

我们把视野放到今天,具体点,Spring中的ApplicationContext(org.springframework.context.ApplicationContext),可以用来从xml格式的bean配置文档中获取bean,你JSR 330可以吗?Spring中支持AOP切面编程,你JSR 330可以吗?所以你可以看到,JSR 330虽然开辟了一个新世界,但是也仅仅是开辟,属于很简单的ioc。

所以Gavin King提出了JSR 299。它对JSR 330进行了加强,以JSR 330作为基础,增加了大量的特性,显著地增加了对模块化(modularzation)、切面编程(cross cutting aspects)、自定义范围(custom scopes)、类型安全的注入(type safe injection)的支持。

有人对JSR 299和JSR 330的区别是这么解释的:

The relation between JSR-299 and JSR-330 is comparable to the relation between JPA and JDBC. JPA uses internally JDBC, but you can still use JDBC without JPA. In Java EE 6 you can just use JSR-330 for basic stuff, and enhance it on demand with JSR-299. There is almost no overlap in the practice. You can even mix JSR-299 / JSR-330 with EJB 3.1 - to streamline your application.

JSR 330更像一个低层次的ioc描述,就如同JDBC一样,而JSR 299就如同JPA,他凌驾于JDBC、底层使用JDBC并增加了大量的特性。

所以结论就是,JSR 330和JSR 299是上下级的关系,但二者的目的是一致的,这并非是重复造轮子,而是不断的对ioc概念的实现进行摸索的过程。

下面我们再聊聊Spring和Quarkus。

Spring和Quarkus在ioc上的区别?

实际上,我一句话就能说清了,Spring的那一套DI都是自己,并不是在JSR 330或者JSR 299上重新写的,而Quarkus则是使用的Java官方的JSR实现,这就是二者的区别了。也就是你使用Spring这一套那肯定避免不了需要依赖,而你如果使用JSR那一套就可以直接使用了。

后话

Quarkus这个框架蛮有意思的,叫Kubernetes native的框架,相较于Spring由于换了底层的虚拟机,节省了内存开销、加快了启动时间、减小了包的体积等等,并且支持打包Kubernetes、打包Docker,是不是有点Go语言那个味了?对的,这个框架就是这个目的了:

Quarkus rethinks Java, using a closed-world approach to building and running it. It has turned Java into a runtime that's comparable to Go. --- 《A guide to Java serverless functions - Why choose Java for serverless application development and how to get started》,OPENSOURCE.COM

为了让Java在也能改头换面,在Serverless领域能占有一席之地。我的另一篇文章Cloud Programming Simplified:A Berkeley View on Serverless Computing · float's blog (gitee.io)里跟进学习了UCB关于Serverless结构的文章,里面就有提到要减小内存开销,提高启动速度,这样也能减小成本开销。那么这个框架就是想区别于Spring(虽然Spring有个Spring Function),达到这样的目的,后续我也会跟进学习这个框架的使用,感兴趣的同学可以一起学学。

参考学习

The Java Community Process(SM) Program - JSRs: Java Specification Requests - detail JSR# 330 (jcp.org)

The Java Community Process(SM) Program - JSRs: Java Specification Requests - detail JSR# 299 (jcp.org)

What Is The Relation Between JSR-299 and JSR-330 In Java EE 6? Do We Need Two DI APIs? - DZone Java

CATALOG
  1. 1. 我在疑惑什么?
    1. 1.1. CDI和DI的关系和区别?
    2. 1.2. Spring和Quarkus在ioc上的区别?
  2. 2. 后话
  3. 3. 参考学习