1.介绍
如何使用与依赖注入相关的注解,即 @Resource、@Inject 和 @Autowired 。 这些注解为类提供了一种解决依赖关系的声明方式:
@Autowired
ArbitraryClass arbObject;
与直接实例化它们相反(命令式方式)
ArbitraryClass arbObject = new ArbitraryClass();
三个注解中有两个属于 Java 扩展包:javax.annotation.Resource 和 javax.inject.Inject。 @Autowired 注解属于 org.springframework.beans.factory.annotation 包。
这些注解中的每一个都可以通过字段注入或 setter 注入来解决依赖关系。 接下来将使用一个简化但实际的示例来演示三个注解之间的区别,基于每个注解所采用的执行路径。
示例将重点介绍如何在集成测试期间使用三个注入。 测试所需的依赖可以是任意文件,也可以是任意类。
2.@Resource注解
@Resource 注解是 JSR-250 注解集合的一部分,并与 Jakarta EE 打包在一起。 此注解具有以下执行路径,按优先级列出:
这些执行路径适用于 setter 和字段注入。
2.1.字段注入
可以通过使用 @Resource 注解使实例变量通过字段注入来解决依赖关系。
2.1.1.按照名称匹配
将使用以下集成测试来演示按名称匹配字段注入:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader= AnnotationConfigContextLoader.class,
classes= ApplicationContextTestResourceNameType.class)
public class FieldResourceInjectionIntegrationTest {
@Resource(name="namedFile")
private File defaultFile;
@Test
public void givenResourceAnnotation_WhenOnField_ThenDependencyValid(){
assertNotNull(defaultFile);
assertEquals("namedFile.txt", defaultFile.getName());
}
}
上面代码,通过将 bean 名称作为属性值传递给 @Resource 注解来按名称解析依赖项:
@Resource(name="namedFile")
private File defaultFile;
此配置将使用按名称匹配的执行路径解析依赖项。 必须在 ApplicationContextTestResourceNameType 应用程序上下文中定义 bean namedFile。
请注意,bean id 和相应的引用属性值必须匹配:
@Configuration
public class ApplicationContextTestResourceNameType {
@Bean(name="namedFile")
public File namedFile() {
File namedFile = new File("namedFile.txt");
return namedFile;
}
}
如果未能在应用程序上下文中定义 bean,它将导致抛出 org.springframework.beans.factory.NoSuchBeanDefinitionException。 可以通过更改 ApplicationContextTestResourceNameType 应用程序上下文中传递给@Bean 注解的属性值,或更改传递给FieldResourceInjectionIntegrationTest集成测试中@Resource 注解的属性值来演示这一点。
2.1.2.按照类型匹配
为了演示按类型匹配的执行路径,只需删除 FieldResourceInjectionIntegrationTest集成测试中的(name=“namedFile”)代码部分:
@Resource
private File defaultFile;
测试仍然会通过,因为如果 @Resource 注解没有接收 bean 名称作为属性值,Spring Framework 将继续下一级优先级,按类型匹配,以尝试解决依赖关系。
2.1.3. 按照限定符号匹配
为了演示 match-by-qualifier 执行路径,将修改集成测试场景,以便在 ApplicationContextTestResourceQualifier 应用程序上下文中定义两个 bean:
@Configuration
public class ApplicationContextTestResourceQualifier {
@Bean(name="defaultFile")
public File defaultFile() {
File defaultFile = new File("defaultFile.txt");
return defaultFile;
}
@Bean(name="namedFile")
public File namedFile() {
File namedFile = new File("namedFile.txt");
return namedFile;
}
}
将使用 QualifierResourceInjectionTest 集成测试来演示按限定符匹配的依赖项解析。 在这种情况下,需要将特定的 bean 依赖项注入到每个引用变量中:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader= AnnotationConfigContextLoader.class,
classes= ApplicationContextTestResourceQualifier.class)
public class QualifierResourceInjectionIntegrationTest {
@Resource
@Qualifier("defaultFile")
private File dependency1;
@Resource
@Qualifier("namedFile")
private File dependency2;
@Test
public void givenResourceAnnotation_WhenField_ThenDependency1Valid(){
assertNotNull(dependency1);
assertEquals("defaultFile.txt", dependency1.getName());
}
@Test
public void givenResourceQualifier_WhenField_ThenDependency2Valid(){
assertNotNull(dependency2);
assertEquals("namedFile.txt", dependency2.getName());
}
}
测试表明,即使在应用程序上下文中定义了多个 bean,也可以使用 @Qualifier 批注通过将特定的依赖项注入到一个类中来清除任何混淆。
2.2. Setter注入
在字段上注入依赖项时采用的执行路径也适用于基于 setter 的注入。
2.2.1.通过名称匹配
唯一的区别是 MethodResourceInjectionTest 集成测试有一个 setter 方法:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader = AnnotationConfigContextLoader.class,
classes = ApplicationContextTestResourceNameType.class)
public class MethodResourceInjectionIntegrationTest {
private File defaultFile;
@Resource(name = "namedFile")
protected void setDefaultFile(File defaultFile) {
this.defaultFile = defaultFile;
}
@Test
public void givenResourceAnnotation_WhenSetter_ThenDependencyValid() {
assertNotNull(defaultFile);
assertEquals("namedFile.txt", defaultFile.getName());
}
}
引用变量相应 setter 方法,通过 setter 注入来解决依赖关系。 然后将 bean 依赖项的名称作为属性值传递给 @Resource 注解:
private File defaultFile;
@Resource(name="namedFile")
protected void setDefaultFile(File defaultFile) {
this.defaultFile = defaultFile;
}
在此示例中重用 namedFile bean 依赖项。 bean 名称和相应的属性值必须匹配。
当运行集成测试时,将是通过。
为了验证按名称匹配的执行路径是否解决了依赖关系,只需要将传递给 @Resource 注释的属性值更改为其他不存在的名称 并再次运行测试。 这一次,测试将失败并返回 NoSuchBeanDefinitionException。
2.2.2.按类型匹配
为了演示基于 setter 的、按类型匹配的执行,将使用 MethodByTypeResourceTest 集成测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader = AnnotationConfigContextLoader.class,
classes = ApplicationContextTestResourceNameType.class)
public class MethodResourceInjectionIntegrationTest {
private File defaultFile;
@Resource(name = "namedFile")
protected void setDefaultFile(File defaultFile) {
this.defaultFile = defaultFile;
}
@Test
public void givenResourceAnnotation_WhenSetter_ThenDependencyValid() {
assertNotNull(defaultFile);
assertEquals("namedFile.txt", defaultFile.getName());
}
}
运行此测试时,将是通过。
为了验证按类型匹配的执行路径是否解决了 File 依赖项,需要将 defaultFile 变量的类类型更改为另一种类类型,如 String。 再次执行 MethodByTypeResourceTest 集成测试,这次会抛出 NoSuchBeanDefinitionException 。
该异常验证了按类型匹配确实用于解析文件依赖项。 NoSuchBeanDefinitionException 确认引用变量名称不需要与 bean 名称匹配。 相反,依赖解析取决于 bean 的类类型与引用变量的类类型相匹配。
2.2.2.按限定符匹配
将使用 MethodByQualifierResourceTest 集成测试来演示 :
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes= ApplicationContextTestResourceQualifier.class)
public class MethodByQualifierResourceIntegrationTest {
private File arbDependency;
private File anotherArbDependency;
@Test
public void givenResourceQualifier_WhenSetter_ThenValidDependencies(){
assertNotNull(arbDependency);
assertEquals("namedFile.txt", arbDependency.getName());
assertNotNull(anotherArbDependency);
assertEquals("defaultFile.txt", anotherArbDependency.getName());
}
@Resource
@Qualifier("namedFile")
public void setArbDependency(File arbDependency) {
this.arbDependency = arbDependency;
}
@Resource
@Qualifier("defaultFile")
public void setAnotherArbDependency(File anotherArbDependency) {
this.anotherArbDependency = anotherArbDependency;
}
}
测试表明,即使在应用程序上下文中定义了特定类型的多个 bean 实现,也可以将 @Qualifier 注解与 @Resource 注解一起使用来解决依赖关系。
类似于基于字段的依赖注入,如果在一个应用上下文中定义了多个bean,必须使用@Qualifier注解来指定使用哪个bean来解析依赖,否则会抛出NoUniqueBeanDefinitionException。
3.@Inject注解
@Inject 注解属于 JSR-330 注解集合。 此注解具有以下执行路径,按优先级列出:
引入maven依赖
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
3.1.字段注入
3.1.1. 按类型匹配
将修改集成测试示例以使用另一种类型的依赖项,即 ArbitraryDependency 类。 ArbitraryDependency 类依赖只是作为一个简单的依赖:
@Component
public class ArbitraryDependency {
private final String label = "Arbitrary Dependency";
public String toString() {
return label;
}
}
@Configuration
public class ApplicationContextTestInjectType {
@Bean
public ArbitraryDependency injectDependency() {
ArbitraryDependency injectDependency = new ArbitraryDependency();
return injectDependency;
}
}
集成测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader= AnnotationConfigContextLoader.class,
classes=ApplicationContextTestInjectType.class)
public class FieldInjectIntegrationTest {
@Inject
private ArbitraryDependency fieldInjectDependency;
@Test
public void givenInjectAnnotation_WhenOnField_ThenValidDependency(){
assertNotNull(fieldInjectDependency);
assertEquals("Arbitrary Dependency",
fieldInjectDependency.toString());
}
}
与首先按名称解析依赖项的@Resource 注解不同,@Inject 注解的默认行为是按类型解析依赖项。
这意味着即使类引用变量名称与 bean 名称不同,只要 bean 是在应用程序上下文中定义的,依赖关系仍将被解析。 请注意以下测试中的引用变量名称:
@Inject
private ArbitraryDependency fieldInjectDependency;
与应用程序上下文中配置的 bean 名称不同:
@Bean
public ArbitraryDependency injectDependency() {
ArbitraryDependency injectDependency = new ArbitraryDependency();
return injectDependency;
}
3.1.2.按照限定符匹配
如果特定类类型有多个实现,并且某个类需要特定的 bean 怎么办? 修改集成测试示例,使其需要另一个依赖项。
在此示例中,将在按类型匹配示例中使用的 ArbitraryDependency 类子类化,以创建 AnotherArbitraryDependency 类:
@Component
public class AnotherArbitraryDependency extends ArbitraryDependency {
private final String label = "Another Arbitrary Dependency";
@Override
public String toString() {
return label;
}
}
@Configuration
public class ApplicationContextTestInjectQualifier {
@Bean
public ArbitraryDependency defaultFile() {
ArbitraryDependency defaultFile = new ArbitraryDependency();
return defaultFile;
}
@Bean
public ArbitraryDependency namedFile() {
ArbitraryDependency namedFile = new AnotherArbitraryDependency();
return namedFile;
}
}
集成测试代码:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader= AnnotationConfigContextLoader.class,
classes= ApplicationContextTestInjectQualifier.class)
public class FieldQualifierInjectIntegrationTest {
@Inject
private ArbitraryDependency defaultDependency;
@Inject
private ArbitraryDependency namedDependency;
@Test
public void givenInjectQualifier_WhenOnField_ThenDefaultFileValid(){
assertNotNull(defaultDependency);
assertEquals("Arbitrary Dependency",
defaultDependency.toString());
}
@Test
public void givenInjectQualifier_WhenOnField_ThenNamedFileValid(){
assertNotNull(defaultDependency);
assertEquals("Another Arbitrary Dependency",
namedDependency.toString());
}
}
如果应用程序上下文中有多个特定类的实现,并且在集成测试尝试以方式注入依赖项,则会抛出 NoUniqueBeanDefinitionException:
@Inject
private ArbitraryDependency defaultDependency;
@Inject
private ArbitraryDependency namedDependency;
抛出这个异常是 Spring Framework 的一种方式,它指出某个类有多个实现,并且不知道该使用哪个.
可以将所需的 bean 名称传递给 @Qualifier 注释,将其与 @Inject 注解一起使用。 这是代码块现在的样子:
@Inject
@Qualifier("defaultFile")
private ArbitraryDependency defaultDependency;
@Inject
@Qualifier("namedFile")
private ArbitraryDependency namedDependency;
@Qualifier 注解在接收 bean 名称时需要严格匹配。 必须确保将 bean 名称正确传递给 Qualifier,否则将抛出 NoUniqueBeanDefinitionException。 如果再次运行测试,会通过。
3.1.3.按名称匹配
用于演示按名称匹配的 FieldByNameInjectTest 集成测试类似于按类型执行路径的匹配。 唯一的区别是现在需要一个特定的 bean,而不是一个特定的类型。 在这个例子中,再次继承 ArbitraryDependency 类以生成 YetAnotherArbitraryDependency 类:
public class YetAnotherArbitraryDependency extends ArbitraryDependency {
private final String label = "Yet Another Arbitrary Dependency";
@Override
public String toString() {
return label;
}
}
@Configuration
public class ApplicationContextTestInjectName {
@Bean
public ArbitraryDependency yetAnotherFieldInjectDependency() {
ArbitraryDependency yetAnotherFieldInjectDependency =
new YetAnotherArbitraryDependency();
return yetAnotherFieldInjectDependency;
}
}
集成单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader = AnnotationConfigContextLoader.class,
classes = ApplicationContextTestInjectName.class)
public class FieldByNameInjectIntegrationTest {
@Inject
@Named("yetAnotherFieldInjectDependency")
private ArbitraryDependency yetAnotherFieldInjectDependency;
@Test
public void givenInjectQualifier_WhenSetOnField_ThenDependencyValid() {
assertNotNull(yetAnotherFieldInjectDependency);
assertEquals("Yet Another Arbitrary Dependency",
yetAnotherFieldInjectDependency.toString());
}
}
为了验证是否通过按名称匹配执行路径注入了依赖项,我们需要将传递给 @Named 注释的值 YetAnotherFieldInjectDependency 更改为我们选择的另一个名称。 当再次运行测试时,将抛出 NoSuchBeanDefinitionException。
3.2. Setter注入
@Inject注解基于setter的注入与@Resource基于setter的注入的方法类似。
4.@Autowired注解
@Autowired 注解的行为类似于@Inject 注释。 唯一的区别是@Autowired 注解是 Spring 框架的一部分。 该注解与@Inject注解具有相同的执行路径,按优先顺序列出:
这些执行路径适用于 setter 和字段注入。
4.1字段注入
4.1.1.按照类型匹配
用于演示@Autowired 按照类型匹配 执行路径的集成测试示例类似于用于@Inject按照类型匹配 执行路径的测试。 使用以下 FieldAutowiredIntegrationTest集成测试来演示使用 @Autowired 注释的按类型匹配:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes= ApplicationContextTestAutowiredType.class)
public class FieldAutowiredIntegrationTest {
@Autowired
private ArbitraryDependency fieldDependency;
@Test
public void givenAutowired_WhenSetOnField_ThenDependencyResolved() {
assertNotNull(fieldDependency);
assertEquals("Arbitrary Dependency", fieldDependency.toString());
}
}
集成测试应用上下文:
@Configuration
public class ApplicationContextTestAutowiredType {
@Bean
public ArbitraryDependency autowiredFieldDependency() {
ArbitraryDependency autowiredFieldDependency =
new ArbitraryDependency();
return autowiredFieldDependency;
}
}
为了确认确实使用 按照类型 执行路径解决依赖关系,需要更改 fieldDependency 引用变量的类型并再次运行集成测试。 这一次,FieldAutowiredIntegrationTest集成测试将失败,并抛出 NoSuchBeanDefinitionException。 这验证了使用 按照类型 来解决依赖关系。
4.1.2.按照限定符匹配
需要在应用程序上下文中定义多个 bean 实现:
@Configuration
public class ApplicationContextTestAutowiredQualifier {
@Bean
public ArbitraryDependency autowiredFieldDependency() {
ArbitraryDependency autowiredFieldDependency =
new ArbitraryDependency();
return autowiredFieldDependency;
}
@Bean
public ArbitraryDependency anotherAutowiredFieldDependency() {
ArbitraryDependency anotherAutowiredFieldDependency =
new AnotherArbitraryDependency();
return anotherAutowiredFieldDependency;
}
}
如果我们执行以下 FieldQualifierAutowiredIntegrationTest 集成测试,将抛出 NoUniqueBeanDefinitionException:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader= AnnotationConfigContextLoader.class,
classes=ApplicationContextTestAutowiredQualifier.class)
public class FieldQualifierAutowiredIntegrationTest {
@Autowired
private ArbitraryDependency fieldDependency1;
@Autowired
private ArbitraryDependency fieldDependency2;
@Test
public void givenAutowiredQualifier_WhenOnField_ThenDep1Valid(){
assertNotNull(fieldDependency1);
assertEquals("Arbitrary Dependency", fieldDependency1.toString());
}
@Test
public void givenAutowiredQualifier_WhenOnField_ThenDep2Valid(){
assertNotNull(fieldDependency2);
assertEquals("Another Arbitrary Dependency",
fieldDependency2.toString());
}
}
异常是由于应用程序上下文中定义的两个 bean 造成的歧义。 Spring 框架不知道应该将哪个 bean 依赖项自动装配到哪个引用变量。 可以通过添加 @Qualifier 注解来解决这个问题:
注入调整为以下方式:
@Autowired
@Qualifier("autowiredFieldDependency")
private ArbitraryDependency fieldDependency1;
@Autowired
@Qualifier("anotherAutowiredFieldDependency")
private ArbitraryDependency fieldDependency2;
4.1.3.按照名称匹配
演示使用@Autowired 注解注入字段依赖项的按名称匹配执行路径。 当按名称自动装配依赖项时,@ComponentScan 注解必须与应用程序上下文 ApplicationContextTestAutowiredName 一起使用:
@Configuration
@ComponentScan(basePackages={"com.spring.demo.di"})
public class ApplicationContextTestAutowiredName {
}
使用@ComponentScan 注解在包中搜索已经用@Component 注解注释的Java 类。 例如,在应用程序上下文中,将扫描 "com.spring.demo.di包以查找已使用 @Component 注解的类。 在这种情况下,Spring 框架必须检测 ArbitraryDependencyByName类,该类具有 @Component 注释:
@Component(value="autowiredFieldDependency")
public class ArbitraryDependencyByName {
private final String label = "Arbitrary Dependency";
@Override
public String toString() {
return label;
}
}
传递给@Component 注解的属性值 autowiredFieldDependency 告诉 Spring Framework ArbitraryDependencyByName类是一个名为 autowiredFieldDependency 的组件。 为了让@Autowired 注解通过名称解析依赖,组件名称必须与FieldAutowiredNameIntegrationTest集成测试中定义的字段名称相对应;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader= AnnotationConfigContextLoader.class,
classes=ApplicationContextTestAutowiredName.class)
public class FieldAutowiredNameIntegrationTest {
@Autowired
private ArbitraryDependency autowiredFieldDependency;
@Test
public void givenAutowiredAnnotation_WhenOnField_ThenDepValid(){
assertNotNull(autowiredFieldDependency);
assertEquals("Arbitrary Dependency",
autowiredFieldDependency.toString());
}
}
4.2. Setter注入
@Autowired 注解的基于 Setter 的注入类似于 @Resource 基于 setter 的注入方法。
5.应用这些注解
这就提出了应该在何种情况下使用何种注解的问题。这些问题的答案取决于所涉及的应用程序所面临的设计场景,以及开发人员希望如何基于每个注解的默认执行路径利用多态性。
5.1. 通过多态实现单例的广泛应用
如果设计使应用程序行为基于接口或抽象类的实现,并且这些行为在整个应用程序中使用,那么可以使用@Inject 或@Autowired 注解。
这种方法的好处是,当升级应用程序或应用补丁以修复错误时,可以在对整个应用程序行为的负面影响最小的情况下换出类。 在这种情况下,主要的默认执行路径是按类型匹配。
5.2. 通过多态进行细粒度的应用行为配置
如果设计是应用程序具有复杂的行为,每个行为都基于不同的接口/抽象类,并且这些实现中的每一个的用法因应用程序而异,那么可以使用@Resource 注解。 在这种情况下,主要的默认执行路径是按名称匹配。
5.3. 依赖注入应该由 Jakarta EE 平台单独处理
如果 Jakarta EE Platform 有一个设计要求注入所有依赖项而不是 Spring,那么选择是在 @Resource 注解和 @Inject 注解之间。 应该根据需要哪个默认执行路径来缩小两个注解之间的最终决定范围。
5.4.依赖注入应该由 Spring 框架单独处理
如果要求 Spring 框架处理所有依赖项,则唯一的选择是 @Autowired 注解。
5.5. 讨论总结
场景 | @Resource | @Inject | @Autowired |
---|
通过多态实现单例的广泛应用 | ? | ? | ? | 通过多态进行细粒度的应用行为配置 | ? | ? | ? | 依赖注入应该由 Jakarta EE 平台单独处理 | ? | ? | ? | 依赖注入应该由 Spring 框架单独处理 | ? | ? | ?ge |
|