Spring源碼-IOC容器(四)-FactoryBean

Spring IOC容器 源碼解析系列,建議你們按順序閱讀,歡迎討論java

(spring源碼均爲4.1.6.RELEASE版本)spring

  1. Spring源碼-IOC容器(一)-構建簡單IOC容器
  2. Spring源碼-IOC容器(二)-Bean的定位解析註冊
  3. Spring源碼-IOC容器(三)-GetBean
  4. Spring源碼-IOC容器(四)-FactoryBean
  5. Spring源碼-IOC容器(五)-Bean的初始化
  6. Spring源碼-IOC容器(六)-bean的循環依賴
  7. Spring源碼-IOC容器(七)-ApplicationContext
  8. Spring源碼-IOC容器(八)-NamespaceHandler與自定義xml
  9. Spring源碼-IOC容器(九)-Component-Scan源碼解析
  10. Spring源碼-IOC容器(十)-@Autowired解析

在上一篇咱們分析了Spring IOC的核心部分——bean的建立和組裝,最後留下了FactoryBean的處理並未講解,是由於我以爲應當單獨用一篇來詳細介紹,也所以足見它的重要性。在Spring的文檔中是這樣定義FactoryBean的:express

The FactoryBean interface is a point of pluggability into the Spring IoC container’s instantiation logic. If you have complex initialization code that is better expressed in Java as opposed to a (potentially) verbose amount of XML, you can create your own FactoryBean, write the complex initialization inside that class, and then plug your custom FactoryBean into the container.編程

翻譯過來就是設計模式

FactoryBean接口是Spring IOC容器的實例化邏輯的可插拔點。若是有複雜的bean初始化,相對於冗長的xml方式,指望經過java編程的方式來表達,就能夠經過建立自定義的FactoryBean來實現並將FactoryBean插入到IOC容器中。緩存

上面的解釋可能有些抽象,簡單地說,FactoryBean就是能夠建立Bean對象的工廠Bean。在Spring中,經過FactoryBean來擴展的遍地都是:AOP,ORM,事務管理,JMX,Remoting,Freemarker,Velocity等等。下面咱們就來分析下FactoryBean的原理。ide

#1.FactoryBean的定義#工具

FactoryBean接口只有三個方法post

  • getObject()學習

  • getObjectType()

  • isSingleton()

    public interface FactoryBean<T> {
    
      	//返回工廠建立的bean對象實例,能夠是單例的也能夠是多例
      	T getObject() throws Exception;
    
      	// 返回建立對象的類型
      	Class<?> getObjectType();
    
          // 建立的對象是否單例
      	boolean isSingleton();
      }

#2.FactoryBean的原理#

如何判斷一個bean是FactoryBean,除了根據對象是否實現了FactoryBean接口,在BeanFactory容器基礎接口中特別定義了FactoryBean的前綴。

public interface BeanFactory {

	String FACTORY_BEAN_PREFIX = "&";
}

給定一個id=mybean的FactoryBean,getBean("mybean")獲得的就是這個FactoryBean建立的對象實例,而getBean("&mybean")獲得的確實FactoryBean自身對象。

在AbstractBeanFactory的doGetBean中,當建立好或獲取到Bean的對象實例後,不管是singleton、prototype或者其餘scope的,都會調用getObjectForBeanInstance方法,這個方法就是處理FactoryBean的入口

protected Object getObjectForBeanInstance(
		Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {

	// 判斷若是請求一個&前綴的beanName,而實例化的對象不是FactoryBean的子類,則拋出BeanIsNotAFactoryException異常
	if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
		throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
	}

	// 若是bean實例對象不是FactoryBean的子類,或者請求的beanName以&前綴,則直接返回bean實例對象
	if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
		return beanInstance;
	}

	Object object = null;
	// mbd==null說明FactoryBean實例對象是單例,且從單例緩存中取出,則從緩存中查詢FactoryBean建立的bean實例對象
	if (mbd == null) {
		object = getCachedObjectForFactoryBean(beanName);
	}
	if (object == null) {
		// 強制轉換beanInstance爲FactoryBean
		FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
		// 緩存不存在且mbd==null,則根據beanName得到RootBeanDefinition
		if (mbd == null && containsBeanDefinition(beanName)) {
			mbd = getMergedLocalBeanDefinition(beanName);
		}
		// bean是否爲合成的,合成bean在得到FactoryBean建立好的bean對象實例後,不須要後置處理
		boolean synthetic = (mbd != null && mbd.isSynthetic());
		// FactoryBean建立bean實例對象
		object = getObjectFromFactoryBean(factory, beanName, !synthetic);
	}
	return object;
}

BeanFactoryUtils.isFactoryDereference(name)方法判斷name是否以&前綴

