2 Star 15 Fork 2

coder_chenjun / spring1.0-jdk1.8-maven

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
spring 1.0 IOC源码分析.md 65.11 KB
一键复制 编辑 原始数据 按行查看 历史

AutowireCapableBeanFactory

装配(autowire)也可以称之为依赖注入,我把其分为自动装配与手动装配,手动装配就是类似下面的xml设置,其中手动设置了userService这个bean的dao属性依赖指定的其它bean,在调用工厂的getBean方法获取userService实例时会处理这个依赖的装配

<bean id="userDao" class="entity.UserDaoImpl"/>
<bean id="userService" class="entity.UserServiceImpl"  >
  <property name="dao">
    <ref bean="userDao"></ref>
  </property>
</bean>

而自动装配能力是由AutowireCapableBeanFactory接口确定的,它的父接口是BeanFactory,层次结构如下,所以spirng 1.0常用的2个工厂类DefaultListableBeanFactory与XMLBeanFactory都具备自动与手动装配两种能力

image-20211010165947289

从源码中可以看出此接口声明了如下的功能

  • 装配模式的4个常量
  • 装配类:对指定的类依据指定的装配模式进行装配,会对提供的class创建出对象,创建对象时就会考虑进行构造函数的装配处理,其中dependencyCheck在构造函数注入这种情况下是被忽略的,因为依赖检查主要针对属性值
  • 装配对象的属性:就是对提供的对象的属性进行装配,简单理解为对一系列setter方法进行处理
  • 应用BPP
public interface AutowireCapableBeanFactory extends BeanFactory {

  int AUTOWIRE_BY_NAME = 1;
  int AUTOWIRE_BY_TYPE = 2;
  int AUTOWIRE_CONSTRUCTOR = 3;
  int AUTOWIRE_AUTODETECT = 4;

  Object autowire(Class beanClass, int autowireMode, boolean dependencyCheck)
    throws BeansException;

  void autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck)
    throws BeansException;

  Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String name)
    throws BeansException;

  Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String name)
    throws BeansException;
}

此接口的实现类在spring 1.0中只有一个,就是AbstractAutowireCapableBeanFactory,此类完整实现了接口的所有功能。

其中autowire方法只在AbstractPathMapHandlerMapping类的initApplicationContext方法中有使用到,整个Factory类型体系是没有用到的,而autowireBeanProperties方法整个项目都没有用到这个方法。

在整个getBean获取对象的过程中,是利用autowireConstructor方法来完成构造函数注入的,而属性的注入是由populateBean方法内部自己进行处理的

假定有以下两个类,由于autowire方法与autowireBeanProperties方法都是公有方法,所以可以直接调用这两个方法来理解其自动装配行为

public interface UserDao {
  void insert();
}

public class BaseDao {
}

public class UserDaoImpl {
  public void insert(){
    System.out.println("insert into db in userDaoImpl---");
  }
}
//========依赖了UserDaoImpl
public class UserServiceImpl {

  private Object dao;

  public UserServiceImpl() {
    System.out.println("---默认构造函数---");
  }

  public UserServiceImpl(UserDaoImpl dao) {
    System.out.println("---构造函数注入UserDaoImpl dao---");
    this.dao = dao;
  }

  public UserServiceImpl(BaseDao dao) {
    System.out.println("---构造函数注入BaseDao dao---");
    this.dao = dao;
  }

  public UserServiceImpl(UserDao dao) {
    System.out.println("---构造函数注入UserDao dao---");
    this.dao = dao;
  }

  public UserServiceImpl(Object dao) {
    System.out.println("---构造函数注入Object dao---");
    this.dao = dao;
  }
  public UserDao getDao() {
    return (UserDao) dao;
  }

  public void setDao(UserDao dao) {
    System.out.println("---属性注入dao---");
    this.dao = dao;
  }

  public void insert(){
    System.out.println("insert in UserServiceImpl---");
    ((UserDao)dao).insert();
  }
}

autowireBeanProperties

编写下面的测试类就可以研究autowireBeanProperties的实现原理

@Test
public void testAutowireBeanPropertiesMethod(){
  DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
  RootBeanDefinition daoDefinition = new RootBeanDefinition(UserDaoImpl.class, 0);
  factory.registerBeanDefinition("dao", daoDefinition);

  //只支持 AUTOWIRE_BY_NAME and AUTOWIRE_BY_TYPE 装配模式

  int modeName = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
  int modeType = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;

  UserServiceImpl userService = new UserServiceImpl();
  //factory.autowireBeanProperties(userService,modeName,false);
  //这里传递的是一个对象
  factory.autowireBeanProperties(userService,modeType,false);

  System.out.println(userService);
  userService.insert();
}

AutowireBeanProperties方法的代码如下,很明显看出只支持名字与类型这两种装配模式,真正完成装配是靠populateBean方法来实现的,具体怎么实现的见属性注入章节

public void autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck)
  throws BeansException {
  if (autowireMode != AUTOWIRE_BY_NAME && autowireMode != AUTOWIRE_BY_TYPE) {
    throw new IllegalArgumentException("Just constants AUTOWIRE_BY_NAME and AUTOWIRE_BY_TYPE allowed");
  }
  RootBeanDefinition bd = new RootBeanDefinition(existingBean.getClass(), autowireMode, dependencyCheck);
  populateBean(existingBean.getClass()
               .getName(), bd, new BeanWrapperImpl(existingBean));
}

从实现可以看出自动属性装配根本不关BeanDefinition的事情,纯粹靠反射解析属性名与类型来完成装配

autowire

可以编写下面的类来研究autowire方法的实现原理

@Test
public void testAutowireMethod() {
  DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
  RootBeanDefinition daoDefinition = new RootBeanDefinition(UserDaoImpl.class, 0);
  factory.registerBeanDefinition("dao", daoDefinition);

  int modeAuto = AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT;
  int modeName = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
  int modeType = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
  int modeConstructor = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR;

    //这里传递是一个Class
  // UserServiceImpl userService = (UserServiceImpl) factory.autowire(UserServiceImpl.class, modeAuto, false);//自动模式采用了属性注入方式
  // UserServiceImpl userService = (UserServiceImpl) factory.autowire(UserServiceImpl.class, modeName, false);//仍然采用属性注入方式,但名字要能匹配上daoDefinition指定的名字
  UserServiceImpl userService = (UserServiceImpl) factory.autowire(UserServiceImpl.class, modeType, false); //属性注入
  //    UserServiceImpl userService = (UserServiceImpl) factory.autowire(UserServiceImpl.class, modeConstructor, false);
  System.out.println(userService);
  userService.insert();
}

autowire方法的代码如下,当指定的装配模式是构造函数模式时就调用autowireConstructor来真正实现构造函数注入,autowireConstructor也是getBean流程中使用的构造函数注入机制,具体怎么实现的见构造函数注入章节

public Object autowire(Class beanClass, int autowireMode, boolean dependencyCheck)
  throws BeansException {
  RootBeanDefinition bd = new RootBeanDefinition(beanClass, autowireMode, dependencyCheck);
  if (bd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR) {
    return autowireConstructor(beanClass.getName(), bd).getWrappedInstance();
  } else {
    Object bean = BeanUtils.instantiateClass(beanClass);
    populateBean(bean.getClass()
                 .getName(), bd, new BeanWrapperImpl(bean));
    return bean;
  }
}

不管是自动装配还是手动装配,真正完成构造函数装配的是autowireConstructor,真正完成属性装配的是populateBean

属性注入

属性注入功能由populateBean方法实现,其核心功能有3个

  • 属性值解析
  • 依赖检查
  • 属性值设置

if代码块中的代码就是解析属性值,依据自动装配模式值分别进行处理,处理完毕之后交给dependencyCheck去检查属性值的设置情况,最后由applyPropertyValues方法把属性值真正应用到bean上面,populateBean的源码如下

