这篇文章,我们来谈一谈Spring中的属性注入(1)

简介: 这篇文章,我们来谈一谈Spring中的属性注入(1)

前言


在前面的文章中已经知道了Spring是如何将一个对象创建出来的,那么紧接着,Spring就需要将这个对象变成一个真正的Bean了,这个过程主要分为两步


1.属性注入

2.初始化

在这两个过程中,Bean的后置处理器会穿插执行,其中有些后置处理器是为了帮助完成属性注入或者初始化的,而有些后置处理器是Spring提供给程序员进行扩展的,当然,这二者并不冲突。整个Spring创建对象并将对象变成Bean的过程就是我们经常提到了Spring中Bean的生命周期。当然,本系列源码分析的文章不会再对生命周期的概念做过多阐述了,如果大家有这方面的需求的话可以参考我之前的文章,或者关注我的公众号:程序员DMZ


Spring官网阅读(九)Spring中Bean的生命周期(上)

Spring官网阅读(十)Spring中Bean的生命周期(下)


源码分析


闲话不再多说,我们正式进入源码分析阶段,本文重点要分析的方法就是org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean,其源码如下:


doCreateBean

  protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
      throws BeanCreationException {
    // 创建对象的过程在上篇文章中我们已经介绍过了,这里不再赘述
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    if (instanceWrapper == null) {
      instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
        // 获取到创建的这个对象
    final Object bean = instanceWrapper.getWrappedInstance();
    Class<?> beanType = instanceWrapper.getWrappedClass();
    if (beanType != NullBean.class) {
      mbd.resolvedTargetType = beanType;
    }
    // Allow post-processors to modify the merged bean definition.
        // 按照官方的注释来说,这个地方是Spring提供的一个扩展点,对程序员而言,我们可以通过一个实现了MergedBeanDefinitionPostProcessor的后置处理器来修改bd中的属性,从而影响到后续的Bean的生命周期
        // 不过官方自己实现的后置处理器并没有去修改bd,而是调用了applyMergedBeanDefinitionPostProcessors方法
        // 这个方法名直译过来就是-应用合并后的bd,也就是说它这里只是对bd做了进一步的使用而没有真正的修改
    synchronized (mbd.postProcessingLock) {
           // bd只允许被处理一次
      if (!mbd.postProcessed) {
        try {
                    // 应用合并后的bd
          applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
        }
        catch (Throwable ex) {
          throw new BeanCreationException(mbd.getResourceDescription(), beanName,
              "Post-processing of merged bean definition failed", ex);
        }
                // 标注这个bd已经被MergedBeanDefinitionPostProcessor的后置处理器处理过
                // 那么在第二次创建Bean的时候,不会再次调用applyMergedBeanDefinitionPostProcessors
        mbd.postProcessed = true;
      }
    }
    // 这里是用来出来循环依赖的,关于循环以来,在介绍完正常的Bean的创建后,单独用一篇文章说明
        // 这里不做过多解释
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
      if (logger.isTraceEnabled()) {
        logger.trace("Eagerly caching bean '" + beanName +
            "' to allow for resolving potential circular references");
      }
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    Object exposedObject = bean;
    try {
            // 我们这篇文章重点要分析的就是populateBean方法,在这个方法中完成了属性注入
      populateBean(beanName, mbd, instanceWrapper);
            // 初始化
      exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
      // 省略异常代码
    }
    // 后续代码不在本文探讨范围内了,暂不考虑
    return exposedObject;
  }

applyMergedBeanDefinitionPostProcessors


源码如下:

// 可以看到这个方法的代码还是很简单的,就是调用了MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition方法
protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) {
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
        if (bp instanceof MergedBeanDefinitionPostProcessor) {
            MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp;
            bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName);
        }
    }
}

这个时候我们就要思考一个问题,容器中现在有哪些后置处理器是MergedBeanDefinitionPostProcessor呢?

image.png

查看这个方法的实现类我们会发现总共就这么几个类实现了MergedBeanDefinitionPostProcessor接口。实际上除了ApplicationListenerDetector之外,其余的后置处理器的逻辑都差不多。我们在这里我们主要就分析两个后置处理


1.ApplicationListenerDetector

