Autowried和Resource注解总结

1、掌握@Autowried和@Resource

在Spring框架中,@Autowired@Resource都是用于实现依赖注入(Dependency Injection, DI)的核心注解,它们可以自动将所需的Bean装配到目标组件中。尽管功能相似,但它们在使用方法、来源和注入机制上存在显著差异。

@Autowired 的使用方法

@Autowired 是 Spring 框架提供的注解,它默认按照类型 (byType) 在应用上下文中查找匹配的Bean进行注入。

基本用法

@Autowired 可以标注在构造方法、成员变量、Setter方法或任意配置方法上,以实现自动装配。

  • 属性注入(Field Injection):这是最简洁和常见的使用方式。

    @Component
    public class MyService {
        @Autowired
        private MyRepository myRepository;
    
        // ...
    }
    
  • 构造方法注入(Constructor Injection):Spring团队推荐使用此方式,因为它可以确保依赖在对象实例化时就完全可用,并且可以轻松地将依赖声明为final

    @Component
    public class MyService {
        private final MyRepository myRepository;
    
        @Autowired
        public MyService(MyRepository myRepository) {
            this.myRepository = myRepository;
        }
    }
    

    注意:从 Spring 4.3 版本开始,如果类中只有一个构造方法,@Autowired 注解可以省略。

  • Setter方法注入(Setter Injection):通过Setter方法进行注入。

    @Component
    public class MyService {
        private MyRepository myRepository;
    
        @Autowired
        public void setMyRepository(MyRepository myRepository) {
            this.myRepository = myRepository;
        }
    }
    

处理多个同类型Bean

当IOC容器中存在多个相同类型的Bean时,仅使用@Autowired会引发歧义,导致注入失败。此时可以结合@Qualifier注解,通过指定Bean的名称来精确注入。

public interface MessageService {
    String sendMessage(String message);
}

@Component("emailService")
public class EmailService implements MessageService {
    // ...
}

@Component("smsService")
public class SmsService implements MessageService {
    // ...
}

@Component
public class MessageProcessor {
    @Autowired
    @Qualifier("smsService")
    private MessageService messageService;
}

@Resource 的使用方法

@Resource 是 Java EE 的规范之一(JSR-250),并非Spring独有,但Spring提供了对它的支持。 它默认按照名称 (byName) 进行Bean的查找和注入。

注入顺序

@Resource 的注入顺序遵循以下规则:

  1. 按名称查找:首先会尝试使用指定的name属性去匹配Bean的ID。如果没有指定name属性,它会默认使用被注解的字段或方法名作为Bean的名称进行查找。
  2. 按类型查找:如果按名称无法找到匹配的Bean,@Resource会回退到按类型进行查找。
  3. 使用@Qualifier:如果按类型查找到多个候选Bean,它会进一步尝试匹配@Qualifier指定的名称。

如果最终找不到唯一匹配的Bean,程序会抛出异常。

基本用法

@Resource 通常标注在成员变量或Setter方法上。它不支持构造方法注入。

  • 属性注入

    • 不指定名称:默认使用字段名 myRepository 作为Bean的名称进行注入。
      @Component
      public class MyService {
          @Resource
          private MyRepository myRepository;
      }
      
    • 指定名称:通过name属性明确指定要注入的Bean的ID。
      @Component
      public class MessageProcessor {
          @Resource(name = "emailService")
          private MessageService messageService;
      }
      
  • Setter方法注入

    @Component
    public class MyService {
        private MyRepository myRepository;
    
        @Resource
        public void setMyRepository(MyRepository myRepository) {
            this.myRepository = myRepository;
        }
    }
    

@Autowired 与 @Resource 的核心区别

