Spring Ioc之初始化

左眼眸子發表於2018-08-10

引言

spring載入xml的物件資訊解析例項化成各個bean的過程我在這裡就不細講了,畢竟從頭開始看很容易繞暈大家,反而讓大家覺得這並不需要寫。我們姑且認為spring已經載入好了各類物件資訊封裝成BeanDefinition,並已經例項化儲存在了某個地方。不管是懶漢還是餓漢,都要經歷反射出物件例項,然後初始化,我們先從spring中比較好理解的地方來入手IOC,那就是IOC中的bean在例項化之後的初始化操作。主要涉及到BeanPostProcessor,InitializingBean這兩個的應用。

IOC的bean初始化

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
		Object wrappedBean = bean;
		if (mbd == null || !mbd.isSynthetic()) {
			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
		}

		invokeInitMethods(beanName, wrappedBean, mbd);

		if (mbd == null || !mbd.isSynthetic()) {
			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
		}
		return wrappedBean;
	}
複製程式碼

為了方便閱讀,我刪除了部分不相干的程式碼。 AbstractAutowireCapableBeanFactory#initializeBean就是初始化IOC容器中的Bean的主要方法,從這個方法入手,來看看IOC載入Bean到底做了什麼? 將例項化好的bean傳入該方法:

--> 呼叫BeanPostProcessor的postProcessBeforeInitialization方法
--> 呼叫bean例項的初始化方法
--> 呼叫BeanPostProcessor的postProcessAfterInitialization方法
複製程式碼

以下三段程式碼就是這三個過程:

@Override
	public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
			throws BeansException {

		Object result = existingBean;
		for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
			result = beanProcessor.postProcessBeforeInitialization(result, beanName);
			if (result == null) {
				return result;
			}
		}
		return result;
	}
複製程式碼

迴圈所有實現了BeanPostProcessor類的bean,並執行相應的物件初始化之前的方法postProcessBeforeInitialization

protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
			throws Throwable {

		boolean isInitializingBean = (bean instanceof InitializingBean);
		if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
			if (logger.isDebugEnabled()) {
				logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
			}
			if (System.getSecurityManager() != null) {
				try {
					AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
						@Override
						public Object run() throws Exception {
							((InitializingBean) bean).afterPropertiesSet();
							return null;
						}
					}, getAccessControlContext());
				}
				catch (PrivilegedActionException pae) {
					throw pae.getException();
				}
			}
			else {
				((InitializingBean) bean).afterPropertiesSet();
			}
		}

		if (mbd != null) {
			String initMethodName = mbd.getInitMethodName();
			if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
					!mbd.isExternallyManagedInitMethod(initMethodName)) {
				invokeCustomInitMethod(beanName, bean, mbd);
			}
		}
	}
複製程式碼

該段程式碼給一些實現了InitializingBean的bean進行初始化操作。

注意:

1:spring為bean提供了兩種初始化的方式,需要實現InitializingBean介面,重寫afterPropertiesSet方法,或者在配置檔案中同過init-method指定,兩種方式可以同時使用,但是會先執行afterPropertiesSet再執行init-method。下文還有demo證明。

2:實現InitializingBean介面是直接呼叫afterPropertiesSet方法,比通過反射呼叫init-method指定的方法效率相對來說要高點。但是init-method方式消除了對spring的依賴

3:如果呼叫afterPropertiesSet方法時出錯,則不呼叫init-method指定的方法。

@Override
	public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
			throws BeansException {

		Object result = existingBean;
		for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
			result = beanProcessor.postProcessAfterInitialization(result, beanName);
			if (result == null) {
				return result;
			}
		}
		return result;
	}
複製程式碼

再次迴圈所有實現了BeanPostProcessor類的bean,並執行相應的物件初始化之前的方法postProcessAfterInitialization。順便提一下,以後要寫的Spring AOP的底層處理也是通過實現BeanPostProcessor來執行代理包裝邏輯的。

IOC的bean注入

我們的ioc物件初始化好了,接下來就要看看最關鍵的依賴注入了。先思考一個問題,spring在什麼時候把物件注入進去的?這裡先不解釋,我們看這段原始碼:

protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
		if (pvs instanceof MutablePropertyValues) {
			...
		}
		else {
			original = Arrays.asList(pvs.getPropertyValues());
		}

		TypeConverter converter = getCustomTypeConverter();
		if (converter == null) {
			converter = bw;
		}
		BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);

		// Create a deep copy, resolving any references for values.
		List<PropertyValue> deepCopy = new ArrayList<PropertyValue>(original.size());
		boolean resolveNecessary = false;
		for (PropertyValue pv : original) {
			if (pv.isConverted()) {
				deepCopy.add(pv);
			}
			else {
				String propertyName = pv.getName();
				Object originalValue = pv.getValue();
				//在這裡解析並塞入了注入的物件
				Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
				Object convertedValue = resolvedValue;
				boolean convertible = bw.isWritableProperty(propertyName) &&
						!PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
				if (convertible) {
					convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
				}
				// Possibly store converted value in merged bean definition,
				// in order to avoid re-conversion for every created bean instance.
				if (resolvedValue == originalValue) {
					if (convertible) {
						pv.setConvertedValue(convertedValue);
					}
					deepCopy.add(pv);
				}
				else if (convertible && originalValue instanceof TypedStringValue &&
						!((TypedStringValue) originalValue).isDynamic() &&
						!(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) {
					pv.setConvertedValue(convertedValue);
					deepCopy.add(pv);
				}
				else {
					resolveNecessary = true;
					deepCopy.add(new PropertyValue(pv, convertedValue));
				}
			}
		}
	}
