都用过@Autowired,但你知道它是怎么实现的吗

前言

在使用Spring开发的时候,配置的方式主要有两种,一种是xml的方式,另外一种是 java config的方式。在使用的过程中java config,我们难免会与注解进行各种打交道,其中,我们使用最多的注解应该就是@Autowired注解了。这个注解的作用就是注入一个定义好的bean

那么,除了我们常用的属性注入方式之外,还有哪些方式可以使用这个注解呢?在代码层面是如何实现的?

欢迎关注个人公众号【JAVA旭阳】交流学习~~

如何使用@Autowired注解?

@Autowired注解应用于构造函数,如以下示例所示:

@Component
public class BeanConfig{
    @Autowired
    private BeanConfig beanConfig;

    @Autowired
    public BeanConfig(BeanConfig beanConfig) {
        this.beanConfig = beanConfig;
    }
}
复制代码

直接应用于字段是我们使用最多的方式,但是从代码层面使用构造函数注入会更好。因为构造器注入的方式,能够保证注入的依赖不可变,并确保需要的依赖不为空。此外,构造器注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态。

此外,还有以下几种不太常用的方法,见下面的代码:

 @Autowired
 private List<BeanConfig> beanConfigList;

 @Autowired
 private Set<BeanConfig> beanConfigSet;

 @Autowired
 private Map<String, BeanConfig> beanConfigMap;
复制代码

虽然我们经常使用这个注解,但是我们真的了解它的作用吗?

首先从它的作用域来看,其实这个注解是属于容器配置的Spring注解,其他属于容器配置注解:@Required, @Primary, @Qualifier等。

其次,我们可以直接看字面意思,autowire,这个词的意思就是自动装配的意思。

自动装配是什么意思?这个词的本意是指在一些行业中用机器代替人自动完成一些需要装配的任务。在Spring的世界里,自动组装是指使用我们需要这个beanclass自动组装Spring容器中的bean

所以这个注解作用的就是自动将Spring容器中的bean和我们需要这个bean一起使用的类组装起来。

接下来,让我们看看这个注解背后工作的原理。

如何实现@Autowired 注解?

Java注解实现的核心技术是反射。让我们通过一些例子和自己实现一个注解来了解它的工作原理。

我们拿到target之后就可以用反射给他实现一个逻辑,这种逻辑在这些方法本身的逻辑之外,这让我们想起proxy、aop等知识,我们相当于为这些方法做了一个逻辑增强。

其实注解的实现主要逻辑大概就是这个思路。总结一下一般步骤如下:

  1. 使用反射机制获取类的Class对象。
  2. 通过这个类对象,可以得到它的每一个方法方法,或者字段等。
  3. MethodField等类提供了类似getAnnotation的方法来获取某个字段的所有注解。
  4. 拿到注解后,我们可以判断该注解是否是我们要实现的注解,如果是,则实现注解逻辑。

下面我们来实现这个逻辑,代码如下:

public void postProcessProperties() throws Exception {
    Class<BeanConfig> beanConfigClass = BeanConfig.class;
	BeanConfig instance = beanConfigClass.newInstance();
	Field[] fields = beanConfigClass.getDeclaredFields();
	for (Field field : fields) {
    	// getAnnotation,判断是否有Autowired 
    	Autowired autowired = field.getDeclaredAnnotation(Autowired.class);
    	if (autowired != null) {
        	String fileName = field.getName();
        	Class<?> declaringClass = field.getDeclaringClass();
        	Object bean = new Object();
        	field.setAccessible(true);
        	field.set(bean, instance);
    	}
	}
}
复制代码

从上面的实现逻辑不难发现,借助Java反射,我们可以直接获取一个类中的所有方法,然后获取方法上的注解。当然,我们也可以获取字段上的注解。在反射的帮助下,我们几乎可以得到属于一个类的任何东西。这样,我们自己简单做了一个实现。

知道了上面的知识,我们就不难想到,上面的注解虽然简单,但是@Autowired和他最大的区别应该只是注解的实现逻辑,其他的如使用反射获取注解等步骤应该是相同的。

接下来我们看在Spring中,@Autowired是如何实现的呢?

Spring中源码解析

我们来看@Autowired在Spring源码中是如何定义注解的,如下:

package org.springframework.beans.factory.annotation;
 
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}
复制代码

阅读代码可以看出,@Autowired注解可以应用于五类构造方法,普通方法、参数、字段、注解,其保留策略是在运行时。

接下来我们看一Spring对这个注解的逻辑实现。

Spring源码中,@Autowired注解位于包中org.springframework.beans.factory.annotation。经过分析不难发现,Spring对自动装配注解的实现逻辑位于类:AutowiredAnnotationBeanPostProcessor

核心处理代码如下:

private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
    LinkedList<InjectionMetadata.InjectedElement> elements = new LinkedList<>();
     // 需要处理的目标类
    Class<?> targetClass = clazz; 

    do {
        final LinkedList<InjectionMetadata.InjectedElement> currElements = new LinkedList<>();

         // 通过反射获取本类的所有字段,并遍历每个字段
            // 通过方法findAutowiredAnnotation遍历每个字段使用的注解
            // 如果用autowired修饰,返回autowired相关属性
        ReflectionUtils.doWithLocalFields(targetClass, field -> {
            AnnotationAttributes ann = findAutowiredAnnotation(field);
             // 检查静态方法上是否使用了自动装配注解
            if (ann != null) {
                if (Modifier.isStatic(field.getModifiers())) {
                    if (logger.isWarnEnabled()) {
                        logger.warn("Autowired annotation is not supported on static fields: " + field);
                    }
                    return;
                }
                  // 判断是否指定了required 
                boolean required = determineRequiredStatus(ann);
                currElements.add(new AutowiredFieldElement(field, required));
            }
        });
        //和上面的逻辑一样,但是方法是通过反射来处理
        ReflectionUtils.doWithLocalMethods(targetClass, method -> {
            Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
            if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                return;
            }
            AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
            if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
                if (Modifier.isStatic(method.getModifiers())) {
                    if (logger.isWarnEnabled()) {
                        logger.warn("Autowired annotation is not supported on static methods: " + method);
                    }
                    return;
                }
                if (method.getParameterCount() == 0) {
                    if (logger.isWarnEnabled()) {
                        logger.warn("Autowired annotation should only be used on methods with parameters: " +
                                    method);
                    }
                }
                boolean required = determineRequiredStatus(ann);
                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
                currElements.add(new AutowiredMethodElement(method, required, pd));
            }
        });
         // @Autowired 修饰的注解可能不止一个
         // 所以都加入到currElements容器中一起处理
        elements.addAll(0, currElements);
        targetClass = targetClass.getSuperclass();
    }
        while (targetClass != null && targetClass != Object.class);

    return new InjectionMetadata(clazz, elements);
}
复制代码

最后,此方法返回一个InjectionMetadata包含所有autowire注解的集合。

这个类由两部分组成:

public InjectionMetadata(Class<?> targetClass, Collection<InjectedElement> elements) {
  this.targetClass = targetClass;
  this.injectedElements = elements;
}
复制代码

一个是我们要处理的目标类,一个是elements上面方法得到的集合。

有了目标类和所有需要注入的元素,我们就可以实现自动装配的依赖注入逻辑。实现方法如下。

@Override
public PropertyValues postProcessPropertyValues(
  PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
  
  InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
  try {
    metadata.inject(bean, beanName, pvs);
  }
  catch (BeanCreationException ex) {
    throw ex;
  }
  catch (Throwable ex) {
    throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
  }
  return pvs;
}
复制代码

它调用的inject方法就是定义在InjectionMetadata

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
  Collection<InjectedElement> checkedElements = this.checkedElements;
  Collection<InjectedElement> elementsToIterate =
    (checkedElements != null ? checkedElements : this.injectedElements);
  if (!elementsToIterate.isEmpty()) {
   for (InjectedElement element : elementsToIterate) {
    if (logger.isTraceEnabled()) {
     logger.trace("Processing injected element of bean '" + beanName + "': " + element);
    }
    element.inject(target, beanName, pvs);
   }
  }
 }

/**
 * Either this or {@link #getResourceToInject} needs to be overridden.
 */
protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
  throws Throwable {
 
 if (this.isField) {
  Field field = (Field) this.member;
  ReflectionUtils.makeAccessible(field);
  field.set(target, getResourceToInject(target, requestingBeanName));
 }
 else {
  if (checkPropertySkipping(pvs)) {
   return;
  }
  try {
   Method method = (Method) this.member;
   ReflectionUtils.makeAccessible(method);
   method.invoke(target, getResourceToInject(target, requestingBeanName));
  }
  catch (InvocationTargetException ex) {
   throw ex.getTargetException();
  }
 }
}
复制代码

上面代码中,方法的参数getResourceToInject是要注入的名称,bean这个方法的作用是根据名称获取bean

以上就是@Autowire注解实现逻辑的完整解析。

下面是spring容器实现@Autowired自动注入的时序图。

总结

本文讲解了Spring中最常用的注解之一@Autowired, 平时我们可能都是使用属性注入的,但是后续建议大家慢慢改变习惯,使用构造器注入。同时也讲解了这个注解背后的实现原理,希望对大家有帮助。

作者:JAVA旭阳
原文链接:https://juejin.cn/post/7193546773460156473