protected void populateBean(String beanName, RootBeanDefinition mergedBeanDefinition, BeanWrapper bw) {
  PropertyValues pvs = mergedBeanDefinition.getPropertyValues();

  if (mergedBeanDefinition.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
      mergedBeanDefinition.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
    MutablePropertyValues mpvs = new MutablePropertyValues(pvs);//每个属性都会被处理.

    if (mergedBeanDefinition.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {
      autowireByName(beanName, mergedBeanDefinition, bw, mpvs);
    }

    if (mergedBeanDefinition.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
      autowireByType(beanName, mergedBeanDefinition, bw, mpvs);
    }

    pvs = mpvs;
  }
  dependencyCheck(beanName, mergedBeanDefinition, bw, pvs);
  applyPropertyValues(beanName, mergedBeanDefinition, bw, pvs);
}

属性值解析

属性值的解析依据装配模式的不同分别交给autowireByName与autowireByType方法处理,解析完毕之后更新原始的pvs值,比如把RuntimeBeanReference类型值替换成某一个bean对象。

同时从获取装配模式的逻辑来看,每个属性不能单独设置自己的装配模式,只能是整个bean用一样的装配模式,因为装配模式是从BeanDefinition里获取的

autowireByName

所谓的依据名字装配就是依据bean的setter属性名来进行装配,核心逻辑如下:

  • 遍历所有的setter方法
  • 检查此属性是否需要装配,因为有一些类型是可以忽略的
  • 能装配的话,依据名字获取对应的bean对象
  • 把刚获取到的对象以及当前的属性名设置到pvs中去
protected void autowireByName(String beanName, RootBeanDefinition mergedBeanDefinition,
                              BeanWrapper bw, MutablePropertyValues pvs) {
  String[] propertyNames = unsatisfiedObjectProperties(mergedBeanDefinition, bw);
  for (int i = 0; i < propertyNames.length; i++) {
    String propertyName = propertyNames[i];
    if (containsBean(propertyName)) {
      Object bean = getBean(propertyName);
      pvs.addPropertyValue(propertyName, bean);
    } 
  }
}

其中unsatisfiedObjectProperties方法就是找出所有还没有装配的属性名,其逻辑如下

  • 是简单属性不会装配,比如int类型的属性
  • 属于忽略的类型不装配

BeanDefinition中这个名字的属性已经有值的不装配,比如已经通过在xml中手动设置ref数据了,这种情况属于手动装配模式,不属于自动装配的情况,直接调用autowireBeanProperties方法不会出现有手动设置值的情况,只有在读取xml元数据时才会有这种情况

  • 不考虑属性是否有对应的getter方法,有setter方法就可以了
  protected String[] unsatisfiedObjectProperties(RootBeanDefinition mergedBeanDefinition, BeanWrapper bw) {
    Set result = new TreeSet();
    Set ignoreTypes = getIgnoredDependencyTypes();
    PropertyDescriptor[] pds = bw.getPropertyDescriptors();
    for (int i = 0; i < pds.length; i++) {
      String name = pds[i].getName();
      if (pds[i].getWriteMethod() != null &&
              !BeanUtils.isSimpleProperty(pds[i].getPropertyType()) &&
              !ignoreTypes.contains(pds[i].getPropertyType()) &&
              mergedBeanDefinition.getPropertyValues()
                      .getPropertyValue(name) == null) {
        result.add(name);
      }
    }
    return (String[]) result.toArray(new String[result.size()]);
  }

找到所有需要装配的属性名之后,就会依据这些名字找是否它包含在工厂中了,这个属性名对应的内容存在的逻辑是

  • 已经包含在单例缓存map中了
  • 是否已经在BeanDefinition map集合中存在,也就是说是否已经注册了同名的BeanDefinition
  • 如果本工厂找不到就依据同样的逻辑到父工厂去找
public boolean containsBean(String name) {
  String beanName = transformedBeanName(name);
  if (this.singletonCache.containsKey(beanName)) {
    return true;
  }
  if (containsBeanDefinition(beanName)) {
    return true;
  }
  else {
    // not found -> check parent
    if (this.parentBeanFactory != null) {
      return this.parentBeanFactory.containsBean(beanName);
    }
    else {
      return false;
    }
  }
}

//
public boolean containsBeanDefinition(String name) {
  return this.beanDefinitionMap.containsKey(name);
}

工厂包含这个名字的bean并不意味着此名字的对象已经在工厂里,可能这个名字的bean不是单例或者只有一个BeanDefinition,并没有实例化过,所以需要调用getBean来获取这个名字的对象,这样会导致所依赖的bean进行实例化,也可能导致被依赖的bean也启动它自己的手动属性以及构造函数注入情况的发生,也就是说会导致整个依赖链的实例都创建出来以及装配完毕,所以dependencyCheck方法放在populateBean方法里的if代码块后面处理是合乎逻辑的

至此就完成了属性值的解析,之后再把这些值应用到bean的属性里即完成的按名字进行装配的能力

autowireByType

依据类型进行自动装配逻辑如下

  • 找出所有需要装配的属性
  • 遍历这些属性
  • 找出当前属性的类型
  • 在工厂中找符合这个类型的bean对象,默认是包含原型bean的
  • 最后把这些设置到pvs中去
protected void autowireByType(String beanName, RootBeanDefinition mergedBeanDefinition,
                              BeanWrapper bw, MutablePropertyValues pvs) {
  String[] propertyNames = unsatisfiedObjectProperties(mergedBeanDefinition, bw);
  for (int i = 0; i < propertyNames.length; i++) {
    String propertyName = propertyNames[i];
    // look for a matching type
    Class requiredType = bw.getPropertyDescriptor(propertyName)
      .getPropertyType();
    Map matchingBeans = findMatchingBeans(requiredType);
    if (matchingBeans != null && matchingBeans.size() == 1) {
      pvs.addPropertyValue(propertyName, matchingBeans.values()
                           .iterator()
                           .next());

    } else if (matchingBeans != null && matchingBeans.size() > 1) {
      throw new UnsatisfiedDependencyException("...");
    } 
  }
}

	protected Map findMatchingBeans(Class requiredType) {
		return BeanFactoryUtils.beansOfTypeIncludingAncestors(this, requiredType, true, true);
	}

findMatchingBeans方法靠BeanFactoryUtils.beansOfTypeIncludingAncestors来查找是requiredType类型的bean,其中第三个参数设置为true表示也查找原型bean,第四个参数表示是否到父系工厂中去找。

如果查找过程中还没有创建出对象出来,那么就创建出bean对象并放置到map中,返回的map是所有的符合此类型的bean对象,map的键是bean的id,值是bean对象。关于此方法的说明请参见附录

应用属性值

应用属性值是把存放在pvs中的数据真正设置到bean对象的属性里去,此功能是靠applyPropertyValues方法完成的,本方法核心就2个功能

  • 设置属性值之前进行必要的类型转换与解析处理,比如把RuntimeBeanReference解析为真正的bean对象,对ManagedList进行处理等,或者是利用PropertyEditor把字符串转换为Class类型等类似的处理
  • 把处理好后的属性值利用BeanWrapper设置到bean对象里

applyPropertyValues方法的核心代码如下

protected void applyPropertyValues(String beanName, RootBeanDefinition mergedBeanDefinition, 
                                   BeanWrapper bw,
                                   PropertyValues pvs) throws BeansException {
  
  MutablePropertyValues deepCopy = new MutablePropertyValues(pvs);
  PropertyValue[] pvals = deepCopy.getPropertyValues();
  for (int i = 0; i < pvals.length; i++) {
    // 1.把属性的值进行必要的转换
    Object value = resolveValueIfNecessary(beanName, mergedBeanDefinition,
                                           pvals[i].getName(), pvals[i].getValue());
    PropertyValue pv = new PropertyValue(pvals[i].getName(), value);
    // 2.用转换后的属性替换掉原来的属性值
    deepCopy.setPropertyValueAt(pv, i);
  }
  bw.setPropertyValues(deepCopy);
}

resolveValueIfNecessary

此方法的功能主要对以下类型的属性值进行转换处理

  • 内部bean
  • ref
  • list
  • set
  • map
protected Object resolveValueIfNecessary(String beanName, RootBeanDefinition mergedBeanDefinition,
                                         String argName, Object value) throws BeansException {

  if (value instanceof AbstractBeanDefinition) {
    BeanDefinition bd = (BeanDefinition) value;
    if (bd instanceof AbstractBeanDefinition) {
      ((AbstractBeanDefinition) bd).setSingleton(false);
    }
    String innerBeanName = "(inner bean for property '" + beanName + "." + argName + "')";
    Object bean = createBean(innerBeanName, getMergedBeanDefinition(innerBeanName, bd));
    if (bean instanceof DisposableBean) {
      this.disposableInnerBeans.add(bean);
    }
    return getObjectForSharedInstance(innerBeanName, bean);
  } else if (value instanceof RuntimeBeanReference) {
    RuntimeBeanReference ref = (RuntimeBeanReference) value;
    return resolveReference(mergedBeanDefinition, beanName, argName, ref);
  }
  else if (value instanceof ManagedList) {
    return resolveManagedList(beanName, mergedBeanDefinition, argName, (ManagedList) value);
  } else if (value instanceof ManagedSet) {
    return resolveManagedSet(beanName, mergedBeanDefinition, argName, (ManagedSet) value);
  } else if (value instanceof ManagedMap) {
    ManagedMap mm = (ManagedMap) value;
    return resolveManagedMap(beanName, mergedBeanDefinition, argName, mm);
  } else {
    // no need to resolve value
    return value;
  }
}

内部bean

内部bean的解析代码中可以看出,内部bean不是单例的,名字也比较特殊,因为它不会被被的bean引用,只会被其父bean引用。如果内部bean实现了DisposableBean接口,仍然会被加到销毁集合中进行销毁处理,但内部bean如果只是配置destroy-method是不会加入到销毁bean中处理的。

这里调用了getObjectForSharedInstance方法,既然不是单例,就不应该调用这个方法,直接返回即可,它这么做可能是为了支持FactoryBean,这里与getBean的处理逻辑有一点点不同

ref

ref子元素解析之后就变成了RuntimeBeanReference类型,其解析最终是交给resolveReference方法来处理的

  protected Object resolveReference(RootBeanDefinition mergedBeanDefinition, String beanName,
                                    String argName, RuntimeBeanReference ref) throws BeansException {
    try {
      return getBean(ref.getBeanName());
    } catch (BeansException ex) {
      throw new BeanCreationException("...");
    }
  }

list

属性的list子元素解析成了ManageList类型,在这里通过resolveManagedList方法来解析此类型的数据,由于的值可能仍然需要解析,所以这里又递归调用了resolveValueIfNecessary方法

protected List resolveManagedList(String beanName, RootBeanDefinition mergedBeanDefinition,
                                  String argName, ManagedList ml) throws BeansException {
  List resolved = new ArrayList();
  for (int i = 0; i < ml.size(); i++) {
    resolved.add(resolveValueIfNecessary(beanName, mergedBeanDefinition, argName + "[" + i + "]", ml.get(i)));
  }
  return resolved;
}

构造函数注入

构造函数注入是靠autowireConstructor方法实现的,构造函数注入是在对象创建过程中发生的,此时对象还没有完全创建完毕,主要是针对BeanDefinition进行处理的。

构造函数注入本质上要解决以下几个问题

  • 选择合适的构造函数,因为bean可能有多个构造函数
  • 保存构造函数配置的数据进行解析,比如把ref数据转换成真正的bean,进行类型转换等
  • 调用构造函数,完成注入以及实例的创建
protected BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mergedBeanDefinition)
  throws BeansException {

  ConstructorArgumentValues cargs = mergedBeanDefinition.getConstructorArgumentValues();
  ConstructorArgumentValues resolvedValues = new ConstructorArgumentValues();

  int minNrOfArgs = 0;
  if (cargs != null) {
    minNrOfArgs = cargs.getNrOfArguments();
    for (Iterator it = cargs.getIndexedArgumentValues()
         .entrySet()
         .iterator(); it.hasNext(); ) {
      Map.Entry entry = (Map.Entry) it.next();
      int index = ((Integer) entry.getKey()).intValue();
      if (index < 0) {
        throw new BeanCreationException(mergedBeanDefinition.getResourceDescription(), beanName,
                                        "Invalid constructor argument index: " + index);
      }
      if (index > minNrOfArgs) {
        minNrOfArgs = index + 1;
      }
      String argName = "constructor argument with index " + index;
      ConstructorArgumentValues.ValueHolder valueHolder = (ConstructorArgumentValues.ValueHolder) entry.getValue();
      Object resolvedValue = resolveValueIfNecessary(beanName, mergedBeanDefinition, argName, valueHolder.getValue());
      resolvedValues.addIndexedArgumentValue(index, resolvedValue, valueHolder.getType());
    }
    for (Iterator it = cargs.getGenericArgumentValues()
         .iterator(); it.hasNext(); ) {
      ConstructorArgumentValues.ValueHolder valueHolder = (ConstructorArgumentValues.ValueHolder) it.next();
      String argName = "constructor argument";
      Object resolvedValue = resolveValueIfNecessary(beanName, mergedBeanDefinition, argName, valueHolder.getValue());
      resolvedValues.addGenericArgumentValue(resolvedValue, valueHolder.getType());
    }
  }

参数值解析

如果有给BeanDefinition配置了构造函数相关信息,比如下面的xml配置情况,那么就需要先进行解析

<bean id="normal" class="entity.NormalClass">
  <constructor-arg index="0" type="int">
    <value>100</value>
  </constructor-arg>
  <constructor-arg index="1">
    <value>cj</value>
  </constructor-arg>
  <constructor-arg >
    <ref bean="user"></ref>
  </constructor-arg>
</bean>

解析的代码就是autowireConstructor方法的下面这块代码,由于构造函数参数配置有两块,一块是有索引的,一块是没有索引的,所以有2个集合需要循环遍历,把其中的每一个构造函数参数进行解析。解析之后的结果就放在一个新的ConstructorArgumentValues类型的对象中去

在解析的过程中也会调整最小参数配置数量的问题,基本逻辑是索引参数配置+无索引的参数配置,但如果出现有某个参数配置的索引比这个最小参数个数大,就再1,因为索引默认是从0开始的

ConstructorArgumentValues cargs = mergedBeanDefinition.getConstructorArgumentValues();
ConstructorArgumentValues resolvedValues = new ConstructorArgumentValues();

int minNrOfArgs = 0;
if (cargs != null) {
  minNrOfArgs = cargs.getNrOfArguments();
  
  for (Iterator it = cargs.getIndexedArgumentValues().entrySet().iterator(); it.hasNext(); ) {
    Map.Entry entry = (Map.Entry) it.next();
    int index = ((Integer) entry.getKey()).intValue();
    if (index > minNrOfArgs) {
      minNrOfArgs = index + 1;
    }
    String argName = "constructor argument with index " + index;
    ConstructorArgumentValues.ValueHolder valueHolder = (ConstructorArgumentValues.ValueHolder) entry.getValue();
    Object resolvedValue = resolveValueIfNecessary(beanName, mergedBeanDefinition, argName, valueHolder.getValue());
    resolvedValues.addIndexedArgumentValue(index, resolvedValue, valueHolder.getType());
  }
  
  for (Iterator it = cargs.getGenericArgumentValues()
       .iterator(); it.hasNext(); ) {
    ConstructorArgumentValues.ValueHolder valueHolder = (ConstructorArgumentValues.ValueHolder) it.next();
    String argName = "constructor argument";
    Object resolvedValue = resolveValueIfNecessary(beanName, mergedBeanDefinition, argName, valueHolder.getValue());
    resolvedValues.addGenericArgumentValue(resolvedValue, valueHolder.getType());
  }
}

挑选合适的构造函数

由于bean的构造函数可能有多个,构造函数参数配置也可能与构造函数参数个数不是很一直,比如构造函数有3个参数,但配置的构造函数只有2个或者4个情况,如果不是人为的低级错误,构造函数配置可能会少一些,这样这些缺少的项就通过自动装配的形式实现,比如下面的类只给第一个与第二个进行了配置,缺少的那个就靠找匹配的类型自动装配。

public class SomeClass {
  public SomeClass(A a,B b,C c){}
  public SomeClass(A a,B b){}
  public SomeClass(A a,Object b){}
}

xml配置

<bean id="s" class="entity.SomeClass">
  <constructor-arg index="0" >
    <ref bean="a">100</value>
  </constructor-arg>

  <constructor-arg index="1" >
    <ref bean="b"></ref>
  </constructor-arg>   
</bean>

不要处理构造函数参数个数小于参数设置的情况,这种情况属于不会使用spring的乱写,基于这种情况,所以优先遍历构造函数参数比较多的构造函数,直到最后找到最合适的构造函数,比如上面的SomeClass类先遍历有3个参数的,发现参数配置是可以匹配的,可以使用这个构造函数,但接着遍历的时候,发现第二个构造函数完全匹配,更合适一些,这样就没有自动装配发生,都是手动指定的依赖,更可信,接着遍历第三个时发现也匹配,因为B对象也是Object,那么用第二个还是第三个呢?这个时候就牵涉到一个权重信息比较问题,基于这些思考我们一点点看其源码实现。

构造函数排序

通常情况下构造函数参数个数是比配置的要多的,所以先要对构造函数进行降序排列,代码如下

Constructor[] constructors = mergedBeanDefinition.getBeanClass()
  .getConstructors();
Arrays.sort(constructors, new Comparator() {
  public int compare(Object o1, Object o2) {
    int c1pl = ((Constructor) o1).getParameterTypes().length;
    int c2pl = ((Constructor) o2).getParameterTypes().length;
    return (new Integer(c1pl)).compareTo(new Integer(c2pl)) * -1;
  }
});

参数值的选择

接着就进入到挑选构造函数的流程了,基本符合两个条件即可

  • 构造函数所需的每一个参数都有值
  • 上一步中选择的构造函数的权重最低

牵涉到代码如下,核心逻辑如下

  • 判断构造函数参数个数是否比配置的参数还少,少的话抛异常
  • 接着给构造函数的每个参数从配置的构造函数数据中找出合适的值
    • 能找到,就进行必要的类型转换,并保存到args里
    • 找不到的话,分2种情况,如果不是构造函数自动装配就报错,如果是构造函数自动装配就调用findMatchingBeans方法依据构造函数参数的类型找合适的bean,如果自动装配还是找不到或者找到不止一个就抛异常
    • 出了异常就进入到catch代码块,如果已经遍历完所有的构造函数,异常继续往上抛,如果还没有遍历完所有的构造函数就直接catch,消化掉这个异常,以便可以继续遍历下一个构造函数
Constructor constructorToUse = null;
Object[] argsToUse = null;
int minTypeDiffWeight = Integer.MAX_VALUE;
for (int i = 0; i < constructors.length; i++) {
  try {
    Constructor constructor = constructors[i];
    if (constructor.getParameterTypes().length < minNrOfArgs) {
      throw new BeanCreationException("构造函数参数个数不能小于配置的参数个数");
    }
    Class[] argTypes = constructor.getParameterTypes();
    Object[] args = new Object[argTypes.length];
    for (int j = 0; j < argTypes.length; j++) {
      ConstructorArgumentValues.ValueHolder valueHolder = resolvedValues.getArgumentValue(j, argTypes[j]);
      if (valueHolder != null) {
            args[j] = bw.doTypeConversionIfNecessary(valueHolder.getValue(), argTypes[j]);       
      } else {
        if (mergedBeanDefinition.getResolvedAutowireMode() != RootBeanDefinition.AUTOWIRE_CONSTRUCTOR) {
          throw new UnsatisfiedDependencyException("不是自动装配,又找不到参数值,可能用户没有配置");
        }
        Map matchingBeans = findMatchingBeans(argTypes[j]);
        if (matchingBeans == null || matchingBeans.size() != 1) {
          throw new UnsatisfiedDependencyException("找不到此参数的匹配bean");
        }
        args[j] = matchingBeans.values().iterator().next();
      }
    }
    int typeDiffWeight = getTypeDifferenceWeight(argTypes, args);
    if (typeDiffWeight < minTypeDiffWeight) {
      constructorToUse = constructor;
      argsToUse = args;
      minTypeDiffWeight = typeDiffWeight;
    }
  } catch (BeansException ex) {
    if (i == constructors.length - 1 && constructorToUse == null) {
      // all constructors tried
      throw ex;
    } else {
      // swallow and try next constructor
    }
  }
}

构造函数权重

某一个构造函数的所有参数类型都能找到合适的值的话就会把当前遍历的构造函数作为一个备选,接着继续遍历仍然可能找到合适的构造函数,选权重差异最小的构造函数

int typeDiffWeight = getTypeDifferenceWeight(argTypes, args);
if (typeDiffWeight < minTypeDiffWeight) {
  constructorToUse = constructor;
  argsToUse = args;
  minTypeDiffWeight = typeDiffWeight;
}

前面提到的UserServiceImpl类有几个构造函数,参数类型分别有Object、UserDao接口、BaseDao、UserDaoImpl等,如果你传递的参数是一个UserDaoImpl的实例,那这几个构造函数都是可以使用的,到底调用哪个构造函数呢?这就是权重差异要解决的问题

如果你的构造函数是UserDaoImpl,你传递的是UserDaoImpl对象,那么权重差异为0,如果参数是Object,那么差异是2,下面的测试代码可以说明这个问题

@Test
public void testTypeDiffWeight() {
  DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
  Class<UserServiceImpl> serviceClass = UserServiceImpl.class;
  Constructor<?>[] constructors = serviceClass.getConstructors();
  Object[] args = new Object[]{new UserDaoImpl()};
  for (int i = 0; i < constructors.length; i++) {
    Constructor<?> constructor = constructors[i];
    Class<?>[] types = constructor.getParameterTypes();
    int weight = factory.getTypeDifferenceWeight(types, args);
    System.out.println("constructor::" + constructor + " weight:" + weight );
  }
}

输出的结果如下,可以看出由于UserDaoImpl实现了UserDao接口,它的权重也为0

constructor::public entity.UserServiceImpl(entity.UserDao) weight:0
constructor::public entity.UserServiceImpl(java.lang.Object) weight:2
constructor::public entity.UserServiceImpl() weight:0
constructor::public entity.UserServiceImpl(entity.UserDaoImpl) weight:0
constructor::public entity.UserServiceImpl(entity.BaseDao) weight:1

我把源码中的getTypeDifferenceWeight方法改为public

其中getTypeDifferenceWeight方法的源码如下

public int getTypeDifferenceWeight(Class[] argTypes, Object[] args) {
  int result = 0;
  for (int i = 0; i < argTypes.length; i++) {
    if (!BeanUtils.isAssignable(argTypes[i], args[i])) {
      return Integer.MAX_VALUE;
    }
    //到这里表明是参数值是可以分配给参数类型的,此时权重相当于等于0
    if (args[i] != null) {
      // 所以直接继续找父类
      Class superClass = args[i].getClass()
        .getSuperclass();
      while (superClass != null) {
        // 父类也可以分配,权重+1,并继续往上
        if (argTypes[i].isAssignableFrom(superClass)) {
          result++;
          superClass = superClass.getSuperclass();
        } else {
          //如果不能分配,结束循环,停止往上
          superClass = null;
        }
      }
    }
  }
  return result;
}

//
public static boolean isAssignable(Class type, Object value) {
  //1.值是type的实例;2.不是基本类型,值为null,3 类型为基本类型,值是对应的包装类型,这3种情况返回true
  return (type.isInstance(value) ||
          (!type.isPrimitive() && value == null) ||
          (type.equals(boolean.class) && value instanceof Boolean) ||
          (type.equals(byte.class) && value instanceof Byte) ||
          (type.equals(char.class) && value instanceof Character) ||
          (type.equals(short.class) && value instanceof Short) ||
          (type.equals(int.class) && value instanceof Integer) ||
          (type.equals(long.class) && value instanceof Long) ||
          (type.equals(float.class) && value instanceof Float) ||
          (type.equals(double.class) && value instanceof Double));
}

至此就找到了最合适的构造函数与此构造函数每一个参数的值了,就可以实例化了

实例化bean对象

实例化的核心代码如下,可以看出直接使用的是工具类来实例化对象的,此对象经过BeanWrapper包装之后返回,这样就结束了构造函数注入

BeanWrapperImpl bw = new BeanWrapperImpl();
initBeanWrapper(bw);
bw.setWrappedInstance(BeanUtils.instantiateClass(constructorToUse, argsToUse));
return bw;

注册定制的PropertyEditor

在上一步中创建BeanWrapperImpl时会调用initBeanWrapper,这个方法是用来注册的PropertyEditor的,注册之后就可以在后续使用BeanWrapperImpl的过程中使用这些转换器了,比如在createBean方法中

protected void initBeanWrapper(BeanWrapper bw) {
  for (Iterator it = this.customEditors.keySet().iterator(); it.hasNext();) {
    Class clazz = (Class) it.next();
    bw.registerCustomEditor(clazz, (PropertyEditor) this.customEditors.get(clazz));
  }
}

循环依赖问题

被spring管理的对象是有一个生命周期的,如果实例化一个对象A时发现其依赖B,而B又反过来依赖A,这样就会导致循环依赖的问题

在官网搜索circular(5.3.10)版本可以看到下面的内容

Circular dependencies

If you use predominantly constructor injection, it is possible to create an unresolvable circular dependency scenario.

For example: Class A requires an instance of class B through constructor injection, and class B requires an instance of class A through constructor injection. If you configure beans for classes A and B to be injected into each other, the Spring IoC container detects this circular reference at runtime, and throws a BeanCurrentlyInCreationException.

One possible solution is to edit the source code of some classes to be configured by setters rather than constructors. Alternatively, avoid constructor injection and use setter injection only. In other words, although it is not recommended, you can configure circular dependencies with setter injection.

Unlike the typical case (with no circular dependencies), a circular dependency between bean A and bean B forces one of the beans to be injected into the other prior to being fully initialized itself (a classic chicken-and-egg scenario).

现象展示

需要注意的是spring 1.0与后续spring 版本对于循环依赖的解决是不一样的,spring 1.0的版本是不完善的。下面演示的情况重点是针对spring 1.0版本

构造函数间互相依赖

比如下面的类

public class A {
  private B b ;
  public A(B b){
    this.b = b;
  }
}

public class B {
  private A a ;
  public B(A a){
    this.a = a;
  }
}

编写下面的xml

<bean id="a" class="ioc.circular.A">
  <constructor-arg>
    <ref bean="b"/>
  </constructor-arg>
</bean>

<bean id="b" class="ioc.circular.B">
  <constructor-arg>
    <ref bean="a"/>
  </constructor-arg>
</bean>

编写下面的测试类

@Test
public void testConstructorCircularReference() {
  ClassPathResource resource = new ClassPathResource("hello.xml");
  XmlBeanFactory factory = new XmlBeanFactory(resource);
  Object a = factory.getBean("a");
}

部分执行结果

204  [main] INFO  o.s.b.factory.xml.XmlBeanFactory - Creating shared instance of singleton bean 'b' 
204  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Creating instance of bean 'b' with merged definition [Root bean with class [ioc.circular.B] defined in class path resource [hello.xml]] 
204  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Resolving reference from property 'constructor argument' in bean 'b' to bean 'a' 
204  [main] INFO  o.s.b.factory.xml.XmlBeanFactory - Creating shared instance of singleton bean 'a' 
204  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Creating instance of bean 'a' with merged definition [Root bean with class [ioc.circular.A] defined in class path resource [hello.xml]] 
204  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Resolving reference from property 'constructor argument' in bean 'a' to bean 'b' 
204  [main] INFO  o.s.b.factory.xml.XmlBeanFactory - Creating shared instance of singleton bean 'b' 
204  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Creating instance of bean 'b' with merged definition [Root bean with class [ioc.circular.B] defined in class path resource [hello.xml]] 
204  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Resolving reference from property 'constructor argument' in bean 'b' to bean 'a' 
204  [main] INFO  o.s.b.factory.xml.XmlBeanFactory - Creating shared instance of singleton bean 'a' 
204  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Creating instance of bean 'a' with merged definition [Root bean with class [ioc.circular.A] defined in class path resource [hello.xml]] 

java.lang.StackOverflowError
	at ch.qos.logback.classic.spi.LoggingEvent.prepareForDeferredProcessing(LoggingEvent.java:206)
	at ch.qos.logback.core.OutputStreamAppender.subAppend(OutputStreamAppender.java:223)
	at ch.qos.logback.core.OutputStreamAppender.append(OutputStreamAppender.java:102)
	at ch.qos.logback.core.UnsynchronizedAppenderBase.doAppend(UnsynchronizedAppenderBase.java:84)
	at ch.qos.logback.core.spi.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:51)
	at ch.qos.logback.classic.Logger.appendLoopOnAppenders(Logger.java:270)
	at ch.qos.logback.classic.Logger.callAppenders(Logger.java:257)
	at ch.qos.logback.classic.Logger.buildLoggingEventAndAppend(Logger.java:421)
	at ch.qos.logback.classic.Logger.filterAndLog_0_Or3Plus(Logger.java:383)
	at ch.qos.logback.classic.Logger.log(Logger.java:765)
	at org.apache.commons.logging.impl.SLF4JLocationAwareLog.debug(SLF4JLocationAwareLog.java:131)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.resolveReference(AbstractAutowireCapableBeanFactory.java:706)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.resolveValueIfNecessary(AbstractAutowireCapableBeanFactory.java:676)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:259)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:171)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:173)