public static boolean isFactoryDereference(String name) {
	return (name != null && name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
}

getObjectFromFactoryBean是實際操做的入口。

protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
	// FactoryBean是單例,且已存在單例對象
	if (factory.isSingleton() && containsSingleton(beanName)) {
		// 以singletonObjects爲鎖,保證建立的對象爲單例
		synchronized (getSingletonMutex()) {
			// 查詢緩存是否存在
			Object object = this.factoryBeanObjectCache.get(beanName);
			if (object == null) {
				// 調用FactoryBean的getObject方法獲取bean實例對象
				object = doGetObjectFromFactoryBean(factory, beanName);
				// 再次查詢緩存是否存在
				Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
				if (alreadyThere != null) {
					object = alreadyThere;
				}
				else {
					// 調用FactoryBean後置處理
					// 默認直接返回bean
					if (object != null && shouldPostProcess) {
						try {
							object = postProcessObjectFromFactoryBean(object, beanName);
						}
						catch (Throwable ex) {
							throw new BeanCreationException(beanName,
									"Post-processing of FactoryBean's singleton object failed", ex);
						}
					}
					// 加入緩存
					this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT));
				}
			}
			return (object != NULL_OBJECT ? object : null);
		}
	}
	else {
		// FactoryBean爲多例,直接調用getObject方法獲取bean實例對象
		Object object = doGetObjectFromFactoryBean(factory, beanName);
		// FactoryBean後置處理
		if (object != null && shouldPostProcess) {
			try {
				object = postProcessObjectFromFactoryBean(object, beanName);
			}
			catch (Throwable ex) {
				throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
			}
		}
		return object;
	}
}

doGetObjectFromFactoryBean方法爲實際獲取FactoryBean建立的bean實例對象的觸發點,核心方法就是調用FactoryBean的getObject方法

private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName)
		throws BeanCreationException {

	Object object;
	try {
		if (System.getSecurityManager() != null) {
			AccessControlContext acc = getAccessControlContext();
			try {
				object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
					[@Override](https://my.oschina.net/u/1162528)
					public Object run() throws Exception {
							return factory.getObject();
						}
					}, acc);
			}
			catch (PrivilegedActionException pae) {
				throw pae.getException();
			}
		}
		else {
			object = factory.getObject();
		}
	}
	catch (FactoryBeanNotInitializedException ex) {
		throw new BeanCurrentlyInCreationException(beanName, ex.toString());
	}
	catch (Throwable ex) {
		throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);
	}

	// Do not accept a null value for a FactoryBean that's not fully
	// initialized yet: Many FactoryBeans just return null then.
	if (object == null && isSingletonCurrentlyInCreation(beanName)) {
		throw new BeanCurrentlyInCreationException(
				beanName, "FactoryBean which is currently in creation returned null from getObject");
	}
	return object;
}

對於單例的FactoryBean,生產出的bean對象實例也是單例的並有緩存,而多例的也是遵循每請求一次就建立一個新對象。

#3. PropertiesFactoryBean#

看完FactoryBean的原理,咱們來介紹一個簡單的實例。PropertiesFactoryBean是常常使用的spring資源配置文件加載工具,一般使用#{prop.key}來獲取資源文件的屬性值,prop爲PropertiesFactoryBean在spring容器中的name,而key爲資源文件中的key,可是key經常以點號分隔,好比key.name=value這樣的,則能夠經過#{prop['key.name']}這樣的表達式來獲取。來看個具體的例子。

example.properties文件中定義了key爲example.factorybean的一個配置

example.factorybean=PropertiesFactoryBean

PropertiesBean.java須要注入配置文件中的配置到propertiesValue屬性中,並將propertiesValue的值打印出來。

package com.lntea.spring.demo.bean;


public class PropertiesBean {

	private String propertiesValue;
	
	public void print(){
		System.out.println("propertiesValue:"+propertiesValue);
	}

	public String getPropertiesValue() {
		return propertiesValue;
	}

	public void setPropertiesValue(String propertiesValue) {
		this.propertiesValue = propertiesValue;
	}
}

properties.xml定義了PropertiesFactoryBean,設置name爲prop,並對locations屬性賦值classpath下的example.properties資源。另外定義了上面的PropertiesBean,指定propertiesValue屬性的值爲#{prop['example.factorybean']}

<?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-3.0.xsd">
	
	<bean id="prop" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
		<property name="locations">
			<value>classpath:example.properties</value>
		</property>
	</bean>
	
	<bean id="propertiesBean" class="com.lntea.spring.demo.bean.PropertiesBean">
		<property name="propertiesValue" value="#{prop['example.factorybean']}"></property>
	</bean>
</beans>

來下個測試

ApplicationContext context = new ClassPathXmlApplicationContext("properties.xml");
 PropertiesBean propertiesBean = context.getBean("propertiesBean",PropertiesBean.class);
 propertiesBean.print();

執行結果拿到了example.factorybean對應的值「PropertiesFactoryBean」。看過以前spring的xml文件解析的可能會問,property標籤裏的value屬性解析出來就是String對象啊,怎麼會轉換成資源文件裏的值呢。

