JSR-330 JAVA 依赖注入标准API说明
JSR-330 JAVA 依赖注入标准
文章目录
- JSR-330 JAVA 依赖注入标准
- Package javax.inject
- @Inject注解
- @Qualifier注解
- `Interface Provider<T>`
- @Named注解
- @Scope注解
- @Singleton注解
- 包的下载
JSR-330 JAVA 依赖注入标准,只发布了规范API源码,没有发布规范文档。
Package javax.inject
这个包规定了一种获取对象的方法,与构造函数、工厂和服务定位器(如JNDI)等传统方法相比,它可以最大限度地提高可重用性、可测试性和可维护性。这个过程被称为依赖性注入,对大多数非琐碎的应用都是有益的。
许多类型依赖于其他类型。例如,一个Stopwatch
可能依赖于一个TimeSource
。一个类型所依赖的类型被称为它的依赖关系。在运行时找到一个依赖关系的实例来使用的过程被称为解析依赖关系。如果找不到这样的实例,则表示该依赖关系未被满足,并且应用程序运行失败。
在没有依赖注入的情况下,一个对象可以通过几种方式解决其依赖关系。它可以调用一个构造函数,将一个对象直接与它的依赖关系的实现和生命周期硬连接起来:
class Stopwatch {final TimeSource timeSource;Stopwatch () {timeSource = new AtomicClock(...); // 通过调用构造函数新建一个依赖对象}void start() { ... }long stop() { ... }}
如果需要更多的弹性,该对象可以调用一个工厂或服务定位器:
class Stopwatch {final TimeSource timeSource;Stopwatch () {timeSource = DefaultTimeSource.getInstance(); // 通过工厂方式新建一个依赖对象}void start() { ... }long stop() { ... }}
在决定这些传统的依赖解决方法时,程序员必须做出权衡。(1)构造器更简洁,但有限制性。(2)工厂在一定程度上将客户端和实现解耦,但需要模板代码。(3)服务定位器甚至可以进一步解耦,但会降低编译时的类型安全性。这三种方法都抑制了单元测试。例如,如果程序员使用工厂,那么针对依赖工厂的代码的每个测试都必须模拟出工厂,并记得自己清理,否则就会有不利影响。
void testStopwatch() {TimeSource original = DefaultTimeSource.getInstance();DefaultTimeSource.setInstance(new MockTimeSource());try {// Now, we can actually test Stopwatch.Stopwatch sw = new Stopwatch();...} finally {DefaultTimeSource.setInstance(original);}}
在实践中,模拟工厂的能力会导致更多的模板代码。对多个依赖关系进行模拟和清理的测试很快就会失去控制。更糟糕的是,程序员必须准确地预测未来需要多少灵活性,否则就会承担后果。如果程序员最初选择使用一个构造函数,但后来决定需要更多的灵活性,那么程序员必须替换对构造函数的每一次调用。如果程序员谨慎行事,预先写好工厂,可能会导致大量不必要的模板代码,增加复杂性和易错性。
依赖性注入解决了所有这些问题。程序员不需要调用构造函数或工厂,而是由一个叫做依赖注入器的工具将依赖关系传递给对象。
class Stopwatch {final TimeSource timeSource;@Inject Stopwatch(TimeSource TimeSource) {this.TimeSource = TimeSource;}void start() { ... }long stop() { ... }}
injector(注入器) 进一步将依赖关系传递给其他依赖关系,直到构建出整个对象图。例如,假设程序员要求注入器创建一个StopwatchWidget
实例。
/** GUI for a Stopwatch */class StopwatchWidget {@Inject StopwatchWidget(Stopwatch sw) { ... }...}
injector 可能:
- 找到一个
TimeSource
实例 - 用
TimeSource
实例构建一个Stopwatch
实例 - 用
Stopwatch
实例构造一个StopwatchWidget
这使得程序员的代码干净、灵活,并且相对来说没有依赖性相关的基础设施。
在单元测试中,程序员现在可以直接构造对象(没有注入器),并传递模拟依赖。程序员不再需要在每个测试中建立和拆除工厂或服务定位器。这大大简化了我们的单元测试。
void testStopwatch() {Stopwatch sw = new Stopwatch(new MockTimeSource());...}
单元测试复杂性的总减少量与单元测试的数量和依赖关系的数量的乘积成正比。
这个包提供了依赖注入注解,使可移植类得以实现,但它将外部依赖配置留给注入器实现。 程序员对构造函数、方法和字段进行注解,以表明其可注入性(构造函数注入在上面的例子中得到了证明)。依赖性注入器通过检查这些注释来识别一个类的依赖性,并在运行时注入依赖性。此外,injector 可以在构建时验证所有的依赖关系是否被满足。相比之下,A service locator(服务定位器)在运行时才能发现未满足的依赖关系。
注入器的实现可以采取多种形式。注入器可以使用XML、注解、DSL(特定领域语言),甚至是普通的Java代码来配置自己。一个注入器可以依靠反射或代码生成。一个使用编译时代码生成的注入器甚至可能没有自己的运行时表示。其他注入器可能根本无法生成代码,无论是在编译还是运行时。一个 “容器”,对于某些定义来说,可以是一个注入器,但这个包规范的目的是尽量减少对注入器实现的限制。
@Inject注解
@Inject注解标明可注入的构造函数、方法和字段。可以适用于静态和实例成员。一个可注入的成员可以有任何访问修饰符(private, package-private, protected, public)。构造函数首先被注入,其次是字段,然后是方法。超类中的字段和方法在子类中的字段和方法之前被注入。在同一类中的字段和方法之间的注入顺序没有被指定。
可注入的构造函数用@Inject
注解,并接受零个或多个依赖作为参数。一个类中的只支持在一个构造函数上添加注解@Inject
。
当没有其他构造函数时,@Inject
对于公共的、无参数的构造函数是可选的。这使得注入器可以调用默认的构造函数。
什么是可注入的字段:
- 用@Inject注释的。
- not final
- may have any otherwise valid name.可以有任何其他有效的名称。
什么是可注入的方法:
- 用@Inject注释的。
- 不是abstract。
- 不声明它们自己的类型参数。
- 可以返回一个结果
- 可以有任何其他有效的名称。
- 接受零个或多个依赖作为参数。
注入器忽略注入方法的结果,但允许非void的返回类型,以支持在其他情况下使用该方法(例如,构建器式的方法链)。
Examples:
public class Car {// Injectable constructor@Inject public Car(Engine engine) { ... }// Injectable field@Inject private Provider<Seat> seatProvider;// Injectable package-private method@Inject void install(Windshield windshield, Trunk trunk) { ... }}
一个用@Inject注解的方法如果覆写了另一个用@Inject注解的方法,每个实例的每个注入请求将只被注入一次。一个没有@Inject注解的方法如果覆写了一个用@Inject注解的方法,将不会被注入。
进行成员注入时必须进行@Inject注解标明。虽然一个可注入的成员可以使用任何可访问性修饰符(包括私有),但平台或注入器的限制(如安全限制或缺乏反射支持)可能会阻止非公共成员的注入。
Qualifiers
一个限定符可以注解一个可注入的字段或参数,并与类型相结合,确定要注入的实现。修饰符是可选的,当在独立于注入器的类中与 @Inject 一起使用时,一个字段或参数不能有超过一个修饰符。在下面的例子中,限定词是@Leather,@Tinted,@Big。
public class Car {@Inject private @Leather Provider<Seat> seatProvider;@Inject void install(@Tinted Windshield windshield,@Big Trunk trunk) { ... }}
如果一个可注入方法覆盖另一个方法,覆盖方法的参数不会自动继承被覆盖方法的参数的限定词。
Injectable Values
对于一个给定的类型T和可选的限定符,一个注入器必须能够注入一个用户指定的类,该类。
- 与T的赋值兼容,并且
- 有一个可注入的构造函数。
例如,用户可以使用外部配置来选择T的实现。除此之外,哪些值被注入取决于注入器的实现和它的配置。
Circular Dependencies
检测和解决循环依赖是留给注入器实现的一个练习。两个构造函数之间的循环依赖是一个明显的问题,但你也可以在可注入的字段或方法之间有一个循环依赖。
class A {@Inject B b;}class B {@Inject A a;}
当构造一个A的实例时,一个“天真”的注入器实现可能会进入一个无限的循环,构造一个B的实例来设置在A上,第二个A的实例来设置在B上,第二个B的实例来设置在第二个A的实例上,等等。
“保守”的注入器可能会在构建时检测到循环依赖并产生错误,此时程序员可以通过注入Provider<A>
或Provider<B>
代替A或B来打破循环依赖关系。从它被注入的构造函数或方法中直接调用get()
,会破坏提供者打破循环依赖的能力。在方法或字段注入的情况下,对其中一个依赖的范围(例如,单例作用域)也可以实现有效的循环关系。
@Qualifier注解
@Qualifier注解标明限定词注释。任何人都可以定义一个新的限定符。一个限定符的注解:
- 有
@Qualifier
,@Retention(RUNTIME)
,通常还有@Documented
的注解。 - 可以有属性。
- 可以是公共API的一部分,与依赖类型很相似,但与实现类型不同,后者不需要是公共API的一部分。
- 如果用
@Target
注释,可以限制使用。虽然本规范只涉及将限定符应用于字段和参数,但一些注入器配置可能在其他地方使用限定符注释(例如,在方法或类上)。
For example:
@java.lang.annotation.Documented@java.lang.annotation.Retention(RUNTIME)@javax.inject.Qualifierpublic @interface Leather {Color color() default Color.TAN;public enum Color { RED, BLACK, TAN }}
Interface Provider<T>
提供T
的实例。通常由一个注入器实现。对于任何可以被注入的T
类型,你也可以注入Provider<T>
。与直接注入T
相比,注入Provider<T>
可以:
- 可以返回多个实例。
- 实例的返回可以延迟化或可选的检索一个实例。
- 打破循环依赖关系。
- 抽象作用域,可以在一个已知的作用域中查询一个作用域更小的实例。
For example:
class Car {@Inject Car(Provider<Seat> seatProvider) {Seat driver = seatProvider.get();Seat passenger = seatProvider.get();...}}
get()方法—可以返回一个完全构造的类型T的实例。
@Named注解
基于String类型的限定器
Example usage:
public class Car {@Inject @Named("driver") Seat driverSeat;@Inject @Named("passenger") Seat passengerSeat;...}
@Scope注解
识别范围注解。范围注解适用于包含可注入构造函数的类,并管理注入器如何重复使用该类型的实例。默认情况下,如果没有范围注解,注入器会创建一个实例(通过注入类型的构造函数),将该实例用于一次注入,然后将其遗忘。如果存在范围注解,注入器可以保留该实例,以便在以后的注入中可能被重用。如果多个线程可以访问一个作用域的实例,其实现应该是线程安全的。作用域本身的实现是由注入者决定的。
在下面的例子中,范围注解@Singleton确保我们只有一个Log实例:
@Singletonclass Log {void log(String message) { ... }}
如果注入器在同一个类上遇到一个以上的范围注解,或者遇到它不支持的范围注解,就会产生一个错误。
一个范围注解:
- 是用
@Scope
、@Retention(RUNTIME)
和典型的@Documented
注解的。 - 不应该有属性。
- 通常没有
@Inherited
,所以范围是与实现的继承正交的。 - 如果用
@Target
来注解,可能会限制使用。虽然本规范只涵盖了对类的作用域的应用,但一些注入器配置可能在其他地方使用作用域注释(例如在工厂方法结果上)。
For example:
@java.lang.annotation.Documented@java.lang.annotation.Retention(RUNTIME)@javax.inject.Scopepublic @interface RequestScoped {}
用 @Scope 来注解作用域可以帮助注入器检测到这样的情况:程序员在类上使用了作用域注解,但忘记在注入器中配置作用域。一个"保守"的注入器会产生一个错误,而不是不应用一个作用域。
@Singleton注解
标识一个类型,注入器只实例化一次。不继承。
包的下载
最新版查询:https://search.maven.org/artifact/jakarta.inject/jakarta.inject-api/2.0.1.MR/jar
maven配置
<dependency><groupId>jakarta.inject</groupId><artifactId>jakarta.inject-api</artifactId><version>2.0.1.MR</version>
</dependency>
Gradle配置
implementation 'jakarta.inject:jakarta.inject-api:2.0.1.MR'
JSR-330 JAVA 依赖注入标准API说明相关推荐
- Java 依赖注入标准(JSR-330)简介
Java 依赖注入标准(JSR-330)简介 转载请保留作者信息: 作者:88250 ,Vanessa 时间:2009 年 11 月 19 日 Java 依赖注入标准(JSR-330,Dependen ...
- Java依赖注入 - DI设计模式示例教程
Java依赖注入 - DI设计模式示例教程 Java依赖注入 设计模式允许我们删除硬编码的依赖项,并使我们的应用程序松散耦合,可扩展和可维护.我们可以在java中实现依赖注入,以将依赖项解析从编译时移 ...
- java依赖注入_Java依赖注入选项
java依赖注入 我想花一些时间来总结一些流行的Java依赖注入(DI)框架. 这是可用功能的高级概述. 首先,什么是依赖注入? "依赖注入是一种软件设计模式,可以删除硬编码的依赖,并可以在 ...
- Java依赖注入选项
我想花一些时间来总结一些流行的Java依赖注入(DI)框架. 这是可用功能的高级概述. 首先,什么是依赖注入? "依赖注入是一种软件设计模式,可以删除硬编码的依赖,并可以在运行时或编译时更改 ...
- Java依赖注入(DI)实例详解
Java依赖注入模式允许我们摆脱硬编码,使我们的应用更加松耦合.增强扩展性以及可维护性.通过依赖注入我们可以降低从编译到运行时的依赖性. Java依赖注入 Java的依赖注入仅仅通过理论是很难解明白的 ...
- 【概念题】java依赖注入,android开发实战-记账本清风紫雪
这是使用构造器注入来装配bean 2.setter注入 这是使用setter注入,p是spring的名称空间,可以用来代替标签. 四.装配的概念 创建应用对象之间协作关系的行为称为装配.也就是说当一个 ...
- java 什么时候依赖注入_玩框架java依赖注入 – 何时使用单例
So I am wondering, should I be using singleton objects as the examples seem to imply? If this is the ...
- java依赖注入inject_@inject依赖注入的过程
首先需要知道实例是如何创建以及如何被注入的,而这一切都由container这个容器进行管理. @H_403_6@1.实例构建 class ContainerImpl implements Contai ...
- java依赖注入上下文_【Java EE】--Contexts and Dependency Injection (上下文與依賴注入)04...
使用范圍 對於Web應用程序來使用注入另一個bean類的bean,bean需要能夠在用戶與應用程序交互的持續時間內保持狀態. 定義這個狀態的方法是給bean一個范圍. 您可以給對象表23-1中描述的任 ...
最新文章
- 运用层通过shell脚本直接操控gpio
- Winform中使用FastReport实现简单的自定义PDF导出
- PowerTip of the Day-How Much RAM Do You Have?
- 腾讯正式宣布成立技术委员会,要对组织架构下狠手
- Microsoft Edge 浏览器开始支持webkit私有样式
- 清华大学开始招收高二学生,数学天赋是最重要的入围条件
- 判断文件是否改变php,PHP判断文件是否被修改实例
- matlab连接散射点,使用小波散射做信号分类
- VsCode云端版本
- 1.1 线性方程组(线性代数及其应用-第5版-系列笔记)
- C专家编程电子书pdf下载
- 毕向东_Java基础
- 在Ubuntu环境下配置Proxmark3(PM3)使用环境
- 北京海淀区千峰计算机学校,千锋Java学院-Java培训|Java开发培训|Java工程师培训开拓者...
- python Django
- teredo 服务器无响应,teredo 服务器能ping通但就是连不上
- web前端面试技巧-如何自我介绍?如何应对hr?
- ssd hdd linux分区方案,windows10+ubuntu 16.04+双硬盘(SSD+HDD)分区(图文)
- OpenMesh-网格光顺的算法
- 17 内存规整(memory compaction)
热门文章
- 计算机开根号原理,根号的原理_怎么开的根号,有原理吗
- C# Xamarin For Android移动开发项目实战篇
- ao史密斯定时设置_下图ao史密斯热水器的预约定时怎么用?-史密斯热水器怎么接线...
- getchar()和gets()
- stm32毕业设计 单片机遥控小车
- FlatBuffer
- IIS是不是相当于服务器?
- Terracotta Server集群
- 编译器之语法分析器(syntax analyzer)
- 设计模式之 Interpreter(解释器) 通俗理解