夯實Spring系列|第十三章:Spring Bean 生命週期-中篇

夯實Spring系列|第十三章:Spring Bean 生命週期-中篇

本章說明

本文是 Spring Bean 生命週期系列的中篇,主要是針對 實例化階段 和實例化之後 屬性賦值階段 的分析和討論。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jeEtSRnr-1588319971643)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429125110856.png)]

1.項目環境

2.Spring Bean 實例化前階段

非主流生命週期 - Bean 實例化前階段

  • org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation

這個階段在實際工作中很少會用到,而且是個非主流的操作,慎用。

可以看下這個接口方法的 Java Doc

Apply this BeanPostProcessor before the target bean gets instantiated.
The returned bean object may be a proxy to use instead of the target bean,

這個 Bean 的前置處理在目標 bean 實例化之前,返回一個 bean 的對象可能是一個代理對象去替換這個目標的 bean。

2.1 示例

  • 自定義類來實現 InstantiationAwareBeanPostProcessor 接口,重寫 postProcessBeforeInstantiation 方法

  • 在這個方法中,我們根據 BeanName 來攔截 superUser,替換成一個新的 SuperUser 對象。

  • 通過 beanFactory.addBeanPostProcessor 來增加我們的實例化前的操作

/**
 * Bean 實例化生命週期
 */
public class BeanInstantiationLifecycleDemo {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        //添加 BeanPostBeanProcess 實現
        beanFactory.addBeanPostProcessor(new MyInstantiationAwareBeanPostProcess());
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        String location = "classpath:/META-INF/dependency-lookup-context.xml";
        int count = reader.loadBeanDefinitions(location);
        System.out.println("Bean 定義的數量: "+count);
        String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
//        Stream.of(beanDefinitionNames).forEach(System.out::println);

        User user = beanFactory.getBean("user", User.class);
        SuperUser superUser = beanFactory.getBean("superUser", SuperUser.class);

        System.out.println(user);
        System.out.println(superUser);
    }

    static class MyInstantiationAwareBeanPostProcess implements InstantiationAwareBeanPostProcessor {
        /**
         * 實例化前階段
         */
        @Override
        public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
            if (ObjectUtils.nullSafeEquals(beanName, "superUser") && beanClass.equals(SuperUser.class)) {
                SuperUser user = new SuperUser();
                user.setName("new-superUser v1");
                return user;
            }
            return null;
        }
}

執行結果:

Bean 定義的數量: 3
User{beanName='user', id=1, name='xwf', age=18, configFileReource=class path resource [META-INF/user-config.properties], city=WUHAN, cities=[WUHAN, BEIJING], lifeCities=[WUHAN, BEIJING]}
SuperUser{address='null'}User{beanName='null', id=null, name='new-superUser v1', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}

2.2 調試

我們將斷點打在示例中的 45 行,也就是我們實現的 postProcessBeforeInstantiation 方法
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-n2KL8iEG-1588319971648)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429084247905.png)]
第一次 user 進來,通過 Idea 左下角的棧信息調用鏈,我們定位到 createBean() 方法
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-AhxJJSCU-1588319971650)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429084443568.png)]
將斷點打在 507行,可以看到 resolveBeforeInstantiation() 方法返回的是 null,程序接着往下執行
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jb15xUxG-1588319971652)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429084628916.png)]
最終我可以發現 user 對象是通過 AbstractAutowireCapableBeanFactory#createBean() 中的 517 行的 doGreatebean() 方法來創建 Bean 的實例化對象。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-EizLVewe-1588319971653)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429084921859.png)]
第二次 superUser 進來,createBean 507 行斷點處,可以看到從 resolveBeforeInstantiation() 方法獲取到的對象是被我們替換之後的 SuperUser (new-superUser v1),因爲 bean 不爲 null,就直接返回了,並沒有走後面的邏輯。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-cEK0mYJx-1588319971655)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200428210155465.png)]
那麼大致可以猜到,我們實現的接口方法 postProcessBeforeInstantiation 應該是在 resolveBeforeInstantiation() 方法中被執行。

我們再繼續往下尋找源碼,最終調用我們自定義邏輯的代碼在AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInstantiation()中,源碼如下:

	@Nullable
	protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof InstantiationAwareBeanPostProcessor) {
				InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
				Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
				if (result != null) {
					return result;
				}
			}
		}
		return null;
	}

