JSR-330 JAVA 依赖注入标准API说明
Posted 顧棟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JSR-330 JAVA 依赖注入标准API说明相关的知识,希望对你有一定的参考价值。
JSR-330 JAVA 依赖注入标准
文章目录
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.Qualifier
public @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实例:
@Singleton
class Log
void log(String message) ...
如果注入器在同一个类上遇到一个以上的范围注解,或者遇到它不支持的范围注解,就会产生一个错误。
一个范围注解:
- 是用
@Scope
、@Retention(RUNTIME)
和典型的@Documented
注解的。 - 不应该有属性。
- 通常没有
@Inherited
,所以范围是与实现的继承正交的。 - 如果用
@Target
来注解,可能会限制使用。虽然本规范只涵盖了对类的作用域的应用,但一些注入器配置可能在其他地方使用作用域注释(例如在工厂方法结果上)。
For example:
@java.lang.annotation.Documented
@java.lang.annotation.Retention(RUNTIME)
@javax.inject.Scope
public @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说明的主要内容,如果未能解决你的问题,请参考以下文章
SpringBoot启动提示JSR-330 javax.inject.Inject
可以在JSR 330中使@Inject成为可选项(如@Autowire(required = false)吗?