這裏簡要介紹一下,ApplicationContext建立時會默認注入一個spring表達式的解析類,叫StandardBeanExpressionResolver,負責解析#{}這樣的表達式。當拿到value屬性中的#{prop['example.factorybean']},解析類識別出#{}的表達式,而後再從spring容器中查找name=prop的bean對象,由於咱們在properties.xml中配置過PropertiesFactoryBean的id=prop,所以就會經過getBean加載,而PropertiesFactoryBean是FactoryBean的子類,最後就經過 getObejct方法獲取真正的bean實例對象。返回的bean實力對象是一個Properties對象,再從中查詢example.factorybean的key對應的值,獲得最終的結果。關於spring表達式的解析這裏就略過,咱們主要來看下PropertiesFactoryBean的源碼。

public class PropertiesFactoryBean extends PropertiesLoaderSupport
	implements FactoryBean<Properties>, InitializingBean {

	private boolean singleton = true;

	private Properties singletonInstance;


	// 設置是否單例
	public final void setSingleton(boolean singleton) {
		this.singleton = singleton;
	}

	[@Override](https://my.oschina.net/u/1162528)
	publ	ic final boolean isSingleton() {
		return this.singleton;
	}


	// 實現InitializingBean接口,初始化時調用
	// 讀取配置文件加載到Properties對象中
	[@Override](https://my.oschina.net/u/1162528)
	public final void afterPropertiesSet() throws IOException {
		if (this.singleton) {
			this.singletonInstance = createProperties();
		}
	}

	// 返回加載完的Properties對象
	[@Override](https://my.oschina.net/u/1162528)
	public final Properties getObject() throws IOException {
		if (this.singleton) {
			return this.singletonInstance;
		}
		else {
			return createProperties();
		}
	}

	[@Override](https://my.oschina.net/u/1162528)
	public Class<Properties> getObjectType() {
		return Properties.class;
	}


	// 資源文件加載方法
	protected Properties createProperties() throws IOException {
		return mergeProperties();
	}

}

PropertiesFactoryBean實現了InitializingBean接口的afterPropertiesSet方法,在bean初始化時調用createProperties方法加載資源文件。而實際調用的mergeProperties在父類PropertiesLoaderSupport中實現。

protected Properties mergeProperties() throws IOException {
	Properties result = new Properties();

	if (this.localOverride) {
		// Load properties from file upfront, to let local properties override.
		loadProperties(result);
	}

	if (this.localProperties != null) {
		for (Properties localProp : this.localProperties) {
			CollectionUtils.mergePropertiesIntoMap(localProp, result);
		}
	}

	if (!this.localOverride) {
		// Load properties from file afterwards, to let those properties override.
		loadProperties(result);
	}

	return result;
}

protected void loadProperties(Properties props) throws IOException {
	if (this.locations != null) {
		for (Resource location : this.locations) {
			if (logger.isInfoEnabled()) {
				logger.info("Loading properties file from " + location);
			}
			try {
				PropertiesLoaderUtils.fillProperties(
						props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister);
			}
			catch (IOException ex) {
				if (this.ignoreResourceNotFound) {
					if (logger.isWarnEnabled()) {
						logger.warn("Could not load properties from " + location + ": " + ex.getMessage());
					}
				}
				else {
					throw ex;
				}
			}
		}
	}
}

最後經過PropertiesLoaderUtils.fillProperties方法讀取配置文件的輸入流加載到Properties對象中。

經過源碼看起來PropertiesFactoryBean的實現比較簡單,首先實現InitializingBean接口,再bean初始化時加載資源,當調用FactoryBean的getObject方法時將加載完的Properties對象返回。**其實大部分的FactoryBean的子類都是經過此種方式來完成和spring的對接,先是在初始化時處理準備工做,而後在getObject調用時返回真正的bean實例對象。**而FactoryBean做爲Spring的重要擴展之一,其實現方式如此簡單,真的值得好好學習。

#4.工廠方法模式#

上面咱們聊了FactoryBean的原理和實現,最後來看下它的設計模式。工廠方法模式其實很是常見,應用者衆多,由於其很好理解且很是使用。先來看下FactoryBean的UML圖 。 工廠方法模式

FactoryBean是工廠的接口,接口中的getObject方法返回的產品默認是一個Object。PropertiesFactoryBean是工廠的一個實現,生產出來的是Object的子類Properties。對於不一樣的工廠實現,生產出來的產品也是不一樣的。好比JndiObjectFactoryBean返回的是JNDI對象,RmiProxyFactoryBean返回的是RMI對象。

工廠方法模式,在編寫代碼時並不清楚要建立的對象是什麼,於是只定義接口及通用方法,把具體的實現交給子類來處理,由於不一樣的子類所建立的對象並不一致。

這一章中咱們重點討論了FactoryBean,由於它是spring容器的重要擴展之一,而另外一重要的擴展方式則是BeanPostProcessor,接下來咱們就來分析下BeanPostProcessor和bean的初始化。

相關文章
相關標籤/搜索