代碼比較簡單,遍歷我們所有的 BeanPostProcessor,如果 BeanPostProcessor 類型爲 InstantiationAwareBeanPostProcessor ,執行並返回執行結果。

2.3 結論

  • 原本我們 Spring 默認實例化的 Bean 會被替換成一個代理對象

  • 如果這個階段返回了我們替換之後的對象,後面的 Spring Bean 其他生命週期其他過程都不會執行

3.Spring Bean 實例化階段

實例化方法

  • 傳統實例化方式
    • 實例化策略- InstantiationStrategy
  • 構造器依賴注入

關鍵源碼位置

AbstractAutowireCapableBeanFactory#doCreateBean

3.1 源碼分析

通過上一小節的源碼調試,我們可以知道 Spring Bean 默認實例化的方法是 doCreateBean(),在這方法中又可以找到 createBeanInstance(),這個就是 user 創建的調用代碼

  • 通過 java8 中的函數式接口進行創建
  • 通過 FactoryMethod 方式進行創建

以上兩種方式在本例子中都不存在,可以跳過

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
		// Make sure bean class is actually resolved at this point.
		Class<?> beanClass = resolveBeanClass(mbd, beanName);
        ...
        // 通過 java8 中的函數式接口進行創建
		Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
		if (instanceSupplier != null) {
			return obtainFromSupplier(instanceSupplier, beanName);
		}
        // 通過 FactoryMethod 方式進行創建
		if (mbd.getFactoryMethodName() != null) {
			return instantiateUsingFactoryMethod(beanName, mbd, args);
		}

		// Shortcut when re-creating the same bean...
        // 重複創建相同的 bean,代碼邏輯和下面的差不多,會少一些判斷,這是一種性能的優化
        ... 
            
		// Candidate constructors for autowiring?
        // 可以提供一些構造器的選擇策略 ctors 通常爲 null
        // 自動綁定爲構造器綁定
        // 或者 beanDefintion 中包含構造器參數
        // 獲取 args 不爲空
		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.
        // 非特殊方式:簡單實用無參構造器
		return instantiateBean(beanName, mbd);
	}

從源碼分析中可以知道有兩種實例化的方式

3.1.1 傳統實例化方式 InstantiationStrategy

  • instantiateBean:非特殊方式:簡單實用無參構造器
    • 源代碼較多,我們這裏直接說關鍵源碼
    • SimpleInstantiationStrategy#instantiate()
      [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-7F18dYKG-1588319971656)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429095757114.png)]
  • 最終是通過 ctor.newInstance(argsWithDefaultValues); 來進行實例化,其實就是 Java 反射中的方法。
  • 源碼位置:org.springframework.beans.BeanUtils#instantiateClass(java.lang.reflect.Constructor, java.lang.Object…) 200 行
    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-kD4RVEbQ-1588319971657)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429100747604.png)]
    最後獲取到實例化之後的 User 對象,被封裝成 BeanWrapper 對象,這個對象比較複雜,我們這裏不進行展開
	protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
		try {
			Object beanInstance;
			final BeanFactory parent = this;
            ...
            beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
            // 封裝成 BeanWrapper 對象
			BeanWrapper bw = new BeanWrapperImpl(beanInstance);
            // 初始化 BeanWrapper 對象
			initBeanWrapper(bw);
			return bw;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
		}
	}

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-cMR4VINa-1588319971657)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429100322876.png)]
這裏 User 對象通過無參的構造器實例化完成,所有的屬性都是 null,這就是 Spring Bean 實例化的過程,此時還沒有進行屬性賦值和初始化相關的過程。

3.1.2 構造器依賴注入

3.1.2.1 示例改造

autowireConstructor:需要對上面的例子進行改造

我們增加一個 UserHolder 類,使用構造器注入的方式,注入 User 對象。

/**
 * UserHolder
 */
public class UserHolder {

    private User user;

    public UserHolder(User user) {
        this.user = user;
    }

    @Override
    public String toString() {
        return "UserHolder{" +
                "user=" + user +
                '}';
    }
}

新增 bean-constructor-injection.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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <import resource="classpath:/META-INF/dependency-lookup-context.xml"/>

    <bean id="userHolder" class="com.huajie.thinking.in.spring.bean.lifecycle.domain.UserHolder"
          autowire="constructor"/>
