Spring 源碼(13)Spring Bean 的創建過程(4)

Spring Bean的創建過程非常的複雜,上一篇重點介紹了Spring在創建Bean的過程中,使用InstantiationBeanPostProcessor進行提前創建Bean,我們可以通過CGLIB創建對象對Bean的方法進行增強,當然也可以進行其他方式的創建方式。通過提前創建Bean,減少了調用doCreateBean方法的複雜邏輯的執行,而且通過這種方式可以訂製創建的方式,便於擴展。

使用 supplier 進行Bean的提前暴露

接下來繼續介紹Spring的創建過程,執行doCreateBean方法:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		// Instantiate the bean.
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
			// 實例化對象
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		Object bean = instanceWrapper.getWrappedInstance();
		Class<?> beanType = instanceWrapper.getWrappedClass();
		if (beanType != NullBean.class) {
			mbd.resolvedTargetType = beanType;
		}
  		// 省略程式碼....
}

這裡會先從快取中獲取FactoryBean實例化的對象,如果有就進行下面的邏輯,一般來說基本是獲取不到的,就會走下面創建createBeanInstance方法。

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
  // Make sure bean class is actually resolved at this point.
  // 解析Bean Class 用於創建對象
  Class<?> beanClass = resolveBeanClass(mbd, beanName);
  // 判斷class必須是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());
  }
  // 獲取到supplier,如果不為空,則創建對象直接返回
  // 擴展點,可以在這裡進行對象的初始化創建,使用BFPP對BeanDefinition進行設置supplier
  Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
  if (instanceSupplier != null) {
    return obtainFromSupplier(instanceSupplier, beanName);
  }
  // 使用FactoryMethod進行對象的創建
  // 擴展點
  if (mbd.getFactoryMethodName() != null) {
    return instantiateUsingFactoryMethod(beanName, mbd, args);
  }
  // 省略部分程式碼....
}

我們可以看到這裡兩個return,意味著只要獲取到Bean,那麼就不需要進行下一步的執行,首先看getInstanceSupplier,這個是BeanDefinition中的方法,那說明可以在解析BeanDefinition的時候進行處理,那麼什麼時候進行BeanDefinition的擴展解析呢?根據前面的介紹可以得知在解析BeanFactoryPostProcessor時可以進行BeanDefinition的處理。

那為啥不是loadBeanDefinition時處理呢?因為Spring在載入階段是沒有提供擴展點的,而在BeanFactoryPostProcessor介面註冊和執行的時候,完全是可以自己定義一個BeanFactoryPostProcessor進行擴展實現。

這個屬性位於AbstractBeanDefinition類中,一般來說用戶自定義的BeanDefinition都是GenericBeanDefinition,而GenericBeanDefinition是繼承這個抽象類的,所以我們在進行BFPP擴展實現時可以對GenericBeanDefinition設置這個屬性值,這個屬性值是一個Supplier函數式介面,相當於lambda表達式的用法,接下來自己實現一個驗證一下。

創建一個SupplierUser對象:

/**
 * @author <a href="//www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
public class SupplierUser {

	private String username;

	public SupplierUser() {
	}

	public SupplierUser(String username) {
		this.username = username;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	@Override
	public String toString() {
		return "SupplierUser{" +
				"username='" + username + '\'' +
				'}';
	}
}

創建一個創建SupplierUser的類:

/**
 * @author <a href="//www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
public class CreateSupplier {

	public static SupplierUser createUser(){
		return new SupplierUser("redwinter");
	}
}

創建BFPP的實現:

/**
 * @author <a href="//www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
public class SupplierBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		BeanDefinition beanDefinition = beanFactory.getBeanDefinition("supplierUser");
		// 獲取原生的BeanDefinition
		GenericBeanDefinition genericBeanDefinition = (GenericBeanDefinition) beanDefinition;
		// 實例化Supplier
		genericBeanDefinition.setInstanceSupplier(CreateSupplier::createUser);
		// 設置類型
		genericBeanDefinition.setBeanClass(CreateSupplier.class);
	}
}

xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
       xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd">
	
	<bean id="supplierUser" class="com.redwinter.test.supplier.SupplierUser"/>
	<bean class="com.redwinter.test.supplier.SupplierBeanFactoryPostProcessor"/>
</beans>

測試類:

/**
 * @author <a href="//www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
public class SupplierTest {

	/**
	 * 使用BFPP設置Supplier進行對象的創建
	 * BFPP可以對BeanDefinition進行設置和修改
	 */
	@Test
	public void test() {
		ApplicationContext ac = new ClassPathXmlApplicationContext("supplier.xml");
		SupplierUser bean = ac.getBean(SupplierUser.class);
		System.out.println(bean);
	}
}

