静下心来的个人专栏
上一篇

依赖注入(一次搞懂):@Autowired、@Resource 和 @Inject

广告
选中文字可对指定文章内容进行评论啦,→和←可快速切换按钮,绿色背景文字可以点击查看评论额。
大纲

依赖注入:@Autowired、@Resource 和 @Inject

在 Spring Framework 中,您基本上可以使用三个注解中的任何一个进行依赖注入,即@Autowired、@Resource 和@Inject。@Autowired 注解属于 core-spring,而另外两个属于 Java 扩展包@javax.annotation.Resource 和@javax.inject.Inject。

我们将通过一个实际用例研究这些注释中的每一个的使用,以帮助您选择最适合您的注释。

常见示例

我将使用同一组类来理解使用@Resource、@Inject 和@Autowired 的注入。如您所见,抽象类 FileReader 扩展了 2 个类,PdfFileReader 和 WordFileReader。下面的示例用于探索所有 3 种情况下的连接(依赖注入)。

package basic.ioc.wiring;

public abstract class FileReader {
  public void print() {
    System.out.println("Inside FileReader");
  }
}
package basic.ioc.wiring;

import org.springframework.stereotype.Component;

@Component
public class PdfFileReader extends FileReader {

  @Override
  public void print() {
    System.out.println("Inside PdfFileReader");
  }
}
package basic.ioc.wiring;

import org.springframework.stereotype.Component;

@Component
public class WordFileReader extends FileReader {

  @Override
  public void print() {
    System.out.println("Inside WordFileReader");
  }
}
package basic.ioc.wiring;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@ComponentScan("basic.ioc.wiring")
@Configuration
public class ConfigWiring {
  //extra configs goes here
}

ConfigWiring 中的@Configuration 注解表明这是用作bean 定义的来源。正如您正确地观察到的, FileReader 没有用 @Component 注释,因为它是一个抽象类。有两个扩展 FileReader 的具体类,PdfFileReader 和 WordFileReader。 Spring 提供了构造型 @Component 注释,它将类注册为 Spring 托管组件。

1、@Resource – 使用 JSR-250 进行依赖注入

@Resource 注解来自 JSR-250 规范,这意味着它来自 javax.annotation.Resource。如果您使用的是 JDK8+,请确保在您的 maven/Gradle 文件中添加 JSR-250 依赖 jar。

<!-- JSR 250 -->
<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

Resource类定义如下所示。如您所见,它接受两个重要参数,即类型和名称。 @Target({TYPE, FIELD, METHOD}) 指示可以使用@Resource 注释的位置。

package javax.annotation;

@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
@Repeatable(Resources.class)
public @interface Resource {

    String name() default "";
    Class<?> type() default java.lang.Object.class;
    boolean shareable() default true;
    String mappedName() default "";
    String description() default "";
    //... Other methods skipped
}

使用@Resource 注解时请记住以下内容

1、您只能在字段或 bean 属性设置器上使用 @Resource 注释。

2、换句话说,@Resource 注解永远不能用来实现构造函数注入。

3、@Resource 注解进行依赖注入的优先级是: 按名称匹配 按类型匹配 由@Qualifier 匹配。

4、@Resource 注解采用 2 个重要的可选参数名称和类型。如果没有明确指定名称,则默认名称派生自字段名称或 setter 方法。

5同样,如果没有明确指定类型,它将进行类型匹配并尝试解析它。

什么是依赖歧义

以下所有代码都使用上面的常见示例,其中 FileReader 是一个抽象类,并且有 2 个类 PdfFileReader 和 WordFileReader 扩展了 FileReader。

当我们想要注入依赖项时,我们可以使用 @Resource 注解来注解属性或其 setter 方法。当我们有歧义时,就会出现问题。下面的代码抛出 NoUniqueBeanDefinitionException,因为我们有 2 个扩展 FileReader 的类。

@SpringJUnitConfig(ConfigWiring.class)
public class ResourceTest {

  //Show the exception that gets reproduced
  @Resource
  private FileReader fReader;
}
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'basic.ioc.wiring.ResourceTest': 
Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: 
No qualifying bean of type 'basic.ioc.wiring.FileReader' available: expected single matching bean but found 2:
pdfFileReader,wordFileReader

上面的代码是我们需要以没有依赖歧义的方式编写代码的原因。

按名称解决依赖关系

为了便于理解,我在属性上使用了 @Resource 注释,出于性能原因,您应该在 setter 上使用它们。在下面的第一个示例中,字段名称 pdfFileReader 用作解析依赖关系的 bean 名称。