这种构造函数间的依赖,技术上是没有办法解决的,spring也解决不了,直接报异常,后续版本的spring抛的是BeanCurrentlyInCreationException

单例bean间的属性依赖

属性依赖为什么会成为一个问题,是因为从spring容器中获取对象时会装配属性,也就是说在获取对象时会把其所有通过属性依赖的bean也顺带创建出来,这样是可能在获取对象时也形成循环依赖的

public class C {
  private D d;

  public D getD() {
    return d;
  }

  public void setD(D d) {
    this.d = d;
  }

  // 注意这里的toString输出的hashCode,不是Idea工具生成的toString方法
  //因为Idea生成的toString会互相调用toString导致死循环
  @Override
  public String toString() {
    return "C{" + this.hashCode() +
      " d=" + d.hashCode() +
      '}';
  }
}

//D
public class D {
  private C c;

  public C getC() {
    return c;
  }

  public void setC(C c) {
    this.c = c;
  }

  @Override
  public String toString() {
    return "D{" + this.hashCode() +
      " c=" + c.hashCode() +
      '}';
  }
}

xml配置

<bean id="c" class="ioc.circular.C">
  <property name="d">
    <ref bean="d"/>
  </property>
</bean>

<bean id="d" class="ioc.circular.D">
  <property name="c">
    <ref bean="c"/>
  </property>
