spring源码14: 实例化 无参构造器

所有Bean创建的真正逻辑都在doCreateBean函数中,本篇把重点放在第一点实例化bean上,先省略其他无关代码。

bean的实例化实质就是调用构造函数的过程。构造函数分为有参数的构造函数和无参数的构造函数,对比起无参数构造函数,有参数构造函数将大量的工作花在了挑选最合适的构造函数的过程中,并非是核心所在,因此本篇只讲无参数的构造函数

// AbstractAutowireCapableBeanFactory.java
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {

		// Instantiate the bean.
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			// 是单例的情况下清空缓存
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
			/**
			 * 1. 实例化bean,这一阶段是调用构造器生产实例的阶段
			 *
			 * 如果有使用@Autowired构造器注入,则在该阶段完成属性的注入。
			 * (注意这里是Autowired构造器,而不是我们正常使用的注解在属性上)
			 * @Autowired
			 * public Outer(Inner inner) {
			 * 	this.inner = inner;
			 * }
			 */
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		
		...
		// 填充、初始化

}

创建实例

// AbstractAutowireCapableBeanFactory.java
	protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
		// Make sure bean class is actually resolved at this point.
		// 确保Class已经被解析,没Class还实例化给龟龟
		Class<?> beanClass = resolveBeanClass(mbd, beanName);

		// beanClass不为空,且beanClass的修饰符不是public,且非公共构造函数和方法不允许访问
		if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
		}

		// spring5的新特性
		Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
		if (instanceSupplier != null) {
			return obtainFromSupplier(instanceSupplier, beanName);
		}

		// 有工厂方法,直接使用工厂方法
		if (mbd.getFactoryMethodName() != null) {
			return instantiateUsingFactoryMethod(beanName, mbd, args);
		}

		// Shortcut when re-creating the same bean...
		// 是否已经解析过的标志位
		boolean resolved = false;
		boolean autowireNecessary = false;

		/**
		 * 	1. 只有显式参数为空的情况下才尝试使用解析过的缓存,否则一律重新匹配寻找合适的构造方法
		 * 	这里指的不是xml或者注解注入所配置的参数,这里指用户指定显式调用xmlBeanFactory.getBean("dog", "wang");
		 * 	主要是因为xml配置文件和注解spring一旦启动解析完不会变了,而用户显式指定的参数spring根本琢磨不透用户待会想怎么调用
		 *
		 * 	spring寻找bean最适用的构造函数是一件非常繁杂且耗性能的操作,因此如果已经解析构造过一次
		 * 	则会把解析过的构造函数缓存在mbd.resolvedConstructorOrFactoryMethod里,下次则直接使用
		 */
		if (args == null) {
			synchronized (mbd.constructorArgumentLock) {
				if (mbd.resolvedConstructorOrFactoryMethod != null) {
					resolved = true;
					autowireNecessary = mbd.constructorArgumentsResolved;
				}
			}
		}

		// 如果该bean的构造函数已经被解析缓存过
		if (resolved) {
			// 使用已经解析过的构造方法(该构造函数有需要自动注入属性的操作)
			if (autowireNecessary) {
				return autowireConstructor(beanName, mbd, null, null);
			}
			// 已经解析过的构造函数没有参数,则使用默认无参构造方法
			else {
				return instantiateBean(beanName, mbd);
			}
		}

		// Candidate constructors for autowiring?
		// 匹配合适的构造函数
		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
			return autowireConstructor(beanName, mbd, ctors, args);
		}

		// Preferred constructors for default construction?
		// 使用默认构造函数的首选构造函数
		ctors = mbd.getPreferredConstructors();
		if (ctors != null) {
			return autowireConstructor(beanName, mbd, ctors, null);
		}

		// No special handling: simply use no-arg constructor.
		// 2. 使用默认的无参构造函数
		return instantiateBean(beanName, mbd);
	}

  1. 可以看到spring为了提升性能在很多地方都用了缓存的操作,这里也例外,前面提到过有参构造函数的匹配是非常复杂且耗性能的,因此如果匹配解析过加入缓存是很有必要的。
  • 普通参数
public class Dog {
	private String name;

	private int age;

	public Dog(String name, int age) {
		this.name = name;
		this.age = age;
	}
}

// xml
<?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 id="dog" class="Dog">
		<constructor-arg index="0" value="小小"/>
		<constructor-arg index="1" value="3"/>
	</bean>
</beans>