</beans>

示例修改

/**
 * Bean 實例化生命週期
 */
public class BeanInstantiationLifecycleDemo {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        //添加 BeanpostBeanProcess 實現
        beanFactory.addBeanPostProcessor(new MyInstantiationAwareBeanPostProcess());
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        String[] locations = {"classpath:/META-INF/dependency-lookup-context.xml","classpath:/META-INF/bean-constructor-injection.xml"};
        int count = reader.loadBeanDefinitions(locations);
        System.out.println("Bean 定義的數量: " + count);
//        String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
//        Stream.of(beanDefinitionNames).forEach(System.out::println);

        User user = beanFactory.getBean("user", User.class);
        SuperUser superUser = beanFactory.getBean("superUser", SuperUser.class);
        UserHolder userHolder = beanFactory.getBean("userHolder", UserHolder.class);

        System.out.println(user);
        System.out.println(superUser);
        System.out.println(userHolder);
    }

    static class MyInstantiationAwareBeanPostProcess implements InstantiationAwareBeanPostProcessor {
        /**
         * 實例化前階段
         */
        @Override
        public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
            if (ObjectUtils.nullSafeEquals(beanName, "superUser") && beanClass.equals(SuperUser.class)) {
                SuperUser user = new SuperUser();
                user.setName("new-superUser v1");
                return user;
            }
            return null;
        }
    }
}

執行結果:

Bean 定義的數量: 4
User{beanName='user', id=1, name='xwf', age=18, configFileReource=class path resource [META-INF/user-config.properties], city=WUHAN, cities=[WUHAN, BEIJING], lifeCities=[WUHAN, BEIJING]}
SuperUser{address='null'}User{beanName='null', id=null, name='new-superUser v1', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
UserHolder{user=SuperUser{address='null'}User{beanName='null', id=null, name='new-superUser v1', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}}

從執行結果可以看到,通過構造器注入 UserHolder 的對象是 SuperUser,通過結果我們大致可以猜到,帶參構造器注入場景中,參數中的 User 對象是通過類型的方式進入注入的,因爲我們的當前上下文中有兩個 User.class 類型的對象,但是 SuperUser 的 primary 屬性爲 true,所以注入的是 SuperUser 對象,因爲如果是通過 user 名稱進行注入的話,應該注入的是 User 對象。

3.1.2.2 源碼調試

斷點打在 AbstractAutowireCapableBeanFactory#createBeanInstance 1162 行,放掉前面的幾個對象,當 beanName 爲 Holder 我們開始進行調試
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-cmm7Fic5-1588319971658)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429141105304.png)]
將斷點打在 AbstractAutowireCapableBeanFactory#createBeanInstance() 1201 行,可以看到滿足第二個條件

mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR,即 BeanDefintion 的自動綁定方式是構造器
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-sDmFbo3F-1588319971659)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429110713225.png)]
繼續往下探源碼

	protected BeanWrapper autowireConstructor(
			String beanName, RootBeanDefinition mbd, @Nullable Constructor<?>[] ctors, @Nullable Object[] explicitArgs) {

		return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs);
	}

ConstructorResolver#autowireConstructor() 這個方法源碼也非常多,我們省略部分場景,關注重點過程即可;