</bean>

测试代码如下:

@Test
public void testPropertyInjectBothSingleton() {
  ClassPathResource resource = new ClassPathResource("hello.xml");
  XmlBeanFactory factory = new XmlBeanFactory(resource);
  Object c = factory.getBean("c");
  System.out.println(c);
  System.out.println("===========================");
  Object d = factory.getBean("d");
  System.out.println(d);
}

执行结果如下

C{60559178 d=395629617}
===========================
167  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Returning cached instance of singleton bean 'd' 
D{395629617 c=60559178}

由此可见c与d都只有一个,都能设置成功,说明spring解决了这种循环依赖的问题,正常来说是会形成死循环的,比如下面这样的流程

  • 实例化对象C
  • 发现有setter属性需要装配
  • 发现需要依赖D
  • 实例化D
  • 发现D也有setter属性,需要装配C
  • 实例化对象C,这样就死循环了

spring怎么解决死循环的,留在后面讲解,现在知道它可以解决单例bean见的setter依赖的循环问题即可。

原型bean的属性依赖

代码如下:

public class E {
  private F f;

  public F getD() {
    return f;
  }

  public void setF(F f) {
    this.f = f;
  }

  @Override
  public String toString() {
    return "E{" + this.hashCode() +
      " f=" + f.hashCode() +
      '}';
  }
}

