JSR-330 JAVA 依赖注入标准API说明
admin
2024-02-25 16:42:09
0

JSR-330 JAVA 依赖注入标准

文章目录

  • JSR-330 JAVA 依赖注入标准
    • Package javax.inject
    • @Inject注解
    • @Qualifier注解
    • `Interface Provider`
    • @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 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 seatProvider;@Inject void install(@Tinted Windshield windshield,@Big Trunk trunk) { ... }}

如果一个可注入方法覆盖另一个方法,覆盖方法的参数不会自动继承被覆盖方法的参数的限定词。

Injectable Values

对于一个给定的类型T和可选的限定符,一个注入器必须能够注入一个用户指定的类,该类。

  1. 与T的赋值兼容,并且
  2. 有一个可注入的构造函数。

例如,用户可以使用外部配置来选择T的实现。除此之外,哪些值被注入取决于注入器的实现和它的配置。

Circular Dependencies

检测和解决循环依赖是留给注入器实现的一个练习。两个构造函数之间的循环依赖是一个明显的问题,但你也可以在可注入的字段或方法之间有一个循环依赖。

  class A {@Inject B b;}class B {@Inject A a;}

当构造一个A的实例时,一个“天真”的注入器实现可能会进入一个无限的循环,构造一个B的实例来设置在A上,第二个A的实例来设置在B上,第二个B的实例来设置在第二个A的实例上,等等。

“保守”的注入器可能会在构建时检测到循环依赖并产生错误,此时程序员可以通过注入ProviderProvider代替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类型,你也可以注入Provider。与直接注入T相比,注入Provider可以:

  • 可以返回多个实例。
  • 实例的返回可以延迟化或可选的检索一个实例。
  • 打破循环依赖关系。
  • 抽象作用域,可以在一个已知的作用域中查询一个作用域更小的实例。

For example:

   class Car {@Inject Car(Provider 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配置

jakarta.injectjakarta.inject-api2.0.1.MR

Gradle配置

implementation 'jakarta.inject:jakarta.inject-api:2.0.1.MR'

相关内容

热门资讯

明天生效!美国CPSC新规全面... 7月8日起(明天),美国消费品安全委员会(CPSC)将联合美国海关与边境保护局(CBP),正式全面实...
北京挂牌顺义、房山2宗地 起始... 观点网讯:7月7日,北京市规划和自然资源委员会公告挂牌2宗地块,分别为房山区良乡大学城主园区FS00...
平桂区财政局:打好“三贷一库”... 近年来,平桂区财政局紧扣黄金珠宝全产业链发展脉搏,针对企业融资“痛点”精准施策,联合金融机构创新推出...
红熊AI营销获客系统V1.3.... 在流量红利见顶、获客成本持续攀升的当下,企业增长的命题始终聚焦:如何用较低的成本,获取更高质量的目标...
原创 为... 众所周知,上海的高房价是全国出了名的。即使是市中心区域的老破小,房价也要达到3-400万,如果是好一...
企业用了两年AI,为什么财报没... 作者:王聪彬 去年零一万物创始人、CEO 李开复就在强调,AI落地打法,要从“一把手工程”开始。 一...
韩国股市暴跌触发熔断机制 △韩国证券交易所(资料图) 韩国综合股价指数(KOSPI)7日盘中一度大跌超8%,触发市场熔断机制。...
爱柯迪跌3.11%,成交额1.... 来源:新浪证券-红岸工作室 7月7日,爱柯迪跌3.11%,成交额1.57亿元,换手率1.08%,总市...
高善文去世,享年55岁 记者据多方证实,国投证券前首席经济学家高善文因病去世,享年55岁。 高善文被公认为是中国资本市场上颇...
南昌黄金回收怎么选更安心 附近... 近年来随着黄金价格波动,不少南昌市民有处置闲置黄金的需求,但黄金回收市场鱼龙混杂,不少用户曾遭遇套路...
SpaceX股票几乎回到首日开... 来源:环球市场播报 SpaceX股价今日下跌逾5%,至152美元以下,距离该火箭制造商在6月12日...
景泽生物三闯港股IPO:尚未实... 财通社——7月3日,景泽生物医药(合肥)股份有限公司(简称“景泽生物”)再次向香港交易所递交主板上市...
亚马逊拟发行债券募资至少250... 来源:环球市场播报 核心要点 消息人士向记者戴维・费伯透露,亚马逊计划通过债券发行募资至少 25...
一季净利暴涨3020%!又一存... 作者 | 铅笔道 松格 编辑 | 铅笔道 黄小贵 2025年前四个月,宏芯宇的净利润只有1.23亿元...
原创 暴... 2026年夏天,饮料这行的人普遍难受。以往一到六七月,仓库就得连轴转,货车排队装水,老板数钱数到手抽...
活力中国调研行|探访李政道研究... 东方网记者程琦7月7日报道:初夏的张江科学城,草木葱茏,创新活力跃动。2026年“活力中国调研行”上...
超5家上市银行密集“自购”,释... 下半年伊始,A股上市银行再度传来股东、高管增持的消息。 常熟银行日前接连发布两份公告称,为提升持股比...
经济学家高善文去世,享年55岁 7月7日晚,据新经济学家智库消息,著名经济学家高善文去世,享年55岁。 公开资料显示,高善文出生于1...
连板背后的资本棋局:华芯晶电实... 图片来源:视觉中国 蓝鲸新闻7月7日讯(记者 徐晓春)因筹划控制权变更停牌五个交易日后,阿科力(60...
AI高端对话:智能体“凉了”?... 智东西 作者 | 杨京丽 编辑 | 心缘 智东西7月7日报道,7月2日,2026中国智能体大会在杭州...