2.AutowiredAnnotationBeanPostProcessor


ApplicationListenerDetector


首先,我们来ApplicationListenerDetector,这个类在之前的文章中也多次提到过了,它的作用是用来处理嵌套Bean的情况,主要是保证能将嵌套在Bean标签中的ApplicationListener也能添加到容器的监听器集合中去。我们先通过一个例子来感受下这个后置处理器的作用吧


配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  <bean class="com.dmz.source.populate.service.DmzService" id="dmzService">
    <constructor-arg name="orderService">
      <bean class="com.dmz.source.populate.service.OrderService"/>
    </constructor-arg>
  </bean>
</beans>

示例代码:

// 事件
public class DmzEvent extends ApplicationEvent {
  public DmzEvent(Object source) {
    super(source);
  }
}
public class DmzService {
  OrderService orderService;
  public DmzService(OrderService orderService) {
    this.orderService = orderService;
  }
}
// 实现ApplicationListener接口
public class OrderService implements ApplicationListener<DmzEvent> {
  @Override
  public void onApplicationEvent(DmzEvent event) {
    System.out.println(event.getSource());
  }
}
public class Main {
  public static void main(String[] args) {
    ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("application-populate.xml");
    cc.publishEvent(new DmzEvent("my name is dmz"));
  }
}
// 程序运行结果,控制台打印:my name is dmz

说明OrderService已经被添加到了容器的监听器集合中。但是请注意,在这种情况下,如果要使OrderService能够执行监听的逻辑,必须要满足下面这两个条件


  • 外部的Bean要是单例的,对于我们的例子而言就是dmzService
  • 内嵌的Bean也必须是单例的,在上面的例子中也就是orderService必须是单例

另外需要注意的是,这种嵌套的Bean比较特殊,它虽然由Spring创建,但是确不存在于容器中,就是说我们不能将其作为依赖注入到别的Bean中。


AutowiredAnnotationBeanPostProcessor


对应源码如下:

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    // 找到注入的元数据,第一次是构建,后续可以直接从缓存中拿
    // 注解元数据其实就是当前这个类中的所有需要进行注入的“点”的集合,
    // 注入点(InjectedElement)包含两种,字段/方法
    // 对应的就是AutowiredFieldElement/AutowiredMethodElement
    InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
    // 排除掉被外部管理的注入点
    metadata.checkConfigMembers(beanDefinition);
}

上面代码的核心逻辑就是


  • 找到所有的注入点,其实就是被@Autowired注解修饰的方法以及字段,同时静态的方法以及字段也会被排除
  • 排除掉被外部管理的注入点,在后续的源码分析中我们再细说


findAutowiringMetadata

// 这个方法的核心逻辑就是先从缓存中获取已经解析好的注入点信息,很明显,在原型情况下才会使用缓存
// 创建注入点的核心逻辑在buildAutowiringMetadata方法中
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
    String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
    InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
    // 可能我们会修改bd中的class属性,那么InjectionMetadata中的注入点信息也需要刷新
    if (InjectionMetadata.needsRefresh(metadata, clazz)) {
        synchronized (this.injectionMetadataCache) {
            metadata = this.injectionMetadataCache.get(cacheKey);
            if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                if (metadata != null) {
                    metadata.clear(pvs);
                }
                // 这里真正创建注入点
                metadata = buildAutowiringMetadata(clazz);
                this.injectionMetadataCache.put(cacheKey, metadata);
            }
        }
    }
    return metadata;
}

buildAutowiringMetadata