//F
public class F {
  private E e;

  public E getE() {
    return e;
  }

  public void setE(E e) {
    this.e = e;
  }

  @Override
  public String toString() {
    return "F{" + this.hashCode() +
      " e=" + e.hashCode() +
      '}';
  }
}

xml配置如下

<bean id="e" class="ioc.circular.E" singleton="false">
  <property name="f">
    <ref bean="f"/>
  </property>
</bean>

<bean id="f" class="ioc.circular.F" singleton="false">
  <property name="e">
    <ref bean="e"/>
  </property>
</bean>

测试代码如下:

@Test
public void testPropertyInjectBothPrototype() {
  ClassPathResource resource = new ClassPathResource("hello.xml");
  XmlBeanFactory factory = new XmlBeanFactory(resource);
  Object e = factory.getBean("e");
  System.out.println(e);
  System.out.println("===========================");
  Object f = factory.getBean("f");
  System.out.println(f);
}

部分执行结果如下:

181  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Resolving reference from property 'f' in bean 'e' to bean 'f' 
181  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Creating instance of bean 'f' with merged definition [Root bean with class [ioc.circular.F] defined in class path resource [hello.xml]] 
181  [main] DEBUG o.s.b.CachedIntrospectionResults - Using cached introspection results for class ioc.circular.F 
181  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Resolving reference from property 'e' in bean 'f' to bean 'e' 
181  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Creating instance of bean 'e' with merged definition [Root bean with class [ioc.circular.E] defined in class path resource [hello.xml]] 
181  [main] DEBUG o.s.b.CachedIntrospectionResults - Using cached introspection results for class ioc.circular.E 
181  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Resolving reference from property 'f' in bean 'e' to bean 'f' 
181  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Creating instance of bean 'f' with merged definition [Root bean with class [ioc.circular.F] defined in class path resource [hello.xml]] 
181  [main] DEBUG o.s.b.CachedIntrospectionResults - Using cached introspection results for class ioc.circular.F 
181  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Resolving reference from property 'e' in bean 'f' to bean 'e' 