複製程式碼

我還是刪了很多其他邏輯,這裡主要的一句話就是Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue); 在這裡分析並塞入了注入物件的關聯。具體怎麼操作還得看自己去翻閱。接下來我用最簡化的程式碼方式來展示我的尋找過程:

AbstractBeanFactory類
@Override
public Object getBean(String name) throws BeansException {
	return doGetBean(name, null, null, false);
}
複製程式碼

我們肯定找到拿到每個bean的如果,如果不存在就會建立。

AbstractBeanFactory類
@SuppressWarnings("unchecked")
protected <T> T doGetBean(
		final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
		throws BeansException {
    // Create bean instance.
    if (mbd.isSingleton()) {
    	sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
    		@Override
    		public Object getObject() throws BeansException {
    			try {
    				return createBean(beanName, mbd, args);
    			}
    			catch (BeansException ex) {
    				// Explicitly remove instance from singleton cache: It might have been put there
    				// eagerly by the creation process, to allow for circular reference resolution.
    				// Also remove any beans that received a temporary reference to the bean.
    				destroySingleton(beanName);
    				throw ex;
    			}
    		}
    	});
    	bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    }
}
複製程式碼

然後再進入createBean(),它的實現是在 AbstractAutowireCapableBeanFactory 當中:

AbstractAutowireCapableBeanFactory類
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
    Object beanInstance = doCreateBean(beanName, mbdToUse, args);
	return beanInstance;
}
複製程式碼
AbstractAutowireCapableBeanFactory類
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
			throws BeanCreationException {
	BeanWrapper instanceWrapper = null;
	if (mbd.isSingleton()) {
		instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
	}
	if (instanceWrapper == null) {
		instanceWrapper = createBeanInstance(beanName, mbd, args);
	}
    // Initialize the bean instance.
	Object exposedObject = bean;
	try {
		populateBean(beanName, mbd, instanceWrapper);
		if (exposedObject != null) {
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
	}			
}
複製程式碼

重點關注 createBeanInstance() 和 populateBean() 這兩個方法。其中,createBeanInstance方法生成了Bean所包含的Java物件:

AbstractAutowireCapableBeanFactory類
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
    if (resolved) {
		if (autowireNecessary) {
			return autowireConstructor(beanName, mbd, null, null);
		}
		else {
			return instantiateBean(beanName, mbd);
		}
	}
}
複製程式碼
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
    Object beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
    BeanWrapper bw = new BeanWrapperImpl(beanInstance);
	initBeanWrapper(bw);
	return bw;
}
複製程式碼

重點關注 getInstantiationStrategy()這個方法,可以看到instantiateBean方法的功能實現是通過呼叫getInstantiationStrategy().instantiate方法實現的。 getInstantiationStrategy方法的作用是獲得例項化的策略物件,也就是指通過哪種方案進行例項化的過程。繼續跟蹤下去我們可以發現,Spring當中提供了兩種例項化方案: BeanUtils和Cglib方式。BeanUtils實現機制是通過Java的反射機制,Cglib是一個第三方類庫採用的是一種位元組碼加強方式機制。Spring中採用的預設例項化策略是Cglib。

接下來就是重頭戲建立bean的依賴關係了。我們回到doCreateBean方法中的populateBean(beanName, mbd, instanceWrapper);

protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
    applyPropertyValues(beanName, mbd, bw, pvs);
}
複製程式碼

有沒有發現,我們已經來到我開始講的applyPropertyValues方法了? 下面我們就不看原始碼了,我們用反射來把value注入進去,這樣更容易理解。

try {
	Method declaredMethod = bean.getClass().getDeclaredMethod(
			"set" + propertyValue.getName().substring(0, 1).toUpperCase()
					+ propertyValue.getName().substring(1), value.getClass());
	declaredMethod.setAccessible(true);
	declaredMethod.invoke(bean, value);
} catch (NoSuchMethodException e) {
	Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName());
	declaredField.setAccessible(true);
	declaredField.set(bean, value);
}
複製程式碼

拿到set引數的方法,如果出現異常,表示並沒有寫set方法,則就粗暴的方法塞入field。(此處並不是原始碼,是我根據原始碼理解寫的比較通俗易懂的方式)

這是我覺得在繁多的spring原始碼中找出一段目的碼先看到,更容易讓人能夠跟著思路走下去。

題外話:

synchronized (this.dependenciesForBeanMap) {
			Set<String> dependenciesForBean =
				this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
			dependenciesForBean.add(canonicalName);
		}
複製程式碼

在看IOC原始碼的時候看到Map的computeIfAbsent讓我恍然大悟,我來用以前怎麼寫這段程式碼的來解釋這段程式碼什麼意思如下:

Set<String> dependenciesForBean = this.dependenciesForBeanMap.get(dependentBeanName);
if (dependenciesForBean != null){
    dependenciesForBean = new LinkedHashSet<>(8);
}
dependenciesForBean.add(dependentBeanName);
dependenciesForBean.add(canonicalName);

複製程式碼

這是jdk1.8才支援的lambda寫法,是不是程式碼更簡潔?

再來分享一種簡潔的方法,計算每個學生的總分記錄到map中:

List<Student> students = new ArrayList<>();
students.add(new Student("張三", "語文", 18));
students.add(new Student("張三", "數學", 20));
Map<String, Integer> resultMap = new HashMap<>();
for (Student student : students) {
    resultMap.merge(student.getName(), student.getScore(), (a, b) -> b + a);
}
複製程式碼

相關文章