写点什么

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

作者:JAVA旭阳
  • 2023-01-28
    浙江
  • 本文字数:4871 字

    阅读完需:约 16 分钟

前言

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


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


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

如何使用 @Autowired 注解?

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


@Componentpublic 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)@Documentedpublic @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上面方法得到的集合。


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


@Overridepublic 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 旭阳】交流学习~~

发布于: 2023-01-28阅读数: 49
用户头像

JAVA旭阳

关注

欢迎关注个人公众号—— JAVA旭阳 2018-07-18 加入

旭阳,希望自己能像初升的太阳一样,对任何事情充满希望~~

评论

发布
暂无评论
都用过@Autowired,但你知道它是怎么实现的吗_Java_JAVA旭阳_InfoQ写作社区