Spring 源碼分析之 bean 實例化原理

本次主要想寫spring bean的實例化相關的內容。建立spring bean 實例是spring bean 生命週期的第一階段。bean 的生命週期主要有以下幾個步驟:面試

  • 建立bean的實例
  • 給實例化出來的bean填充屬性
  • 初始化bea
  • 經過IOC容器使用bean
  • 容器關閉時銷燬bean

在實例化bean以前在BeanDefinition裏頭已經有了全部須要實例化時用到的元數據,接下來spring 只須要選擇合適的實例化方法以及策略便可。實例化方法有兩大類分別是工廠方法和構造方法實例化,後者是最多見的。spring默認的實例化方法就是無參構造函數實例化。
如咱們在xml裏定義的 <bean id="xxx" class="yyy"/> 以及用註解標識的bean都是經過默認實例化方法實例化的。spring

  • 兩種實例化方法(構造函數 和 工廠方法)
  • 源碼閱讀
  • 實例化策略(cglib or 反射)

兩種實例化方

使用適當的實例化方法爲指定的bean建立新實例:工廠方法,構造函數實例化。緩存

代碼演示bash

啓動容器時會實例化全部註冊的bean(lazy-init懶加載的bean除外),對於全部單例非懶加載的bean來講當從容器裏獲取bean(getBean(String name))的時候不會觸發,實例化階段,而是直接從緩存獲取已準備好的bean,而生成bean的時機則是下面這行代碼運行時觸發的。更多關於懶加載的內容能夠參考這篇文章。
Spring lazy-init 原理分析app

@Test
    public void testBeanInstance(){
        // 啓動容器
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");
    }
複製代碼

一 使用工廠方法實例化(不多用)

1.靜態工廠方法
public class FactoryInstance {

    public FactoryInstance() {
        System.out.println("instance by FactoryInstance");
    }
}
複製代碼
public class MyBeanFactory {

    public static FactoryInstance getInstanceStatic(){
        return new FactoryInstance();
    }
}
複製代碼
<?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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="factoryInstance" class="spring.service.instance.MyBeanFactory" 
          factory-method="getInstanceStatic"/>
</beans>
複製代碼

輸出結果爲:ide

instance by FactoryInstance函數

2.實例工廠方法
public class MyBeanFactory {

    /**
     * 實例工廠建立bean實例
     *
     * @return
     */
    public FactoryInstance getInstance() {
        return new FactoryInstance();
    }
}
複製代碼
<?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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 工廠實例 -- >   
    <bean id="myBeanFactory" class="MyBeanFactory"/>
    <bean id="factoryInstance" factory-bean="myBeanFactory" factory-method="getInstance"/>
</beans>
複製代碼

輸出結果爲:源碼分析

instance by FactoryInstancepost

二 使用構造函數實例化(無參構造函數 & 有參構造函數)

1.無參構造函數實例化(默認的)
public class ConstructorInstance {

    public ConstructorInstance() {
        System.out.println("ConstructorInstance none args");
    }

}
複製代碼
<?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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <bean id="constructorInstance" class="spring.service.instance.ConstructorInstance"/>
</beans>
複製代碼

輸出結果爲:ui

ConstructorInstance none args

1.有參構造函數實例化
public class ConstructorInstance {

    private String name;
    
    public ConstructorInstance(String name) {
        System.out.println("ConstructorInstance with args");
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}
複製代碼
<?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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
       
   <bean id="constructorInstance" class="spring.service.instance.ConstructorInstance">
        <constructor-arg index="0" name="name" value="test constructor with args"/>
    </bean>
</beans>
複製代碼

輸出結果爲:

ConstructorInstance with args

源碼閱讀

下面這段是 有關spring bean生命週期的代碼,也是咱們本次要討論的bean 實例化的入口。

doCreateBean方法具體實如今AbstractAutowireCapableBeanFactory類,感興趣的朋友能夠進去看看調用鏈。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
		//第一步 建立bean實例 還未進行屬性填充和各類特性的初始化
		BeanWrapper instanceWrapper = null;
		if (instanceWrapper == null) {
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
		Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);

		Object exposedObject = bean;
		try {
		    // 第二步 進行屬性填充(依賴注入)
			populateBean(beanName, mbd, instanceWrapper);
			if (exposedObject != null) {
			    // 第三步  執行bean的初始化方法
				exposedObject = initializeBean(beanName, exposedObject, mbd);
			}
		}catch (Throwable ex) {
		    //  拋相應的異常
		}