特性 @Autowired @Resource
来源 Spring 框架 Java EE (JSR-250 规范)
默认注入规则 按类型 (byType) 按名称 (byName)
注入顺序 1. 按类型查找。
2. 如果有多个匹配,再按变量名查找。
3. 可使用 @Qualifier 指定名称。
1. 按name属性查找。
2. 若无name属性,按字段/方法名查找。
3. 若按名称找不到,则回退到按类型查找。
支持的注入位置 构造方法、成员变量、Setter方法、配置方法、参数 成员变量、Setter方法、类
主要属性 required (boolean类型,默认为 true,表示是否必须注入成功) name (String类型,指定Bean的名称)
type (Class类型,指定Bean的类型)
兼容性 仅适用于Spring环境。 遵循Java标准,可在支持JSR-250的框架中使用,如Java EE环境。

总结来说,@Autowired@Resource 都是实现依赖注入的强大工具。@Autowired 是Spring生态中的首选,特别是当结合构造方法注入时,能更好地保证代码的健壮性。而 @Resource 提供了更强的语义(按名称获取资源),并且作为Java标准,具有更好的跨框架兼容性。

2、@Autowried和@Resource底层注入步骤

在 Spring 6 中,@Autowired@Resource 的底层注入步骤都深度集成在 Spring Bean 的生命周期中。这个过程的核心是 BeanPostProcessor 接口,它允许在 Bean 初始化前后插入自定义逻辑。

@Autowired@Resource 分别由两个不同的 BeanPostProcessor 实现来处理:

  • @AutowiredAutowiredAnnotationBeanPostProcessor 处理。
  • @ResourceCommonAnnotationBeanPostProcessor 处理。

尽管最终都实现了依赖注入,但它们的底层步骤和解析逻辑有所不同。

统一前置步骤:Bean生命周期

无论是哪个注解,注入都发生在 Bean 生命周期的特定阶段。一个简化的流程如下:

  1. 实例化 (Instantiation): Spring 容器通过构造函数或工厂方法创建 Bean 的一个原始实例。此时,Bean 仅仅是一个普通的 Java 对象,依赖项都还是 null
  2. 填充属性 (Populate Properties): 这是依赖注入发生的关键阶段。Spring 容器会调用相关的 BeanPostProcessor(如 AutowiredAnnotationBeanPostProcessor)来扫描 Bean,查找被 @Autowired@Resource 等注解标记的字段和方法,并从容器中查找、解析并注入所需的依赖。
  3. 初始化 (Initialization): 在属性填充完毕后,Spring 会执行进一步的初始化操作,例如调用 @PostConstruct 注解的方法或 afterPropertiesSet 方法。

接下来,我们将深入探讨在“填充属性”阶段,这两个注解各自的底层注入步骤。

@Autowired 的底层注入步骤

@Autowired 的处理由 AutowiredAnnotationBeanPostProcessor 负责,它是一个 InstantiationAwareBeanPostProcessor,这意味着它可以在实例化后、属性填充前介入。

