Spring 原始碼分析之 bean 例項化原理

小小木發表於2019-04-03

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

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

在例項化bean之前在BeanDefinition裡頭已經有了所有需要例項化時用到的後設資料,接下來spring 只需要選擇合適的例項化方法以及策略即可。例項化方法有兩大類分別是工廠方法和構造方法例項化,後者是最常見的。spring預設的例項化方法就是無參建構函式例項化。
如我們在xml裡定義的 <bean id="xxx" class="yyy"/> 以及用註解標識的bean都是通過預設例項化方法例項化的。

  • 兩種例項化方法(建構函式 和 工廠方法)
  • 原始碼閱讀
  • 例項化策略(cglib or 反射)

兩種例項化方

使用適當的例項化方法為指定的bean建立新例項:工廠方法,建構函式例項化。

程式碼演示

啟動容器時會例項化所有註冊的bean(lazy-init懶載入的bean除外),對於所有單例非懶載入的bean來說當從容器裡獲取bean(getBean(String name))的時候不會觸發,例項化階段,而是直接從快取獲取已準備好的bean,而生成bean的時機則是下面這行程式碼執行時觸發的。更多關於懶載入的內容可以參考這篇文章。
Spring lazy-init 原理分析

  @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>
複製程式碼

輸出結果為:

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 FactoryInstance

二 使用建構函式例項化(無參建構函式 & 有參建構函式)

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>
複製程式碼

輸出結果為:

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 實現原理

相關文章