xml中不配置BFPP的時候:

輸出:

SupplierUser{username='null'}

如果配置了BFPP

輸出:

SupplierUser{username='redwinter'}

說明Bean的創建的過程中通過Supplier進行了提前的創建。

接下來看下一個擴展點:

FactoryMethod 對象的創建

根據源碼可以看出這個屬性也是在BeanDefinition中的,但是這個可以通過標籤的方式進行設置,在Springfactory-method創建Bean有兩種方式,一種是靜態工廠創建,一種是實例工廠創建。

接下來實驗一下:

創建電視類,這個就是需要創建的Bean對象:

/**
 * @author <a href="//www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
public class Tv {

	private String name;
	private String age;

	public String getAge() {
		return age;
	}

	public void setAge(String age) {
		this.age = age;
	}

	public String getName() {
		return name;
	}

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

	@Override
	public String toString() {
		return "Tv{" +
				"name='" + name + '\'' +
				", age='" + age + '\'' +
				'}';
	}
}

創建靜態類用於靜態工廠創建bean:

/**
 * 家電類
 * @author <a href="//www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
public class StaticJiaDian {

	public static Tv getTv(String name){
		Tv tv = new Tv();
		tv.setName(name);
		tv.setAge("15");
		return tv;
	}

}

創建實例類,用於實例工廠創建對象:

/**
 * 家電類
 * @author <a href="//www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
public class JiaDian {

	public Tv getTv(String name){
		Tv tv = new Tv();
		tv.setName(name);
		tv.setAge("13");
		return tv;
	}
}

xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
       xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd">
	<!--靜態工廠創建對象-->
	<bean id="tv" class="com.redwinter.test.factorymethod.StaticJiaDian" factory-method="getTv">
		<constructor-arg>
			<value type="java.lang.String">海爾</value>
		</constructor-arg>
	</bean>

	<!--實例工廠-->
	<bean class="com.redwinter.test.factorymethod.JiaDian" id="jiaDian"/>
	<bean id="tv2" class="com.redwinter.test.factorymethod.Tv" factory-bean="jiaDian" factory-method="getTv">
		<constructor-arg>
			<value type="java.lang.String">美的</value>
		</constructor-arg>
	</bean>
</beans>

測試類:

/**
 * @author <a href="//www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
public class FactoryMethodTest {

	/**
	 * factory-method 對象的創建方式
	 * 靜態工廠創建方式: 直接使用靜態工廠類進行創建
	 * 實例工廠創建方式: 需要配合FactoryBean進行創建
	 */
	@Test
	public void test() {
		ApplicationContext ac = new ClassPathXmlApplicationContext("factory-method.xml");
		Tv tv = ac.getBean("tv", Tv.class);
		System.out.println(tv);
		Tv tv2 = ac.getBean("tv2", Tv.class);
		System.out.println(tv2);

	}
}

輸出:

Tv{name='海爾', age='15'}
Tv{name='美的', age='13'}

說明確實是調用了我們自定義的方法創建的對象。

總結下目前來說Bean的創建方式有:

  • 使用FactoryBean創建
  • 使用InstantiationAwreBeanPostProcessor的前置實例化方法postProcessBeforeInstantiation進行創建
  • 使用Supplier進行創建
  • 使用factory-method標籤進行創建
    • 實例工廠創建(配合factory-bean標籤)
    • 靜態工廠創建
  • 反射創建(常規的,完整的創建流程)

本篇就介紹到這裡,下一篇繼續介紹Bean的創建流程。