其核心步骤如下:

  1. 寻找注入点:
    在 Bean 实例化之后,AutowiredAnnotationBeanPostProcessorpostProcessProperties 方法会被调用。此方法会扫描当前 Bean 的所有字段、方法和构造函数,查找被 @Autowired 注解标记的“注入点”(Injection Points)。找到的注入元数据(如需要注入的类型、@Qualifier 信息等)会被缓存起来,以提高后续处理效率。

  2. 解析依赖描述符:
    对于每一个注入点,Spring 会创建一个“依赖描述符”(DependencyDescriptor)。这个描述符包含了注入所需的所有上下文信息,例如:

    • 需要注入的 Bean 的类型。
    • 字段或参数的名称。
    • 是否是必需的 (required 属性)。
    • 是否存在 @Qualifier 或其他限定符注解。
  3. 在容器中查找候选 Bean:
    Spring 容器(BeanFactory)会使用这个依赖描述符来查找匹配的候选 Bean。这个查找过程遵循 @Autowired 的核心逻辑:

    • 按类型查找 (byType): 容器会首先根据依赖描述符中指定的类型,在自身以及父容器中查找所有匹配该类型的 Bean。
    • 处理多候选 Bean:
      • 如果只找到一个唯一的 Bean,那么它就是注入的目标,流程继续。
      • 如果找到了多个同类型的 Bean,Spring 会启动一个消歧义(disambiguation)流程:
        • @Primary 注解: 检查是否有候选 Bean 被标记为 @Primary。如果有,则优先选择它。
        • 变量名匹配: 如果没有 @Primary Bean,Spring 会将注入点的变量名或参数名作为 Bean 的名称,在候选者中进行匹配。如果匹配成功,则选择该 Bean。
        • @Qualifier 注解: 如果注入点上同时使用了 @Qualifier("beanName"),Spring 会直接根据 beanName 在所有候选 Bean 中精确查找。
  4. 注入依赖:
    一旦找到了唯一的候选 Bean,Spring 就会通过反射机制将其注入到目标位置。

    • 对于字段注入,使用 Field.set() 方法。
    • 对于 Setter 方法注入,调用该 Setter 方法并传入依赖 Bean。
  5. 处理注入失败:
    如果在整个查找过程中没有找到任何匹配的 Bean,并且 @Autowiredrequired 属性为 true(默认值),Spring 将会抛出 NoSuchBeanDefinitionExceptionUnsatisfiedDependencyException 异常,导致应用启动失败。如果 requiredfalse,则会跳过注入,该依赖保持为 null

@Resource 的底层注入步骤

@Resource 的处理由 CommonAnnotationBeanPostProcessor 负责,它同样也是一个 InstantiationAwareBeanPostProcessor。值得注意的是,在 Spring 6 中,@Resource 对应的是 jakarta.annotation.Resource 包路径,以兼容 Jakarta EE 9+ 规范。

其注入步骤如下:

  1. 寻找注入点:
    @Autowired 类似,CommonAnnotationBeanPostProcessor 也会在 Bean 实例化后扫描类,查找被 @Resource 注解标记的字段和方法,并缓存这些元数据。

  2. 确定要查找的 Bean 名称:
    @Resource 的核心是“按名称查找”。 它确定 Bean 名称的顺序是:

    • 检查 name 属性: 首先,检查 @Resource(name = "beanName") 中是否明确指定了 name 属性。如果指定了,就使用这个名称。
    • 使用字段名/属性名: 如果没有指定 name 属性,Spring 会默认使用被注解的字段名或 Setter 方法对应的属性名作为要查找的 Bean 名称。 例如,@Resource private MyService myService; 会查找名为 “myService” 的 Bean。
  3. 按名称在容器中查找:
    使用上一步确定的名称,直接在 Spring 容器中查找同名的 Bean。

    • 如果找到了一个名称匹配的 Bean,Spring 会检查其类型是否与注入点的类型兼容。如果兼容,则注入成功,流程结束。
    • 如果按名称没有找到 Bean,则会进入回退策略。
  4. 回退策略 (Fallback):
    这是 @Resource@Autowired 行为相似的地方。当按名称查找失败时,@Resource 会回退到按类型查找

    • 它会查找容器中与注入点类型唯一匹配的 Bean。
    • 如果按类型找到了唯一的 Bean,则注入该 Bean。
    • 如果按类型找到了多个候选 Bean,此时它会尝试使用字段名或属性名作为限定符再次进行匹配(类似于 @Autowired 的消歧义流程)。
  5. 注入依赖:
    找到唯一的 Bean 后,通过反射将其注入到目标字段或方法中。

  6. 处理注入失败:
    如果在所有步骤(按名称查找和按类型回退)之后,仍然无法找到一个唯一的、符合条件的 Bean,Spring 将会抛出异常,导致依赖注入失败。

3、@Autowried和@Resource和解决依赖注入冲突

在Spring框架中,当进行依赖注入时,如果容器中存在多个类型相同的Bean,就会产生注入冲突或歧义性(Ambiguity)。@Autowired@Resource本身提供了解决冲突的机制,此外Spring还支持其他几种方式来精确控制注入过程。