java.lang.StackOverflowError
	at ch.qos.logback.classic.pattern.TargetLengthBasedClassNameAbbreviator.abbreviate(TargetLengthBasedClassNameAbbreviator.java:43)
	at ch.qos.logback.classic.pattern.NamedConverter.convert(NamedConverter.java:53)
	at ch.qos.logback.classic.pattern.NamedConverter.convert(NamedConverter.java:18)
	at ch.qos.logback.core.pattern.FormattingConverter.write(FormattingConverter.java:36)
	at ch.qos.logback.core.pattern.PatternLayoutBase.writeLoopOnConverters(PatternLayoutBase.java:115)
	at ch.qos.logback.classic.PatternLayout.doLayout(PatternLayout.java:141)
	at ch.qos.logback.classic.PatternLayout.doLayout(PatternLayout.java:39)
	at ch.qos.logback.core.encoder.LayoutWrappingEncoder.encode(LayoutWrappingEncoder.java:115)
	at ch.qos.logback.core.OutputStreamAppender.subAppend(OutputStreamAppender.java:230)
	at ch.qos.logback.core.OutputStreamAppender.append(OutputStreamAppender.java:102)
	at ch.qos.logback.core.UnsynchronizedAppenderBase.doAppend(UnsynchronizedAppenderBase.java:84)
	at ch.qos.logback.core.spi.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:51)
	at ch.qos.logback.classic.Logger.appendLoopOnAppenders(Logger.java:270)
	at ch.qos.logback.classic.Logger.callAppenders(Logger.java:257)
	at ch.qos.logback.classic.Logger.buildLoggingEventAndAppend(Logger.java:421)
	at ch.qos.logback.classic.Logger.filterAndLog_0_Or3Plus(Logger.java:383)
	at ch.qos.logback.classic.Logger.log(Logger.java:765)
	at org.apache.commons.logging.impl.SLF4JLocationAwareLog.debug(SLF4JLocationAwareLog.java:131)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:158)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:182)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.resolveReference(AbstractAutowireCapableBeanFactory.java:708)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.resolveValueIfNecessary(AbstractAutowireCapableBeanFactory.java:676)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:616)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:445)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:186)

发现会抛出堆栈溢出,这就是由于不断的创建对象导致的

单例与原型的属性依赖

类型代码如下

public class G {
  public G() {
    System.out.println("g 默认构造函数 hashcode:-----" + this.hashCode());
  }

  private H h;

  public H getH() {
    return h;
  }

  public void setH(H h) {
    System.out.println("-----给g设置的h的hashcode:" + h.hashCode());
    this.h = h;
  }

  @Override
  public String toString() {
    return "G{" + this.hashCode() +
      " H=" + h.hashCode() + " h的g=" + h.getG().hashCode() +
      '}';
  }
}

//H
public class H {
  public H() {
    System.out.println("h 默认构造函数 hashcode:-----" + this.hashCode());
  }
  private G g;

  public G getG() {
    return g;
  }

  public void setG(G g) {
    this.g = g;
  }

  @Override
  public String toString() {
    return "H{" + this.hashCode() +
      " G=" + g.hashCode() + " G的h="  + g.getH().hashCode()+
      '}';
  }
}

xml配置如下

<bean id="g" class="ioc.circular.G" >
  <property name="h">
    <ref bean="h"/>
  </property>
</bean>

<bean id="h" class="ioc.circular.H" singleton="false">
  <property name="g">
    <ref bean="g"/>
  </property>
</bean>

测试代码如下

@Test
public void testPropertyInjectOneSingletonOnePrototypeGetOnlySingletonObjectOnce() {
  ClassPathResource resource = new ClassPathResource("hello.xml");
  XmlBeanFactory factory = new XmlBeanFactory(resource);
  Object g = factory.getBean("g");
  System.out.println(g);
}

上面的测试代码只获取了单例bean对象,并且只获取了一次单例bean的实例,执行结果如下,一切正常,不存在循环依赖问题

g 默认构造函数 hashcode:-----90320863
h 默认构造函数 hashcode:-----60559178
-----给g设置的h的hashcode:60559178 
G{90320863 H=60559178 h的g=90320863}

如果是下面的测试代码,多次获取单例bean

@Test
public void testPropertyInjectOneSingletonOnePrototypeGetOnlySingletonObjectMultiTimes() {
  ClassPathResource resource = new ClassPathResource("hello.xml");
  XmlBeanFactory factory = new XmlBeanFactory(resource);
  Object g = factory.getBean("g");
  System.out.println(g);
  Object g2 = factory.getBean("g");
  System.out.println(g2);
}

执行结果如下,从中可以发现h对象虽然是一个原型bean,但没有反复创建出来,2次获取的g对象的hashcode一样,其h的hashcode也是一样的

g 默认构造函数 hashcode:-----90320863
h 默认构造函数 hashcode:-----60559178
-----给g设置的h的hashcode:60559178
G{90320863 H=60559178 h的g=90320863}
G{90320863 H=60559178 h的g=90320863}

