Spring 原始碼(13)Spring Bean 的建立過程(4)

玲丶蹊發表於2022-05-12

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="https://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="https://www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
public class CreateSupplier {

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

建立BFPP的實現:

/**
 * @author <a href="https://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="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="supplierUser" class="com.redwinter.test.supplier.SupplierUser"/>
	<bean class="com.redwinter.test.supplier.SupplierBeanFactoryPostProcessor"/>
</beans>

測試類:

/**
 * @author <a href="https://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="https://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="https://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="https://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="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="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="https://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的建立流程。

相關文章