因爲進來這個方法之前的判斷,是有多個或者的判斷條件,所以此方法裏面又會進行相同的判斷,以便確定具體場景,後面有重要節點的調試截圖

	public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
			@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {

		BeanWrapperImpl bw = new BeanWrapperImpl();
		this.beanFactory.initBeanWrapper(bw);

		Constructor<?> constructorToUse = null;
		ArgumentsHolder argsHolderToUse = null;
		Object[] argsToUse = null;
        // 這個 Args 我們沒配置過 所有爲null 跳過
		if (explicitArgs != null) {
			argsToUse = explicitArgs;
		}
		else {
		... // FactoryMethod場景
        }
	    ...
		if (constructorToUse == null || argsToUse == null) {
			// Take specified constructors, if any.
			Constructor<?>[] candidates = chosenCtors;
			if (candidates == null) {
                // 獲取beanDefintion的class,這個 class 就是我們加載階段從 String 替換成的 Class
				Class<?> beanClass = mbd.getBeanClass();
				try {
                    // 調試點1
                    // mbd.isNonPublicAccessAllowed() 默認爲true 則返回所有的構造器(沒有限定修飾符)
					candidates = (mbd.isNonPublicAccessAllowed() ?
							beanClass.getDeclaredConstructors() : beanClass.getConstructors());
				}
             ...//異常處理
                 
            // 調試點2
            // 通過上面我們獲取到 UserHolder 的構造器,可以滿足這個條件
			if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
				Constructor<?> uniqueCandidate = candidates[0];
                // 但是我們的構造器是帶參數的不滿足這個條件,跳過
				if (uniqueCandidate.getParameterCount() == 0) {
                ...
				}
			}

			// Need to resolve the constructor.
            // 調試點3
            // 我們前面進來的時候已經確定是構造器注入 autowiring = ture
			boolean autowiring = (chosenCtors != null ||
					mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
			ConstructorArgumentValues resolvedValues = null;

            ...//無此場景
            // 對構造器進行排序
			AutowireUtils.sortConstructors(candidates);
			int minTypeDiffWeight = Integer.MAX_VALUE;
			Set<Constructor<?>> ambiguousConstructors = null;
			LinkedList<UnsatisfiedDependencyException> causes = null;

			for (Constructor<?> candidate : candidates) {
            ...
				ArgumentsHolder argsHolder;
                // 調試點4
                // 獲取參數類型
				Class<?>[] paramTypes = candidate.getParameterTypes();
				//對構造器的參數進行分析
				if (resolvedValues != null) {
					try {
                        // 調試點5
                        // 獲取 ConstructorProperties註解 配置的參數名稱
						String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount);
						if (paramNames == null) {
                            // 獲取參數名稱處理器
							ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
							if (pnd != null) {
								paramNames = pnd.getParameterNames(candidate);
							}
						}
                        // 調試點6
                        // 創建參數數組,這個方法等會要單獨分析,因爲我們參數本身也是通過注入方式,會觸發依賴處理過程
						argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
								getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
					}
		...
		Assert.state(argsToUse != null, "Unresolved constructor arguments");
        // 調試點7
        // 設置 BeanWrapper 的 BeanInstance,並返回
		bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
		return bw;
	}

調試點 1 獲取 candidates 構造器信息

獲取 UserHolder 構造器,這裏我們只定義了一個
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-cFmyV5jQ-1588319971660)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429144521721.png)]
調試點2 因爲我們的構造器有一個參數(result = 1),所以並不會進入這段邏輯
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NAmamhm3-1588319971660)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429163016386.png)]
調試點3 autowiring 爲 true,因爲滿足 mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR 這個其實在外面已經判斷過了
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-OoeT4cG0-1588319971661)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429145127853.png)]
調試點4 獲取構造器的參數相關信息

參數類型爲 User.class
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bZUeKrwz-1588319971662)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429145719163.png)]
調試點5 參數名稱爲 user

  • ConstructorPropertiesChecker 是 Java Beans 裏面的規範,可以通過註解的方式配置參數名稱,這裏我們沒有配置所以爲 null

  • ParameterNameDiscoverer 類,這個類是 Spring 專門用來做參數名稱獲取的
    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Kalp2LYo-1588319971663)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429145819487.png)]

調試點6 依賴處理

ConstructorResolver#createArgumentArray

此方法中也會判斷多種構造器參數的場景,本例子的場景在 779 行之後
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-MZPZZ47r-1588319971664)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429164117388.png)]
resolveAutowiredArgument 這個方法會觸發依賴處理,原因就是 User 這個參數需要注入,這個 beanFactory.resolveDependency 我們在之前的章節 第九章:IoC 依賴注入(專題)-下 7.依賴處理過程 小節中分析過這裏就不展開了,我們直接看結果
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-OpDlIFdG-1588319971664)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429164209688.png)]
返回的參數爲 SuperUser,因爲我們之前的例子,在實例化之前對 SuperUser 做了攔截,所以這裏返回的是我們攔截之後的代理對象。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bp8BdSRs-1588319971665)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429161132672.png)]
然後也是通過 BeanUtils#instantiateClass(java.lang.reflect.Constructor, java.lang.Object…)

ctor.newInstance(argsWithDefaultValues);帶參數的 newInstance 來實例化,和 user 實例化的方法不同,user 是無參的構造。