如果想保持h的原型特性,需要使用后续spring版本提供的作用域代理功能,参见: https://stackoverflow.com/questions/14371335/spring-scoped-proxy-bean

如果你编写下面的代码,获取原型对象一次,也不会产生循环依赖问题,所以有一个基本结论:在spring 1.0中属性间依赖只要有一个bean是单例的,就不会产生死循环的问题

@Test
public void testPropertyInjectOneSingletonOnePrototypeGetOnlyPrototypeObjectOnce() {
  ClassPathResource resource = new ClassPathResource("hello.xml");
  XmlBeanFactory factory = new XmlBeanFactory(resource);
  Object h = factory.getBean("h");
  System.out.println(h);
}

获取一次原型对象的执行结果如下,从中一个非常有趣的现象发生了,就是h对象被实例化了2次,h需要的g被正确设置了,但g本来预期的h对象是hashcode为90320863的这个h,但却是另外一个hashcode为1122134344的对象

h 默认构造函数 hashcode:-----90320863
g 默认构造函数 hashcode:-----60559178
h 默认构造函数 hashcode:-----1122134344
-----给g设置的h的hashcode:1122134344
H{90320863 G=60559178 G的h=1122134344}

而且当你n次获取h的实例时,由于有一个单例G依赖h,总是会产生n+1个h实例,g里面设置的实例通常不是所期望的h,下面的测试代码是3次获取了h

@Test
public void testPropertyInjectOneSingletonOnePrototypeGetOnlyPrototypeObjectMultiTimes() {
  ClassPathResource resource = new ClassPathResource("hello.xml");
  XmlBeanFactory factory = new XmlBeanFactory(resource);
  System.out.println("==========测试多次获取原型,它所依赖的单例bean会怎么处理=========");

  System.out.println("==========第1次获取原型bean=========");
  Object h = factory.getBean("h");
  System.out.println(h);
  System.out.println("==========第2次获取原型bean=========");
  Object h2 = factory.getBean("h");
  System.out.println(h2);
  System.out.println("==========第3次获取原型bean=========");
  Object h3 = factory.getBean("h");
  System.out.println(h3);
}

执行的结果如下:

==========测试多次获取原型,它所依赖的单例bean会怎么处理=========
==========第1次获取原型bean=========
h 默认构造函数 hashcode:-----90320863
g 默认构造函数 hashcode:-----60559178
h 默认构造函数 hashcode:-----1122134344
-----给g设置的h的hashcode:1122134344
H{90320863 G=60559178 G的h=1122134344}
==========第2次获取原型bean=========
h 默认构造函数 hashcode:-----1471868639
H{1471868639 G=60559178 G的h=1122134344}
==========第3次获取原型bean=========
h 默认构造函数 hashcode:-----876563773
H{876563773 G=60559178 G的h=1122134344}

这严格意义上来说是一个bug,至于为什么产生这样的情况,在下一小节中讲解

循环依赖的解决

spring解决循环依赖的办法其实很简单,就是把bean的生命周期分成了逻辑上2大部分:实例化(new一个对象)和初始化。然后获取bean对象时总是先从单例缓存中找,找到就返回,这点在AbstractBeanFactory的getBean方法可以看到

public Object getBean(String name) throws BeansException {
  String beanName = transformedBeanName(name);

  Object sharedInstance = this.singletonCache.get(beanName);
  if (sharedInstance != null) {
    return getObjectForSharedInstance(name, sharedInstance);
  }
}

如果找不到就去走bean的生命周期流程,但是在实例化bean对象出来后,还未完成初始化时,如果是单例bean就立即放到单例缓存中,这点可以在AbstractAutowireCapableBeanFactory类的createBean方法中可以看到

Object bean = instanceWrapper.getWrappedInstance();
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
if (mergedBeanDefinition.isSingleton()) {
  addSingleton(beanName, bean);
}

populateBean(beanName, mergedBeanDefinition, instanceWrapper);

我们以2个单例bean属性依赖的实例化流程来验证下是否解决了

  • 当getBean("c"),实例化c对象,并放置到缓存中,此时c还没有初始化完毕
  • 发现需要注入d,从缓存中找d,没有找到,开始实例化d
  • d实例化后,发现需要注入c,所以从缓存中找,发现找到了c,直接设置给d,此时设置的c是还没有走完生命周期流程的c,还是个半成品
  • d走后实例化之后的后续流程
  • c设置d的流程结束
  • c继续后续流程
  • 最终c与d都实例化完毕,并都放置到了缓存中

一个单例一个原型并且获取原型对象的流程如下

  • getBean("h")这个原型bean,缓存中没有,开始实例化
  • 发现注入注入g,从缓存中找g,没有找到,开启g的实例化
  • g实例化之后,立即放置到缓存中,发现需要注入h,此时g的流程还没有走完
  • 从缓存中找h,因为h是原型的,所以在缓存中找不到,所以开启第二个h的实例化,以准备g设置h
  • 第二个h也需要g,但此时缓存中已经有未完全实例化结束的g这个半成品,但不管怎么样,第二个h赋值g成功
  • 第二个h继续后续流程,此时回到g的设置h的流程,这个流程也结束
  • 之后g的整个流程结束
  • 之后继续进入到第一个h设置g的流程,注入完毕后继续第一个h之后的所有流程
  • 最终h对象创建完毕并返回,g完全实例化并放置在缓存中

FactoryBeanCircularReferenceException

如果一个类依赖一个FactoryBean,那到底是依赖FactoryBean本身还是依赖FactoryBean创建出来的对象呢?以及FactoryBean与普通类互相属性依赖会出现什么情况?

依赖FactoryBean

假定你有如下的类型,此类的类型是Object,以便FactoryBean不管返回什么类型的数据时都不报错

public class CircularTest2 {
  private Object factoryBean;

  public Object getFactoryBean() {
    return factoryBean;
  }

  public void setFactoryBean(Object factoryBean) {
    this.factoryBean = factoryBean;
  }
}


public class CircularTestFactoryBean2 implements FactoryBean {

  @Override
  public Object getObject() throws Exception {
    return "abc";
    // return this;
  }

  @Override
  public Class getObjectType() {
    return null;
  }

  @Override
  public boolean isSingleton() {
    return false;
  }
}

xml的配置如下:

<bean id="factoryBean2" class="ioc.circular.CircularTestFactoryBean2" >
</bean>
<bean id="circularTest2" class="ioc.circular.CircularTest2" >
  <property name="factoryBean">
    <ref bean="factoryBean2"/>
  </property>
</bean>

编写下面的测试代码

@Test
public void testDependencyFactoryBean() {
  ClassPathResource resource = new ClassPathResource("hello.xml");
  XmlBeanFactory factory = new XmlBeanFactory(resource);
  System.out.println("====================getBean============");
  CircularTest2 circular2 = (CircularTest2) factory.getBean("circularTest2");
  System.out.println(circular2.getFactoryBean());
}

最终的执行结果如下,这点已经可以充分的说明一个类依赖FactoryBean,不是依赖FactoryBean本身而是FactoryBean创建出来的对象

abc

FactoryBean与普通类互相依赖

既然一个普通的类依赖FactoryBean时是依赖其创建的对象,而且getBean一个FactoryBean时也是返回此FactoryBean创建的对象而不是FactoryBean本身,那如果一个普通bean与一个FactoryBean互相有属性依赖时会出现什么情况呢?下面的代码就符合这种情况

public class CircularTest {
  private CircularTestFactoryBean factoryBean;

  public CircularTestFactoryBean getFactoryBean() {
    return factoryBean;
  }

  public void setFactoryBean(CircularTestFactoryBean factoryBean) {
    this.factoryBean = factoryBean;
  }
}
// FactoryBean
public class CircularTestFactoryBean implements FactoryBean {
  private CircularTest circularTest;

  public CircularTest getCircularTest() {
    return circularTest;
  }

  public void setCircularTest(CircularTest circularTest) {
    this.circularTest = circularTest;
  }