@Autowired 解决依赖注入冲突的方式

@Autowired 默认按类型(byType)注入。当发现多个相同类型的Bean时,它会按照以下顺序尝试解决冲突:

1. 使用 @Primary 注解

@Primary 注解可以被添加到Bean的定义上,用来标识在多个候选Bean中,哪一个应该是首选的。当@Autowired遇到多个同类型的Bean时,它会优先选择被@Primary标注的那个。

示例:
假设我们有一个MessageService接口和两个实现类。

public interface MessageService {
    String send();
}

@Component("emailService")
public class EmailServiceImpl implements MessageService {
    // ...
}

@Primary
@Component("smsService")
public class SmsServiceImpl implements MessageService {
    // ...
}

在注入时,smsService将会被自动注入,因为它被标记为@Primary

@Component
public class NotificationManager {
    // 这里会自动注入 SmsServiceImpl 的实例
    @Autowired
    private MessageService messageService;
}

这种方式的优点是提供了一个全局的默认选项,但如果需要注入非首选的Bean,则需要其他方式。

2. 使用 @Qualifier 注解

@Qualifier 注解与@Autowired配合使用,通过指定Bean的名称来精确选择要注入的Bean。这解决了@Primary只能有一个首选的问题,允许在不同的注入点选择不同的实现。

示例:
继续上面的例子,我们可以按名称注入emailService

@Component
public class NotificationManager {
    // 通过 @Qualifier 指定注入 id 为 "emailService" 的 Bean
    @Autowired
    @Qualifier("emailService")
    private MessageService messageService;
}

@Qualifier提供了最灵活、最精确的控制,是解决@Autowired注入冲突最常用的方法。

3. 变量名匹配

如果既没有使用@Primary也没有使用@Qualifier@Autowired会尝试将注入点的变量名作为Bean的ID在候选者中进行匹配。

示例:
如果变量名与某个Bean的ID(默认为类名首字母小写)一致,Spring会选择该Bean。

@Component
public class NotificationManager {
    // 变量名 "smsService" 匹配到了 Bean "smsService"
    // 因此会注入 SmsServiceImpl 的实例
    @Autowired
    private MessageService smsService;
}

这种方式依赖于代码命名规范,可读性较好,但在某些情况下可能不够明确。

@Resource 解决依赖注入冲突的方式

@Resource 默认按名称(byName)注入,因此它在设计上就天然地倾向于避免类型冲突。它的解析顺序本身就是一种解决冲突的机制。

1. 使用 name 属性

@Resource最直接的解决冲突方式是使用其name属性,直接指定要注入的Bean的ID。

示例:

@Component
public class NotificationManager {
    // 直接指定注入 id 为 "smsService" 的 Bean
    @Resource(name = "smsService")
    private MessageService messageService;
}```

这是`@Resource`最推荐和最明确的使用方式。

#### 2. 使用字段名/方法名匹配

如果`@Resource`没有指定`name`属性,它会默认使用被注解的字段名或方法对应的属性名作为Bean的名称进行查找。

**示例:**

```java
@Component
public class NotificationManager {
    // 字段名 "emailService" 将被用作 Bean 的名称进行查找
    @Resource
    private MessageService emailService;
}

这与@Autowired的变量名匹配机制非常相似,也是@Resource默认的核心工作模式。

3. 回退到按类型查找

只有当按名称完全找不到匹配的Bean时,@Resource才会回退到按类型查找。如果此时按类型找到了唯一的Bean,则注入成功。但如果按类型依然找到多个候选者,它会抛出NoUniqueBeanDefinitionException异常,因为它无法解决这种二级冲突。

通用的其他解决冲突方式

除了上述注解自带的功能外,还可以使用更通用的配置方式来处理注入冲突。

使用Java配置类中的@Qualifier