@SpringJUnitConfig(ConfigWiring.class)
public class ResourceTest {

  //Resolve by property name
  @Resource
  private FileReader pdfFileReader;

  @Test
  public void resolveByPropertyName() {
    Assert.assertNotNull(pdfFileReader);
    Assert.assertEquals(pdfFileReader.getClass(), PdfFileReader.class);
  }
}

您还可以显式指定 bean 名称,如下所示。

@SpringJUnitConfig(ConfigWiring.class)
public class ResourceTest {

  //Resolve by explicit name
  @Resource(name = "wordFileReader")
  private FileReader reader;

  @Test
  public void resolveByExplicitName() {
    Assert.assertNotNull(reader);
    Assert.assertEquals(reader.getClass(), WordFileReader.class);
  }
}

按类型解决依赖关系

pring 将尝试通过名称解决依赖关系。但是,它不会找到任何具有此名称的 bean,因此接下来,它会尝试按类型解析。由于我们已经注册了 WordFileReader bean,spring 将自动检测此类型并解析此字段。

@SpringJUnitConfig(ConfigWiring.class)
public class ResourceTest {
 
 //Resolve by type auto detection
  @Resource
  private WordFileReader fileReader;

  @Test
  public void resolveByAutoType(){
    Assert.assertNotNull(fileReader);
    Assert.assertEquals(fileReader.getClass(), WordFileReader.class);
  }
}

在下面的代码中,Spring 既不能按名称检测依赖,也不能按类型解析。由于存在歧义,类型检测将失败并出现异常 BeanCreationException。所以我们会明确指定类型,type = PdfFileReader.class 来注入PdfFileReader。

@SpringJUnitConfig(ConfigWiring.class)
public class ResourceTest {

  //Resolve by explicit Type
  @Resource(type = PdfFileReader.class)
  private FileReader fileReader2;

  @Test
  public void resolveByExplicitType() {
    Assert.assertNotNull(fileReader2);
    Assert.assertEquals(fileReader2.getClass(), PdfFileReader.class);
  }
}

通过@Qualifier 解决依赖关系

spring 有这个特殊的 @Qualifier 注解,我们可以在其中传递要解析的 bean 的名称。

@SpringJUnitConfig(ConfigWiring.class)
public class ResourceTest {

  //Resolve by Qualifier
  @Qualifier("pdfFileReader")
  @Resource
  private FileReader myFileReader;

  @Test
  public void resolveByQualifier(){
    Assert.assertNotNull(myFileReader);
    Assert.assertEquals(myFileReader.getClass(), PdfFileReader.class);
  }
}

2、@Inject – 使用 JSR-330 进行依赖注入

@Inject 注解来自 JSR-330 规范,这意味着它来自 javax.inject.Inject。如果您使用的是 JDK8+,请确保在您的 pom/Gradle 文件中添加 JSR-330 依赖 jar。

<!-- JSR 330 -->
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

inject 注解如下所示,这意味着它可以用于基于字段、setter 或构造函数的注解。

package javax.inject;

@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
@Documented
public @interface Inject {}

使用 @Inject 注解时请记住以下几点:

1、@Inject 可以在 Setter、Field 或 Constructor 上使用来进行相应的类型注入。

2、此注释的优先级是: 按类型解析 Resolve by Qualifier – @Qualifier 注解 按名称解析 - @Named 注解

3Inject 注解不带任何参数。

按类型解决依赖关系

在下面的示例中,我在属性字段上使用了注释。但是在实际项目中,由于性能原因,您应该在 setter 或构造函数上使用它。

下面的例子 fileReader 是 PdfFileReader 类型的,所以依赖注入是直截了当的。

@SpringJUnitConfig(ConfigWiring.class)
public class InjectTest {

  //Inject by Type
  @Inject
  private PdfFileReader fileReader;

  @Test
  public void injectByType() {
    Assert.assertNotNull(fileReader);
    Assert.assertEquals(fileReader.getClass(), PdfFileReader.class);
  }
}

通过@Qualifier 注解解决

基类 FileReader 有 2 个实现,它们是 PdfFileReader 和 WordFileReader。要消除这种歧义,您需要在 @Qualifier 注释中指定 bean 名称。下面的代码@Qualifier("wordFileReader") 表示相同。

@SpringJUnitConfig(ConfigWiring.class)
public class InjectTest {

  //Inject by @Qualifier
  @Inject
  @Qualifier("wordFileReader")
  private FileReader fileReader3;