如上方代码,构造函数有2个参数,但是请一定注意这2个参数并不是变量args的值。在spring启动阶段,也就是读取解析xml时,spring便已经将xml中的name, age这2个参数以第一位置跟第二位置的顺序保存到了BeanDefinition中

  • 显式参数 args
Dog dog = xmlBeanFactory.getBean("dog", "wang");

如上方代码,用户通过getBean()方法获取名为dog的bean,而后面"wang"指的就是显式参数。由于是用户指定的参数,对比起1.1的普通参数,这里的优先级更高。因此当存在显式参数时,spring会使用显式参数进行匹配构造函数,例如上面的代码就应该尽量匹配出public Dog(String )这样的构造函数。
还有一个问题,为什么当存在args时就不进行缓存呢?
如1.1所说,spring在启动阶段,就会解析所有xml或者扫描注解,将普通的参数全部读入到BeanDefinition中,spring是可以实现预知有多少种构造函数可能被调用的。但是通过用户显式参数的调用spring并捉摸不透,甚至显式调用的代码可以是动态的,因此并没有缓存的意义


  1. 上方有工厂模式等方式去实例化bean,但是我们还是将重点先放在无参构造器上,这是最简单也式最核心的代码

无参构造器

// AbstractAutowireCapableBeanFactory.java
	protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
		try {
			Object beanInstance;
			final BeanFactory parent = this;
			// 权限管理器不为空,尝试使用特权调用
			if (System.getSecurityManager() != null) {
				beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () ->
						getInstantiationStrategy().instantiate(mbd, beanName, parent),
						getAccessControlContext());
			}
			else {
				// 获取策略,并根据对应策略进行实例化
				beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
			}
			// 包装成BeanWrapper
			BeanWrapper bw = new BeanWrapperImpl(beanInstance);
			initBeanWrapper(bw);
			return bw;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
		}
	}

在这里并没有什么太复杂的操作,至于权限管理器的内容大家可以自行google(太常见了,而且写法固定,下面马上又会出现这个语句),代码大至就是先生产beanInstance,然后将他包装到BeanWrapper中

beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
// SimpleInstantiationStrategy.java
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
		// Don't override the class with CGLIB if no overrides.
		// 1. 如果有lookup或replace方法,则使用cglib代理实例化,否则直接反射
		if (!bd.hasMethodOverrides()) {
			Constructor<?> constructorToUse;
			synchronized (bd.constructorArgumentLock) {
				// 2. 尝试获取已经解析的构造方法
				constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
				if (constructorToUse == null) {
					final Class<?> clazz = bd.getBeanClass();
					if (clazz.isInterface()) {
						throw new BeanInstantiationException(clazz, "Specified class is an interface");
					}
					try {
						if (System.getSecurityManager() != null) {
							constructorToUse = AccessController.doPrivileged(
									(PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);
						}
						else {
							// 获取构造方法
							constructorToUse = clazz.getDeclaredConstructor();
						}
						// 3. 该bean的构造方法已经解析完,缓存起来下一次直接使用
						bd.resolvedConstructorOrFactoryMethod = constructorToUse;
					}
					catch (Throwable ex) {
						throw new BeanInstantiationException(clazz, "No default constructor found", ex);
					}
				}
			}
			// 4. 反射调用构造方法
			return BeanUtils.instantiateClass(constructorToUse);
		}
		else {
			// Must generate CGLIB subclass.
			// 6. 使用cglib
			return instantiateWithMethodInjection(bd, beanName, owner);
		}
	}
  1. 选择实例化策略
  • 当没有lookup-methodreplace-method方法,则当前解析完的class就是目标类了,无需多做操作,直接通过反射调用构造函数便可以创建出实例
  • 如果存在lookup-methoreplace-method方法,比如需要将Class A下的org()方法替换成rep方法,如果直接反射实例化出来的类肯定是不符合要求的。因此我们需要通过cglib包所提供的代理功能,将Class A的字节码进行修改,将字节码org的方法替换成rep的字节码,然后再进行实例化,才是符合条件的类。在AOP的专题会大量用到cglib代理,到时候会详细讲,有兴趣的可以参考:cglib原理Jdk动态代理原理
  1. 即使是无参构造器,spring也在尽量尝试缓存的操作
  2. 如果是接口就直接报错了,如果是正常的类,通过clazz.getDeclaredConstructor()反射获取到构造函数
  3. 直接调用构造函数进行实例化
  4. 使用cglib代理,同时要将owner也就是需要替换的方法传入到,以便生产代理实例
发布了30 篇原创文章 · 获赞 9 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/chaitoudaren/article/details/104833601