在通过@Bean方法定义Bean时,也可以结合@Qualifier来创建具有特定限定符的Bean。

@Configuration
public class AppConfig {

    @Bean
    @Qualifier("main")
    public MyService mainService() {
        return new MyServiceImplA();
    }

    @Bean
    @Qualifier("secondary")
    public MyService secondaryService() {
        return new MyServiceImplB();
    }
}

在注入时,可以使用@Qualifier("main")@Qualifier("secondary")来选择。

使用泛型作为限定符

如果你的Bean是泛型类型,Spring 6在处理泛型注入时更加智能,可以将具体的泛型类型作为一种限定条件。

示例:

// 定义泛型接口
public interface Storage<T> {
    void save(T data);
}

// 字符串存储实现
@Component
public class StringStorage implements Storage<String> {
    // ...
}

// 整数存储实现
@Component
public class IntegerStorage implements Storage<Integer> {
    // ...
}

// 注入时,Spring会根据泛型类型进行匹配
@Component
public class DataProcessor {
    // Spring能识别出这里需要 Storage<String> 类型的Bean
    @Autowired
    private Storage<String> stringStorage;
}

这种方式在处理具有层级或特定数据类型的服务时非常有用,代码也更加类型安全和清晰。

4、@Autowried和@Resource注解区别

@Autowired@Resource 注解区别的总结。

核心区别对比

特性 @Autowired @Resource
来源 Spring 框架自带的注解。 Java 的标准规范 (JSR-250),Spring 框架提供了对它的支持。在 Spring 6/Spring Boot 3 中,它来自 jakarta.annotation 包。
默认注入规则 按类型 (byType)。首先在 Spring 容器中查找与注入点类型匹配的 Bean。 按名称 (byName)。首先尝试根据名称在 Spring 容器中查找 Bean。
注入顺序与流程 1. 按类型查找匹配的 Bean。
2. 如果找到多个,则尝试将变量名作为 Bean ID 进行匹配。
3. 如果仍有歧义,需要配合 @Qualifier@Primary 来精确指定。
1. 首先检查 name 属性,使用其值作为 Bean ID 查找。
2. 如果 name 属性未指定,则使用字段名或方法名作为 Bean ID 查找。
3. 如果按名称完全找不到,则回退到按类型查找。
解决冲突方式 依赖 @Qualifier("beanName") 来指定名称,或依赖 @Primary 标注首选 Bean。 主要通过 name 属性 (@Resource(name = "beanName")) 来直接指定名称。
支持的注入位置 构造方法、成员变量、Setter 方法、任意配置方法。Spring 官方推荐使用构造方法注入 成员变量、Setter 方法。不支持构造方法注入
主要属性 required (boolean):默认为 true,表示依赖必须存在,否则抛出异常。 name (String):用于显式指定要注入的 Bean 的名称。
type (Class):用于指定要注入的 Bean 的类型(较少使用)。
代码耦合度 与 Spring 框架耦合度较高。如果更换为其他 DI 框架,此注解将失效。 与 Java 标准耦合,与具体框架解耦。在其他支持 JSR-250 规范的框架(如 Java EE 容器)中也能使用,移植性更好。

总结与选择建议

  • 优先选择 @Autowired:在纯粹的 Spring 应用中,@Autowired 是首选。它与 Spring 生态(如 @Primary、构造函数注入)的集成度更高,特别是构造函数注入方式,可以保证依赖的不可变性,是 Spring 官方推荐的最佳实践。

  • 何时使用 @Resource:当你希望代码有更好的移植性,或者想非常明确地通过名称来注入资源时,@Resource 是一个很好的选择。它的 (name = "...") 语法在语义上非常清晰,就是“我要获取名为 xxx 的资源”。

简单来说,可以这样记忆:

  • @Autowired:Spring 的“亲儿子”,默认按类型,功能更丰富,首选构造注入。
  • @Resource:Java 的“标准兵”,默认按名称,语义更明确,兼容性更强。