調試點7

最後 bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse)); 將實例化的SuperUser 對象放到 BeanWrapper 中 並返回。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-r87BkRLM-1588319971666)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429165551531.png)]
因此我們可以得到一個結論:

構造器注入默認是按照類型注入,底層方法是 BeanFactory#resolveDependecy()

4.Spring Bean 實例化後階段

Bean 屬性值(Populate)判斷

  • InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation

4.1 示例改造

我們在上面示例的基礎上,在 MyInstantiationAwareBeanPostProcess 新增一個 postProcessAfterInstantiation 的重載方法

/**
 * Bean 實例化生命週期
 */
public class BeanInstantiationLifecycleDemo {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        //添加 BeanpostBeanProcess 實現
        beanFactory.addBeanPostProcessor(new MyInstantiationAwareBeanPostProcess());
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        String[] locations = {"classpath:/META-INF/dependency-lookup-context.xml", "classpath:/META-INF/bean-constructor-injection.xml"};
        int count = reader.loadBeanDefinitions(locations);
        System.out.println("Bean 定義的數量: " + count);
//        String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
//        Stream.of(beanDefinitionNames).forEach(System.out::println);

        User user = beanFactory.getBean("user", User.class);
        SuperUser superUser = beanFactory.getBean("superUser", SuperUser.class);
        // 構造器注入是按照類型注入,resolveDependency
        UserHolder userHolder = beanFactory.getBean("userHolder", UserHolder.class);

        System.out.println(user);
        System.out.println(superUser);
        System.out.println(userHolder);
    }

    static class MyInstantiationAwareBeanPostProcess implements InstantiationAwareBeanPostProcessor {
        /**
         * 實例化前階段
         */
        @Override
        public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
            if (ObjectUtils.nullSafeEquals(beanName, "superUser") && SuperUser.class.equals(beanClass)) {
                SuperUser user = new SuperUser();
                user.setName("new-superUser v1");
                return user;
            }
            return null;
        }

        /**
         * 實例化後階段
         */
        @Override
        public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
            if (ObjectUtils.nullSafeEquals(beanName, "user") && User.class.equals(bean.getClass())) {
                //"user" 對象不允許屬性賦值(配置元信息-> 屬性值)
                User user = User.class.cast(bean);
                user.setId(2L);
                user.setName("after-superUser v2");
                return false;//返回 false 表示忽略掉配置元信息,比如 <bean ...<property name = id value = 1/>
            }
            return true;
        }
    }
}

執行結果:

Bean 定義的數量: 4
User{beanName='user', id=2, name='after-superUser v2', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
SuperUser{address='null'}User{beanName='null', id=null, name='new-superUser v1', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
UserHolder{user=SuperUser{address='null'}User{beanName='null', id=null, name='new-superUser v1', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}}

可以看到 User 對象的 id 和 name 屬性被我們替換掉了。

4.2 源碼調試

斷點打在 AbstractAutowireCapableBeanFactory#populateBean 594 行,當 User 實例化之後,會執行該方法
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-HGMOXY6r-1588319971667)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429202953356.png)]
進入到方法中,這裏邏輯比較簡單,遍歷所有的 BeanPostProcessors,找到類型爲 InstantiationAwareBeanPostProcessor 的(這裏就是我們實現的 MyInstantiationAwareBeanPostProcess 類),執行 postProcessAfterInstantiation 方法。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vF1jtCjk-1588319971668)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429202846314.png)]
如果返回結果爲 false,程序直接 return,後面的操作(屬性賦值)不會執行,當然這個接口默認返回 true 。

4.3 結論

postProcessAfterInstantiation 方法可以用來判斷這個 Bean 是否要進行屬性賦值(populateBean),如果不需要可以進行攔截返回 false ;這個時候 Bean 的實例化已經完成,但是屬性賦值還沒有開始。

5.Spring Bean 屬性賦值前階段

Bean 屬性值元信息

  • PropertyValues

Bean 屬性賦值前回調

  • Spring 1.2 - 5.0 :

    org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor#postProcessPropertyValues

  • Spring 5.1:

    org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor#postProcessProperties

5.1 示例改造

首先 UserHolder 增加兩個屬性, numebr 和 description

public class UserHolder {

    private User user;

    private Integer number;
    
    private String description;