  @Test
  public void injectByQualifier() {
    Assert.assertNotNull(fileReader3);
    Assert.assertEquals(fileReader3.getClass(), WordFileReader.class);
  }
}

如果您不指定@Qualifier,您最终将重现如下异常。

SEVERE: Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@783a467b] to prepare test instance [basic.ioc.wiring.InjectTest@5b202a3a]
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'basic.ioc.wiring.InjectTest': Unsatisfied dependency expressed through field 'fileReader3'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'basic.ioc.wiring.FileReader' available: expected single matching bean but found 2: pdfFileReader,wordFileReader

按名称解决依赖关系

@Inject 注解通过 2 种方式检测要注入的 bean。首先,如果指定了@Named("beanName") 注解。否则,字段名称将用作 bean 名称。@Named 注释来自 JSR-330 javax.inject.* 包,它用于指定将注入其实例的 bean 名称。

第 6 行表示使用 @Named 注释的 bean 名称检测。另一方面,第 17 行表示按字段名称自动检测。

@SpringJUnitConfig(ConfigWiring.class)
public class InjectTest {

  //Inject by name with @Named annotation
  @Inject
  @Named("pdfFileReader")
  private FileReader fileReader2;

  @Test
  public void injectByName() {
    Assert.assertNotNull(fileReader2);
    Assert.assertEquals(fileReader2.getClass(), PdfFileReader.class);
  }

  //Inject by field Name
  @Inject
  private FileReader wordFileReader;

  @Test
  public void injectByName2() {
    Assert.assertNotNull(wordFileReader);
    Assert.assertEquals(wordFileReader.getClass(), WordFileReader.class);
  }
}

3、@Autowired – Spring 风格的依赖注入

@Autowired 注解类似于@Inject 注解。唯一的区别是@Inject 来自JSR-330 规范,而@Autowired 纯粹来自Spring 框架。

@Autowired 注释如下所示。正如你所看到的,@Autowired 可以用在构造函数、setter、字段和参数中。

package org.springframework.beans.factory.annotation;

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, 
ElementType.PARAMETER, ElementType.FIELD, 
ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {

	/**
	 * Declares whether the annotated dependency is required.
	 * <p>Defaults to {@code true}.
	 */
	boolean required() default true;

}

下面讨论的示例使用基于字段的依赖注入,但由于性能原因,您应该始终在应用程序中使用基于 setter 或基于构造函数的注入。

使用 @Autowired 注解时请记住以下几点:

1、@Autowired 可用于 Setter、Field、Constructor 或参数以执行相应的类型注入。

2、此注释的优先级是:

 按类型解析 

通过限定符解析——使用@Qualifier 

按名称解析

3@Autowired(required=true) 注释将 r​​equired 作为可选参数,默认情况下始终为 true。

按类型解决依赖关系

Spring首先尝试按其类型检测要注入的bean并进行DI。在下面的代码中,spring 只是注入 PdfFileReader 实例。

@SpringJUnitConfig(ConfigWiring.class)
public class AutowiredTest {

  //Inject by Type
  @Autowired
  private PdfFileReader pdfFileReader;

  @Test
  public void injectByType() {
    Assert.assertNotNull(pdfFileReader);
    Assert.assertEquals(pdfFileReader.getClass(), PdfFileReader.class);
  }
}

通过@Qualifier 解决依赖关系

当您有歧义时,使用@Qualifier 注解来指定要注入的 bean 名称。

@SpringJUnitConfig(ConfigWiring.class)
public class AutowiredTest {

  //Inject by @Qualifier
  @Autowired
  @Qualifier("wordFileReader")
  private FileReader fileReader;

  @Test
  public void injectByQualifier() {
    Assert.assertNotNull(fileReader);
    Assert.assertEquals(fileReader.getClass(), WordFileReader.class);
  }
}

按名称解决依赖关系

如前所述,pring @Autowired 注解可以通过字段名称解析依赖关系。在这个例子中,spring 注入了名为 wordFileReader 的 bean。

@SpringJUnitConfig(ConfigWiring.class)
public class AutowiredTest {

  //Inject by Field name
  @Autowired
  private FileReader wordFileReader;

  @Test
  public void injectByFieldName() {
    Assert.assertNotNull(wordFileReader);
    Assert.assertEquals(wordFileReader.getClass(), WordFileReader.class);
  }

}

版权声明:著作权归作者所有。

X

欢迎加群学习交流

联系我们