		// Register bean as disposable.
		try {
			registerDisposableBeanIfNecessary(beanName, bean, mbd);
		}catch (BeanDefinitionValidationException ex) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
		}
		return exposedObject;
	}

複製代碼

咱們這裏只需關注第一步建立bean實例的流程便可
instanceWrapper = createBeanInstance(beanName, mbd, args);

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
		// Make sure bean class is actually resolved at this point.
		Class<?> beanClass = resolveBeanClass(mbd, beanName);
        // 使用工廠方法進行實例化
		if (mbd.getFactoryMethodName() != null)  {
			return instantiateUsingFactoryMethod(beanName, mbd, args);
		}
		// Need to determine the constructor...
		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
		// 使用帶參構造函數初始化
		if (ctors != null ||
				mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args))  {
		   		
			return autowireConstructor(beanName, mbd, ctors, args);
		}

		// 默認實例化方式 無參構造實例化
		return instantiateBean(beanName, mbd);
	}
複製代碼

上面代碼就是spring 實現bean實例建立的核心代碼。這一步主要根據BeanDefinition裏的元數據定義決定使用哪一種實例化方法,主要有下面三種:

  • instantiateUsingFactoryMethod 工廠方法實例化的具體實現
  • autowireConstructor 有參構造函數實例化的具體實現
  • instantiateBean 默認實例化具體實現(無參構造函數)

實例化策略(cglib or 反射)

工廠方法的實例化手段沒有選擇策略直接用了發射實現的
實例化策略都是對於構造函數實例化而言的

上面說到的兩構造函數實例化方法無論是哪種都會選一個實例化策略進行,到底選哪種策略也是根據BeanDefinition裏的定義決定的。

beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
複製代碼

上面這一行代碼就是選擇實例化策略的代碼,進入到上面兩種方法的實現以後發現都有這段代碼。

下面選一個instantiateBean的實現來介紹

protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
		try {
			Object beanInstance;
			final BeanFactory parent = this;
			if (System.getSecurityManager() != null) {
				beanInstance = AccessController.doPrivileged(new PrivilegedAction<Object>() {
					@Override
					public Object run() {
						return getInstantiationStrategy().instantiate(mbd, beanName, parent);
					}
				}, getAccessControlContext());
			}
			else {
			    // 在這裏選擇一種策略進行實例化
				beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
			}
			BeanWrapper bw = new BeanWrapperImpl(beanInstance);
			initBeanWrapper(bw);
			return bw;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
		}
	}
複製代碼

選擇使用反射仍是cglib

先判斷若是beanDefinition.getMethodOverrides()爲空也就是用戶沒有使用replace或者lookup的配置方法,那麼直接使用反射的方式,簡單快捷,可是若是使用了這兩個特性,在直接使用反射的方式建立實例就不妥了,由於須要將這兩個配置提供的功能切入進去,因此就必需要使用動態代理的方式將包含兩個特性所對應的邏輯的攔截加強器設置進去,這樣才能夠保證在調用方法的時候會被相應的攔截器加強,返回值爲包含攔截器的代理實例。---引用自《spring 源碼深度剖析》這本書

<bean id="constructorInstance" class="spring.service.instance.ConstructorInstance" >
        <lookup-method name="getName" bean="xxx"/>
        <replaced-method name="getName" replacer="yyy"/>
    </bean>
複製代碼

若是使用了lookup或者replaced的配置的話會使用cglib,不然直接使用反射。
具體lookup-methodreplaced-method的用法能夠查閱相關資料。

public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner) {
		// Don't override the class with CGLIB if no overrides. if (bd.getMethodOverrides().isEmpty()) { constructorToUse = clazz.getDeclaredConstructor((Class[]) null); return BeanUtils.instantiateClass(constructorToUse); }else { // Must generate CGLIB subclass. return instantiateWithMethodInjection(bd, beanName, owner); } } 複製代碼

因爲篇幅省略了部分代碼


其餘文章

【Spring面試題】Spring 爲啥默認把bean設計成單例的?

【Redis面試題】Redis的字符串是怎麼實現的?

Spring 源碼分析之 bean 依賴注入原理(注入屬性)

Spring源碼分析之 lazy-init 實現原理

相關文章
相關標籤/搜索