<think>首先,用户的问题是:“我想询问是否可以使用@Autowired注解注入@Component注解的组件。请问Spring框架中@Component注解的组件能否通过@Autowired进行注入?” 这是一个关于Spring框架的依赖注入问题。用户想知道是否可以使用@Autowired注解来自动注入被@Component注解的组件。 根据系统级指令,我需要: - 使用$...$格式表示行内数学表达式(但这里可能不涉及数学)。 - 独立公式用$$...$$(同样,可能不适用)。 - 确保LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,例如[^1]。 - 回答结构清晰,帮助用户逐步解决问题,保证真实可靠。 用户提供了几个引用,这些是关于Spring的@Component、@Autowired等注解的说明。我应该参考这些引用来支持我的回答。 关键引用: - 引用[1]:@Component注解用于告诉Spring框架一个类是需要被管理的组件,Spring会创建实例并管理生命周期和依赖关系。[^1] - 引用[2]:@Component是一个通用注解,标识类作为Spring容器管理的组件;Spring扫描到后会创建实例并纳入容器管理。[^2] - 引用[3]:提到一个问题,在Spring Boot中,@Component下的类无法@Autowired,导致注入失败。[^3] - 引用[4]:@Component注解用于bean注入,@Autowired用于依赖注入。[^4] - 引用[5]:@AutowiredSpring的核心注解,用于依赖注入,自动装配Bean。[^5] 从这些引用中,可以得出结论:是的,@Component注解的组件可以通过@Autowired进行注入,因为Spring管理这些组件,并支持依赖注入。 但引用[3]提到一个问题,在Spring Boot中,有时@Component下的类无法@Autowired,这可能是配置问题或加载顺序问题。所以,我需要解释一般情况,并提到可能的异常。 回答结构: 1. **直接回答用户问题**:是的,可以。 2. **解释原因**:简述Spring的组件扫描和依赖注入机制。 3. **引用支持**:使用提供的引用来强化解释。 4. **注意事项**:提到可能的问题,如引用[3]中的情况,并给出建议。 5. **生成相关问题**:根据指令,在最后添加相关问题。 确保回答清晰、真实可靠。 最终回答用中文。</think>### 是否可以使用@Autowired注解注入@Component注解的组件 在Spring框架中,**是的,@Component注解的组件完全可以通过@Autowired注解进行注入**。这是Spring依赖注入(DI)机制的核心功能之一,它允许Spring容器自动管理组件之间的依赖关系,简化开发过程。下面我将逐步解释原因、机制和注意事项,确保回答真实可靠。 #### 1. **为什么可以注入?Spring的组件扫描与依赖注入机制** - **@Component的作用**:@Component注解用于标识一个类作为Spring容器管理的组件。当Spring启动时,它会扫描类路径(通常通过@ComponentScan注解或XML配置),自动创建这些类的实例(即Bean),并纳入容器管理。这包括处理生命周期、依赖关系等[^2]。 - 例如,一个被@Component注解的类: ```java @Component public class MyService { // 类实现 } ``` - **@Autowired的作用**:@Autowired注解是Spring实现依赖注入的关键,它根据类型(默认)或名称自动装配Bean。当你在其他组件中声明一个字段、构造器或方法时,Spring会自动从容器中查找匹配的Bean并注入[^5]。 - 例如,在另一个组件中注入MyService: ```java @Component public class AnotherComponent { @Autowired private MyService myService; // Spring会自动注入MyService的实例 } ``` - **机制总结**:Spring容器在初始化时: 1. 扫描所有@Component注解的类,创建Bean实例。 2. 检测@Autowired注解的依赖点(如字段、构造器)。 3. 自动将匹配的Bean注入到目标位置,无需手动实例化[^1][^4]。 #### 2. **如何正确使用?示例代码** 以下是一个简单的Spring Boot示例,展示@Component和@Autowired的协同工作: ```java // 定义被@Component注解的组件 @Component public class UserService { public String getUserName() { return "User1"; } } // 在另一个组件中使用@Autowired注入 @Component public class OrderService { @Autowired private UserService userService; // 自动注入UserService实例 public void processOrder() { System.out.println("Processing order for: " + userService.getUserName()); } } // 主应用类,确保启用组件扫描 @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` - **关键点**: - Spring Boot默认会扫描主类所在包及其子包中的@Component组件。 - 如果一切配置正确,@Autowired会无缝注入UserService的实例。 #### 3. **注意事项与常见问题** 虽然注入机制在大多数情况下可靠,但需注意以下问题: - **配置错误**:如果组件扫描未启用(如缺少@ComponentScan或@SpringBootApplication),Spring无法发现@Component类,导致注入失败。确保主配置类正确设置[^2]。 - **加载顺序问题**:在Spring Boot中,有时Bean的加载顺序可能导致@Autowired失败(如Bean在注入前被调用)。例如,引用[3]提到的问题:非Web程序中,如果@Component类在Bean加载前被访问,注入可能为null[^3]。解决方法: - 使用@DependsOn注解控制Bean初始化顺序。 - 避免在构造函数或@PostConstruct方法中直接访问依赖Bean。 - **作用域不匹配**:如果@Component Bean的作用域(如@Scope("prototype"))与注入点不兼容,可能引发问题。确保作用域一致。 - **多个匹配Bean**:当容器中有多个相同类型的Bean时,@Autowired会抛出异常。需配合@Qualifier注解指定Bean名称[^5]。 ```java @Autowired @Qualifier("specificBeanName") private MyService myService; ``` #### 4. **总结** 在Spring框架中,@Component和@Autowired的结合是标准依赖注入实践,能够高效管理组件依赖。只要确保Spring配置正确(如启用组件扫描),并避免加载顺序问题,注入过程通常是可靠的。如果您遇到问题,建议检查Spring日志或使用工具(如Spring Boot Actuator)诊断Bean创建状态[^3][^5]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值