// 我们应用中使用@Autowired注解标注在字段上或者setter方法能够完成属性注入
// 就是因为这个方法将@Autowired注解标注的方法以及字段封装成InjectionMetadata
// 在后续阶段会调用InjectionMetadata的inject方法进行注入
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
    List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
    Class<?> targetClass = clazz;
    do {
        final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
    // 处理所有的被@AutoWired/@Value注解标注的字段
        ReflectionUtils.doWithLocalFields(targetClass, field -> {
            AnnotationAttributes ann = findAutowiredAnnotation(field);
            if (ann != null) {
                // 静态字段会直接跳过
                if (Modifier.isStatic(field.getModifiers())) {
                    // 省略日志打印
                    return;
                }
                // 得到@AutoWired注解中的required属性
                boolean required = determineRequiredStatus(ann);
                currElements.add(new AutowiredFieldElement(field, required));
            }
        });
    // 处理所有的被@AutoWired注解标注的方法,相对于字段而言,这里需要对桥接方法进行特殊处理
        ReflectionUtils.doWithLocalMethods(targetClass, method -> {
            // 只处理一种特殊的桥接场景,其余的桥接方法都会被忽略
            Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
            if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                return;
            }
            AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
            // 处理方法时需要注意,当父类中的方法被子类重写时,如果子父类中的方法都加了@Autowired
            // 那么此时父类方法不能被处理,即不能被封装成一个AutowiredMethodElement
            if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
                if (Modifier.isStatic(method.getModifiers())) {
                    // 省略日志打印
                    return;
                }
                if (method.getParameterCount() == 0) {
                    // 当方法的参数数量为0时,虽然不需要进行注入,但是还是会把这个方法作为注入点使用
                    // 这个方法最终还是会被调用
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation should only be used on methods with parameters: " +
                                    method);
                    }
                }
                boolean required = determineRequiredStatus(ann);
                // PropertyDescriptor: 属性描述符
                // 就是通过解析getter/setter方法,例如void getA()会解析得到一个属性名称为a
                // readMethod为getA的PropertyDescriptor,
                // 在《Spring官网阅读(十四)Spring中的BeanWrapper及类型转换》文中已经做过解释
                // 这里不再赘述,这里之所以来这么一次查找是因为当XML中对这个属性进行了配置后,
                // 那么就不会进行自动注入了,XML中显示指定的属性优先级高于注解
                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);       // 构造一个对应的AutowiredMethodElement,后续这个方法会被执行
                // 方法的参数会被自动注入,这里不限于setter方法
                currElements.add(new AutowiredMethodElement(method, required, pd));
            }
        });
    // 会处理父类中字段上及方法上的@AutoWired注解,并且父类的优先级比子类高
        elements.addAll(0, currElements);
        targetClass = targetClass.getSuperclass();
    }
    while (targetClass != null && targetClass != Object.class);
    return new InjectionMetadata(clazz, elements);
}

难点代码分析

上面的代码整体来说应该很简单,就如我们之前所说的,处理带有@Autowired注解的字段及方法,同时会过滤掉所有的静态字段及方法。上面复杂的地方在于对桥接方法的处理,可能大部分人都没办法理解这几行代码:

// 第一行
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
// 第二行
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
    return;
}
// 第三行
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
}

要理解这些代码,首先你得知道什么是桥接,为此我已经写好了一篇文章:

Spring杂谈 | 从桥接方法到JVM方法调用

除了在上面的文章中提到的桥接方法外,还有一种特殊的情况

// A类跟B类在同一个包下,A不是public的
class A {
  public void test(){
  }
}
// 在B中会生成一个跟A中的方法描述符(参数+返回值)一模一样的桥接方法
// 这个桥接方法实际上就是调用父类中的方法
// 具体可以参考:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=63424113
public class B extends A {
}

在理解了什么是桥接之后,那么上边的第一行代码你应该就能看懂了,就以上面的代码为例,B中会生成一个桥接方法,对应的被桥接的方法就是A中的test方法。

接着,我们看看第二行代码

public static boolean isVisibilityBridgeMethodPair(Method bridgeMethod, Method bridgedMethod) {
    // 说明这个方法本身就不是桥接方法,直接返回true
    if (bridgeMethod == bridgedMethod) {
        return true;
    }
    // 说明是桥接方法,并且方法描述符一致
    // 当且仅当是上面例子中描述的这种桥接的时候这个判断才会满足
    // 正常来说桥接方法跟被桥接方法的返回值+参数类型肯定不一致
    // 所以这个判断会过滤掉其余的所有类型的桥接方法
    // 只会保留本文提及这种特殊情况下产生的桥接方法
    return (bridgeMethod.getReturnType().equals(bridgedMethod.getReturnType()) &&
            Arrays.equals(bridgeMethod.getParameterTypes(), bridgedMethod.getParameterTypes()));
}