  @Override//返回的是依赖的属性
  public Object getObject() throws Exception {
    return circularTest;
  }

  @Override
  public Class getObjectType() {
    return null;
  }

  @Override
  public boolean isSingleton() {
    return false;
  }
}

xml配置文件

<bean id="factoryBean" class="ioc.circular.CircularTestFactoryBean" >
  <property name="circularTest">
    <ref bean="circularTest"/>
  </property>
</bean>

<bean id="circularTest" class="ioc.circular.CircularTest" >
  <property name="factoryBean">
    <ref bean="factoryBean"/>
  </property>
</bean>

测试代码如下

@Test
public void testFactoryBeanCircularReference() {
  ClassPathResource resource = new ClassPathResource("hello.xml");
  XmlBeanFactory factory = new XmlBeanFactory(resource);
  System.out.println("====================getBean============");
  Object factoryBean = factory.getBean("factoryBean");
  System.out.println(factoryBean);
}

不管测试代码是想获取FactoryBean对象还是想获取普通bean对象,执行结果都是类似的,最终都是抛出FactoryBeanCircularReferenceException异常

====================getBean============
151  [main] INFO  o.s.b.factory.xml.XmlBeanFactory - Creating shared instance of singleton bean 'factoryBean' 
151  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Creating instance of bean 'factoryBean' with merged definition [Root bean with class [ioc.circular.CircularTestFactoryBean] defined in class path resource [hello.xml]] 
155  [main] DEBUG o.s.b.CachedIntrospectionResults - Getting BeanInfo for class [ioc.circular.CircularTestFactoryBean] 
165  [main] DEBUG o.s.b.CachedIntrospectionResults - Caching PropertyDescriptors for class [ioc.circular.CircularTestFactoryBean] 
165  [main] DEBUG o.s.b.CachedIntrospectionResults - Found property 'circularTest' of type [class ioc.circular.CircularTest]; editor=[null] 
165  [main] DEBUG o.s.b.CachedIntrospectionResults - Found property 'class' of type [class java.lang.Class]; editor=[null] 
165  [main] DEBUG o.s.b.CachedIntrospectionResults - Found property 'object' of type [class java.lang.Object]; editor=[null] 
165  [main] DEBUG o.s.b.CachedIntrospectionResults - Found property 'objectType' of type [class java.lang.Class]; editor=[null] 
165  [main] DEBUG o.s.b.CachedIntrospectionResults - Found property 'singleton' of type [boolean]; editor=[null] 
166  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Resolving reference from property 'circularTest' in bean 'factoryBean' to bean 'circularTest' 
166  [main] INFO  o.s.b.factory.xml.XmlBeanFactory - Creating shared instance of singleton bean 'circularTest' 
166  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Creating instance of bean 'circularTest' with merged definition [Root bean with class [ioc.circular.CircularTest] defined in class path resource [hello.xml]] 
166  [main] DEBUG o.s.b.CachedIntrospectionResults - Getting BeanInfo for class [ioc.circular.CircularTest] 
169  [main] DEBUG o.s.b.CachedIntrospectionResults - Caching PropertyDescriptors for class [ioc.circular.CircularTest] 
169  [main] DEBUG o.s.b.CachedIntrospectionResults - Found property 'class' of type [class java.lang.Class]; editor=[null] 
169  [main] DEBUG o.s.b.CachedIntrospectionResults - Found property 'factoryBean' of type [class ioc.circular.CircularTestFactoryBean]; editor=[null] 
169  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Resolving reference from property 'factoryBean' in bean 'circularTest' to bean 'factoryBean' 
169  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Returning cached instance of singleton bean 'factoryBean' 
169  [main] DEBUG o.s.b.factory.xml.XmlBeanFactory - Bean with name 'factoryBean' is a factory bean 

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'factoryBean' defined in class path resource [hello.xml]: Can't resolve reference to bean 'circularTest' while setting property 'circularTest'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'circularTest' defined in class path resource [hello.xml]: Can't resolve reference to bean 'factoryBean' while setting property 'factoryBean'; nested exception is org.springframework.beans.factory.FactoryBeanCircularReferenceException: Factory bean 'factoryBean' returned null object - possible cause: not fully initialized due to circular bean reference

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'circularTest' defined in class path resource [hello.xml]: Can't resolve reference to bean 'factoryBean' while setting property 'factoryBean'; nested exception is org.springframework.beans.factory.FactoryBeanCircularReferenceException: Factory bean 'factoryBean' returned null object - possible cause: not fully initialized due to circular bean reference
org.springframework.beans.factory.FactoryBeanCircularReferenceException: Factory bean 'factoryBean' returned null object - possible cause: not fully initialized due to circular bean reference
	at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForSharedInstance(AbstractBeanFactory.java:495)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:148)

抛出FactoryBeanCircularReferenceException异常的原因如下

  • getBean("beanFactory")获取对象,实例化FactoryBean本身,并放到缓存,此时getObject方法还没有调用,准备调用
  • 接着准备装配依赖的普通bean
  • 实例化普通bean,放置到缓存
  • 准备装配普通bean依赖的FactoryBean,从缓存中获取到了,接着调用FactoryBean的getObject方法
  • getObject返回的是字段circularTest的值,这个值还没有装配完毕,仍然是null
  • 这样就会又进入到FactoryBean的getObject,所以有死循环

问题代码出现在AbstractBeanFactory的getObjectForSharedInstance方法中,代码如下:

if (beanInstance instanceof FactoryBean) {
  FactoryBean factory = (FactoryBean) beanInstance;

  beanInstance = factory.getObject();

  if (beanInstance == null) {
    throw new FactoryBeanCircularReferenceException(
      "Factory bean '" + beanName + "' returned null object - " +
      "possible cause: not fully initialized due to circular bean reference");
  }
}

FactoryBean的这种情况由于依赖的不是FactoryBean自己,而是其创建出来的对象,所以通过积极缓存(eagerly)是没法解决这个问题的。FactoryBean这种必须是完全初始化之后才能使用的

附录

BeanFactoryUtils

beansOfTypeIncludingAncestors

public static Map beansOfTypeIncludingAncestors(ListableBeanFactory lbf, Class type,
                                                boolean includePrototypes, boolean includeFactoryBeans)
  throws BeansException {
  Map result = new HashMap();
  result.putAll(lbf.getBeansOfType(type, includePrototypes, includeFactoryBeans));
  if (lbf instanceof HierarchicalBeanFactory) {
    HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf;
    if (hbf.getParentBeanFactory() != null && hbf.getParentBeanFactory() instanceof ListableBeanFactory) {
      Map parentResult = beansOfTypeIncludingAncestors((ListableBeanFactory) hbf.getParentBeanFactory(),
                                                       type, includePrototypes, includeFactoryBeans);
      for (Iterator it = parentResult.keySet().iterator(); it.hasNext();) {
        Object key = it.next();
        if (!result.containsKey(key)) {
          result.put(key, parentResult.get((key)));
        }
      }
    }
  }
  return result;
}

getBeansOfType方法是ListableBeanFactory中声明的,DefaultListableBeanFactory类有对其进行实现,代码如下:

public Map getBeansOfType(Class type, boolean includePrototypes, boolean includeFactoryBeans)
  throws BeansException {

  String[] beanNames = getBeanDefinitionNames(type);
  Map result = new HashMap();
  for (int i = 0; i < beanNames.length; i++) {
    if (includePrototypes || isSingleton(beanNames[i])) {
      result.put(beanNames[i], getBean(beanNames[i]));
    }
  }

参考资料

https://xie.infoq.cn/article/e3b46dc2c0125ab812f9aa977 (循环依赖)

1
https://gitee.com/coder_chenjun/spring1.0-jdk1.8-maven.git
git@gitee.com:coder_chenjun/spring1.0-jdk1.8-maven.git
coder_chenjun
spring1.0-jdk1.8-maven
spring1.0-jdk1.8-maven
master

搜索帮助

53164aa7 5694891 3bd8fe86 5694891