    public UserHolder(User user) {
        this.user = user;
    }

    public Integer getNumber() {
        return number;
    }

    public void setNumber(Integer number) {
        this.number = number;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }


    @Override
    public String toString() {
        return "UserHolder{" +
                "user=" + user +
                ", number=" + number +
                ", description='" + description + '\'' +
                '}';
    }
}

對應的 xml 配置如下,我們只設置 description 的值爲 “The user holder”

    <bean id="userHolder" class="com.huajie.thinking.in.spring.bean.lifecycle.domain.UserHolder"
          autowire="constructor">
        <!--<property name="number" value="1"/>-->
        <property name="description" value="The user holder" />
    </bean>

我們在上面示例的基礎上,在 MyInstantiationAwareBeanPostProcess 新增一個 postProcessProperties 的重載方法

  • 設置 numebr 屬性值爲 1
  • 覆蓋 description 的屬性爲 “The user holder v2”
/**
 * Bean 實例化生命週期
 */
public class BeanInstantiationLifecycleDemo {
    ... //省略掉 main 方法,和之前的代碼一樣
    static class MyInstantiationAwareBeanPostProcess implements InstantiationAwareBeanPostProcessor {
        /**
         * 實例化前階段
         */
        ...//省略掉 postProcessBeforeInstantiation 方法

        /**
         * 實例化後階段
         */
        ...//省略掉 postProcessAfterInstantiation 方法

        // user 跳過 Bean 屬性賦值
        // superUser 也是完全跳過 Bean 實例化
        // 這裏我們只能攔截 UserHolder
        @Override
        public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
            if (ObjectUtils.nullSafeEquals(beanName, "userHolder") && UserHolder.class.equals(bean.getClass())) {
                final MutablePropertyValues propertyValues;
                // 兼容 pvs 爲空的情況 pvs 對應xml中配置的 <property .../>
                if (pvs instanceof MutablePropertyValues) {
                    propertyValues = (MutablePropertyValues) pvs;
                } else {
                    propertyValues = new MutablePropertyValues();
                }
                // 此操作等價於 <property name="number" value="1"/>
                propertyValues.addPropertyValue("number", "1");
                if(propertyValues.contains("description")){
                    // PropertyValue 無法直接覆蓋,因爲是 final 類型
                    PropertyValue description = propertyValues.getPropertyValue("description");
                    propertyValues.removePropertyValue("description");
                    propertyValues.addPropertyValue("description","The user holder v2");
                }
                return propertyValues;
            }
            return null;
        }

    }
}

執行結果:

UserHolder{user=SuperUser{address='null'}User{beanName='null', id=null, name='new-superUser v1', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}, number=1, description='The user holder v2'}

可以看到 numer =1 , 而且 description 被替換成 ‘The user holder v2’

5.2 源碼調試

斷點位置 AbstractAutowireCapableBeanFactory#populateBean 1395 行,當 UserHolder 進來的時候,會走這段邏輯,可以看到此時 pvs 只有 description 一個屬性,因爲我們 xml 文件中只配置了這一個屬性。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Ta2uWsoD-1588319971669)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429221853159.png)]
在 1419 行通過遍歷 BeanPostProcessors 執行我們的 postProcessPropertyValues 方法,pvs 的 length 變成了 2,此時屬性已經變成 number = 1,description = ‘The user holder v2’。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-VjvFN0g6-1588319971671)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429222041844.png)]
最後通過 applyPropertyValues 方法將最新的屬性設置到 bw(BeanWrapper)對象中
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-AAU3qkUH-1588319971671)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200429222628941.png)]

6.Spring Bean 屬性賦值階段

還是上面的例子,applyPropertyValues 其實就是屬性賦值階段。

源碼位置 AbstractAutowireCapableBeanFactory#applyPropertyValues

源碼相對比較簡單,我們就不貼出來了,大致分爲三步

  • 遍歷我們前面傳進來的 pvs 參數
  • 進行 deepCopy 深拷貝
  • 然後通過 bw.setPropertyValues(new MutablePropertyValues(deepCopy)); 進行賦值操作

斷點打在 AbstractAutowireCapableBeanFactory#applyPropertyValues 1732 行
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dTwcrfqj-1588319971672)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200430083523673.png)]

7.參考

  • 極客時間-小馬哥《小馬哥講Spring核心編程思想》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章