最后,再来看看第三行代码,核心就是这句method.equals(ClassUtils.getMostSpecificMethod(method, clazz)。这句代码的主要目的就是为了处理下面这种情况

@Component
public class D extends C {
  @Autowired
  @Override
  public void setDmzService(DmzService dmzService) {
    dmzService.init();
    this.dmzService = dmzService;
  }
}
// C不是Spring中的组件
public class C {
  DmzService dmzService;
    @Autowired
  public void setDmzService(DmzService dmzService) {
    this.dmzService = dmzService;
  }
}

这种情况下,在处理D中的@Autowired注解时,虽然我们要处理父类中的@Autowired注解,但是因为子类中的方法已经复写了父类中的方法,所以此时应该要跳过父类中的这个被复写的方法,这就是第三行代码的作用。


小结


到这里我们主要分析了applyMergedBeanDefinitionPostProcessors这段代码的作用,它的执行时机是在创建对象之后,属性注入之前。按照官方的定义来说,到这里我们仍然可以使用这个方法来修改bd的定义,那么相对于通过BeanFactoryPostProcessor的方式修改bd,applyMergedBeanDefinitionPostProcessors这个方法影响的范围更小,BeanFactoryPostProcessor影响的是整个Bean的生命周期,而applyMergedBeanDefinitionPostProcessors只会影响属性注入之后的生命周期。


其次,我们分析了Spring中内置的MergedBeanDefinitionPostProcessor,选取了其中两个特殊的后置处理器进行分析,其中ApplicationListenerDetector主要处理内嵌的事件监听器,而AutowiredAnnotationBeanPostProcessor主要用于处理@Autowired注解,实际上我们会发现,到这里还只是完成了@Autowired注解的解析,还没有真正开始进行注入,真正注入的逻辑在后面我们要分析的populateBean方法中,在这个方法中会使用解析好的注入元信息完成真正的属性注入,那么接下来我们就开始分析populateBean这个方法的源码。


相关文章
|
6天前
|
存储 Java 数据安全/隐私保护
|
6天前
|
XML Java 开发者
Spring Boot中的bean注入方式和原理
Spring Boot中的bean注入方式和原理
139 0
|
6天前
|
前端开发 Java 数据库连接
Spring系列文章1:Spring入门程序
Spring系列文章1:Spring入门程序
|
4天前
|
前端开发 Java 编译器
详解Spring与JDK注入
依赖注入是Spring框架的核心概念之一,它通过将对象之间的依赖关系外部化,实现了松耦合和可测试性。面向切面编程则允许开发人员将横切关注点(如日志、事务管理)从应用程序的主要业务逻辑中分离出来,以提高代码的模块化和可维护性。
10 4
|
6天前
|
XML Java 数据格式
Spring 属性注入方式
Spring 属性注入方式
14 2
|
6天前
|
Java 数据库连接 数据库
Spring事务简介,事务角色,事务属性
Spring事务简介,事务角色,事务属性
20 2
|
6天前
|
Java Apache Spring
Spring BeanUtils与Apache BeanUtils提供基本属性复制,适用于简单需求
【5月更文挑战第4天】Spring BeanUtils与Apache BeanUtils提供基本属性复制,适用于简单需求;Cglib BeanCopier用于转换为Cglib代理对象;Apache PropertyUtils处理属性操作;Dozer支持复杂对象映射。选择工具取决于具体需求,如需精细控制或对象映射,推荐Dozer或Apache PropertyUtils。Apache BeanUtils可能因潜在的封装性破坏被禁用。
24 3
|
6天前
|
Java 开发者 Spring
Spring Boot中的资源文件属性配置
【4月更文挑战第28天】在Spring Boot应用程序中,配置文件是管理应用程序行为的重要组成部分。资源文件属性配置允许开发者在不重新编译代码的情况下,对应用程序进行灵活地配置和调整。本篇博客将介绍Spring Boot中资源文件属性配置的基本概念,并通过实际示例展示如何利用这一功能。
27 1
|
6天前
|
Java Spring 容器
Spring注入
Spring注入
33 13
|
6天前
|
JSON Java 数据库连接
属性注入掌握:Spring Boot配置属性的高级技巧与最佳实践
属性注入掌握:Spring Boot配置属性的高级技巧与最佳实践
28 1