FactoryBean——Spring的擴展點之一

掃描下方二維碼或者微信搜索公衆號菜鳥飛呀飛,便可關注微信公衆號,閱讀更多Spring源碼分析文章html

微信公衆號

首先須要說明的是,FactoryBean和BeanFactory雖然名字很像,可是這二者是徹底不一樣的兩個概念,用途上也是天差地別。BeanFactory是一個Bean工廠,在必定程度上咱們能夠簡單理解爲它就是咱們日常所說的Spring容器(注意這裏說的是簡單理解爲容器),它完成了Bean的建立、自動裝配等過程,存儲了建立完成的單例Bean。而FactoryBean經過名字看,咱們能夠猜出它是Bean,但它是一個特殊的Bean,究竟有什麼特殊之處呢?它的特殊之處在咱們平時開發過程當中又有什麼用處呢?java

1. FactoryBean的用法

FactoryBean的特殊之處在於它能夠向容器中註冊兩個Bean,一個是它自己,一個是FactoryBean.getObject()方法返回值所表明的Bean。先經過以下示例代碼來感覺下FactoryBean的用處吧。spring

  • 自定義一個類CustomerFactoryBean,讓它實現了FactoryBean接口,重寫了接口中的兩個方法,在getObejct()方法中,返回了一個UserService的實例對象;在getObjectType()方法中返回了UserService.class。而後在CustomerFactoryBean添加了註解@Component註解,意思是將CustomerFactoryBean類交給Spring管理。
package com.tiantang.study.component;

import com.tiantang.study.service.UserService;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;

@Component
public class CustomerFactoryBean implements FactoryBean<UserService> {
    @Override
    public UserService getObject() throws Exception {
        return new UserService();
    }

    @Override
    public Class<?> getObjectType() {
        return UserService.class;
    }
}
複製代碼
  • 定義了一個UserService類,在構造方法中打印了一行日誌。
package com.tiantang.study.service;

public class UserService {

    public UserService(){
        System.out.println("userService construct");
    }
}
複製代碼
  • 定義了一個配置類AppConfig,在類中指明瞭Spring須要掃描包com.tiantang.study.component
package com.tiantang.study.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.tiantang.study.component")
public class AppConfig {
}
複製代碼
  • 啓動類
public class MainApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        System.out.println("容器啓動完成");
        UserService userService = applicationContext.getBean(UserService.class);
        System.out.println(userService);
        Object customerFactoryBean = applicationContext.getBean("customerFactoryBean");
        System.out.println(customerFactoryBean);
    }
}
複製代碼
  • 控制檯打印的結果

測試結果

  • 在進行源碼分析以前,咱們能夠先看下這兩個問題:
    1. 在AppConfig類中咱們只掃描了com.tiantang.study.component這個包下的類,按照咱們的常規理解,這個時候應該只會有CustomerFactoryBean這個類被放進Spring容器中了,UserService並無被掃描。而咱們在測試時卻能夠經過applicationContext.getBean(UserService.class)從容器中獲取到Bean,爲何?
    1. 咱們知道默認狀況下,在咱們沒有自定義命名策略的狀況下,咱們自定義的類被Spring掃描進容器後,Bean在Spring容器中的beanName默認是類名的首字母小寫,因此這本次demo中,CustomerFactoryBean類的單例對象在容器中的beanName是customerFactoryBean。因此這個時候咱們調用方法getBean(beanName)經過beanName去獲取Bean,這個時候理論上應該返回的是CustomerFactoryBean類的單例對象。然而,咱們將結果打印出來,卻發現,這個對象的hashCode居然和userService對象的hashCode如出一轍,這說明這兩個對象是同一個對象,爲何會出現這種狀況呢?爲何不是CustomerFactoryBean類的實例對象呢?
    1. 既然經過customerFactoryBean這個beanName沒法獲取到CustomerFactoryBean的單例對象,那麼應該怎麼獲取呢?

以上3個問題的答案能夠用一個答案解決,那就是FactoryBean是一個特殊的Bean。咱們自定義的CustomerFactoryBean實現了FactoryBean接口,因此當CustomerFactoryBean被掃描進Spring容器時,實際上它向容器中註冊了兩個bean,一個是CustomerFactoryBean類的單例對象;另一個就是getObject()方法返回的對象,在demo中,咱們重寫的getObject()方法中,咱們經過new UserService()返回了一個UserService的實例對象,因此咱們從容器中能獲取到UserService的實例對象。若是咱們想經過beanName去獲取CustomerFactoryBean的單例對象,須要在beanName前面添加一個&符號,以下代碼,這樣就能根據beanName獲取到原生對象了。sql

public class MainApplication {

    public static void main(String[] args) {
        CustomerFactoryBean rawBean = (CustomerFactoryBean) applicationContext.getBean("&customerFactoryBean");
        System.out.println(rawBean);
    }
}
複製代碼

2. FactoryBean的源碼

經過上面的示例代碼,咱們知道了FactoryBean的做用,也知道該如何使用FactoryBean,那麼接下來咱們就經過源碼來看看FactoryBean的工做原理。緩存

  • 在Spring容器啓動階段,會調用到refresh()方法,在refresh()中有調用了finishBeanFactoryInitialization()方法,最終會調用到beanFactory.preInstantiateSingletons()方法。因此咱們先看下這個方法的源碼。(對refresh()方法不太熟悉的朋友,能夠去看下筆者的另外兩篇文章:Spring源碼系列之容器啓動流程經過源碼看Bean的建立過程)。
public void preInstantiateSingletons() throws BeansException {
	// 從容器中獲取到全部的beanName
	List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
	for (String beanName : beanNames) {
		RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
		if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
			// 在此處會根據beanName判斷bean是否是一個FactoryBean,實現了FactoryBean接口的bean,會返回true
			// 此時當beanName爲customerFactoryBean時,會返回true,會進入到if語句中
			if (isFactoryBean(beanName)) {
				// 而後經過getBean()方法去獲取或者建立單例對象
				// 注意:在此處爲beanName拼接了一個前綴:FACTORY_BEAN_PREFIX
				// FACTORY_BEAN_PREFIX是一個常量字符串,即:&
				// 因此在此時容器啓動階段,對於customerFactoryBean,應該是:getBean("&customerFactoryBean")
				Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
				// 下面這一段邏輯,是判斷是否須要在容器啓動階段,就去實例化getObject()返回的對象,便是否調用FactoryBean的getObject()方法
				if (bean instanceof FactoryBean) {
					final FactoryBean<?> factory = (FactoryBean<?>) bean;
					boolean isEagerInit;
					if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
						isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
										((SmartFactoryBean<?>) factory)::isEagerInit,
								getAccessControlContext());
					}
					else {
						isEagerInit = (factory instanceof SmartFactoryBean &&
								((SmartFactoryBean<?>) factory).isEagerInit());
					}
					if (isEagerInit) {
						getBean(beanName);
					}
				}
			}
		}
	}
}
複製代碼
  • 在容器啓動階段,會先經過getBean()方法來建立CustomerFactoryBean的實例對象。若是實現了SmartFactoryBean接口,且isEagerInit()方法返回的是true,那麼在容器啓動階段,就會調用getObject()方法,向容器中註冊getObject()方法返回值的對象。不然,只有當第一次獲取getObject()返回值的對象時,纔會去回調getObject()方法。
  • 在getBean()中會調用到doGetBean()方法,下面爲doGetBean()精簡後的源碼。從源碼中咱們發現,最終都會調用getObjectForBeanInstance()方法。
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
	final String beanName = transformedBeanName(name);
	Object bean;

	Object sharedInstance = getSingleton(beanName);
	if (sharedInstance != null && args == null) {
		bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
	}
	else {
		if (mbd.isSingleton()) {
			
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
		}
		else if (mbd.isPrototype()) {
			bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
		}
		else {
			bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
		}
		
	}
	return (T) bean;
}
複製代碼
  • 在getObjectForBeanInstance()方法中會先判斷bean是否是FactoryBean,若是不是,就直接返回Bean。若是是FactoryBean,且name是以&符號開頭,那麼表示的是獲取FactoryBean的原生對象,也會直接返回。若是name不是以&符號開頭,那麼表示要獲取FactoryBean中getObject()方法返回的對象。會先嚐試從FactoryBeanRegistrySupport類的factoryBeanObjectCache這個緩存map中獲取,若是緩存中存在,則返回,若是不存在,則去調用getObjectFromFactoryBean()方法。getObjectForBeanInstance()方法的部分源碼以下:
protected Object getObjectForBeanInstance( Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {

	if (BeanFactoryUtils.isFactoryDereference(name)) {
		if (beanInstance instanceof NullBean) {
			return beanInstance;
		}
		if (!(beanInstance instanceof FactoryBean)) {
			throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
		}
	}
	// 若是bean不是factoryBean,那麼會直接返回Bean
	// 或者bean是factoryBean但name是以&特殊符號開頭的,此時表示要獲取FactoryBean的原生對象。
	// 例如:若是name = &customerFactoryBean,那麼此時會返回CustomerFactoryBean類型的bean
	if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
		return beanInstance;
	}
	// 若是是FactoryBean,那麼先從cache中獲取,若是緩存不存在,則會去調用FactoryBean的getObject()方法。
	Object object = null;
	if (mbd == null) {
		// 從緩存中獲取。何時放入緩存的呢?在第一次調用getObject()方法時,會將返回值放入到緩存。
		object = getCachedObjectForFactoryBean(beanName);
	}
	if (object == null) {
		FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
		if (mbd == null && containsBeanDefinition(beanName)) {
			mbd = getMergedLocalBeanDefinition(beanName);
		}
		boolean synthetic = (mbd != null && mbd.isSynthetic());
		// 在getObjectFromFactoryBean()方法中最終會調用到getObject()方法
		object = getObjectFromFactoryBean(factory, beanName, !synthetic);
	}
	return object;
}
複製代碼
  • getObjectFromFactoryBean()方法中,主要是經過調用doGetObjectFromFactoryBean()方法獲得bean,而後對bean進行處理,最後放入緩存。並且還會針對單例bean和非單例bean作區分處理,對於單例bean,會在建立完後,將其放入到緩存中,非單例bean則不會放入緩存,而是每次都會從新建立。
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
	// 若是BeanFactory的isSingleton()方法返回值是true,表示getObject()返回值對象是單例的
	if (factory.isSingleton() && containsSingleton(beanName)) {
		synchronized (getSingletonMutex()) {
			// 再一次判斷緩存中是否存在。(雙重檢測機制,和平時寫線程安全的代碼相似)
			Object object = this.factoryBeanObjectCache.get(beanName);
			if (object == null) {
				// 在doGetObjectFromFactoryBean()中才是真正調用getObject()方法
				object = doGetObjectFromFactoryBean(factory, beanName);
				Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
				if (alreadyThere != null) {
					object = alreadyThere;
				}
				else {
					// 下面是進行後置處理,和普通的bean的後置處理沒有任何區別
					if (shouldPostProcess) {
						if (isSingletonCurrentlyInCreation(beanName)) {
							return object;
						}
						beforeSingletonCreation(beanName);
						try {
							object = postProcessObjectFromFactoryBean(object, beanName);
						}
						catch (Throwable ex) {
							throw new BeanCreationException(beanName,
									"Post-processing of FactoryBean's singleton object failed", ex);
						}
						finally {
							afterSingletonCreation(beanName);
						}
					}
					// 放入到緩存中
					if (containsSingleton(beanName)) {
						this.factoryBeanObjectCache.put(beanName, object);
					}
				}
			}
			return object;
		}
	}
	// 非單例
	else {
		Object object = doGetObjectFromFactoryBean(factory, beanName);
		if (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的getObject()方法。部分源碼以下
private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName) throws BeanCreationException {

	Object object;
	if (System.getSecurityManager() != null) {
		AccessControlContext acc = getAccessControlContext();
		try {
			object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc);
		}
		catch (PrivilegedActionException pae) {
			throw pae.getException();
		}
	}
	else {
		// 調用getObject()方法
		object = factory.getObject();
	}
	return object;
}
複製代碼
  • Spring的代碼實在是寫的太好了,每一個方法幾乎都複用性比較高,這就致使了老是方法中套方法,層級比較深,因此最後以一張流程圖總結下FactoryBean的建立流程。

FactoryBean的建立流程

  • 看完這一段的源碼分析,這個時候能理解demo中打印結果了吧。

3. FactoryBean的應用場景——Spring-Mybatis插件原理

如今知道了FactoryBean的原理,那麼在平時工做中,你見過哪些FactoryBean的使用場景。若是沒有留意過的話,筆者在這裏就拿Spring整合Mybatis的原理來舉例吧。安全

  • 咱們能夠先回憶一下,單獨使用Mybatis時,咱們須要作哪些工做。添加依賴,配置數據源,建立SqlSessionFactory,這樣環境就搭建完成了。(若是有朋友記憶比較模糊的話,能夠參考下官方文檔:mybatis.org/mybatis-3/g…)。
  • 當咱們在將Mybatis整合到Spring中時,也是添加mybatis的依賴,但還須要額外添加一個jar包:mybatis-spring,而後是配置數據源。最後還須要一個配置,若是你是經過XML配置的話,還須要以下配置:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>
複製代碼
  • 若是你不是經過JavaConfig配置,那麼須要進行以下配置:
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
    SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
    sqlSessionFactoryBean.setDataSource(dataSource);
    return sqlSessionFactoryBean;
}
複製代碼
  • 咱們發現,不管是XML仍是JavaConfig,都是向容器中註冊了一個SqlSessionFactoryBean。從類名咱們就能知道這是一個FactoryBean。當咱們單獨使用Mybatis時,須要建立一個SqlSessionFactory,然而當MyBatis和Spring整合時,卻須要一個SqlSessionFactoryBean,因此咱們能夠猜想,是否是SqlSessionFactoryBean經過FactoryBean的特殊性,向Spring容器中註冊了一個SqlSessionFactory。查看SqlSessionFactoryBean的源代碼發現,它果真實現了FactoryBean接口,而且重寫了getObejct方法,經過getObject()方法向容器中註冊了一個SqlSessionFactory。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
	// ...省略其餘代碼
	
	public SqlSessionFactory getObject() throws Exception {
	if (this.sqlSessionFactory == null) {
	  afterPropertiesSet();
	}

	return this.sqlSessionFactory;
	}
}
複製代碼
  • sqlSessionFactory是SqlSessionFactoryBean的一個屬性,它的賦值是在經過回調afterPropertiesSet()方法進行的。(由於SqlSessionFactoryBean實現了InitializingBean接口,因此在Spring初始化Bean的時候,能回調afterPropertiesSet()方法)
public void afterPropertiesSet() throws Exception {
    // buildSqlSessionFactory()方法會根據mybatis的配置進行初始化。
	this.sqlSessionFactory = buildSqlSessionFactory();
}
複製代碼
  • 在Spring和MyBatis整合時,還有另一個地方也利用到了FactoryBean。咱們在開發時,一般會經過MapperScan註解來掃描咱們Mapper文件。MapperScan註解中,添加了Import(MapperScannerRegistrar.class),(關於Import註解的做用,能夠看下筆者的另外一篇文章:mp.weixin.qq.com/s/y_2Z9m0ge…)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
}
複製代碼
  • 在MapperScannerRegistrar的registerBeanDefinitions()方法中,會將咱們定義的Mapper掃描出來,解析成BeanDefinition,注意,解析成BeanDefinition後,beanClass屬性再也不是咱們定義的Mapper類的class了,而是被設置成了MapperFactoryBean.class。這說明了咱們定義的每個Mapper接口,被加載進Spring後,最後都會對應一個MapperFactoryBean。
  • 咱們再看看MapperFactoryBean這個類幹了哪些事。下面是MapperFactoryBean類的部分源碼。從源碼中,咱們發現,它實現了FactoryBean接口,重寫了接口中的三個方法。在getObject()方法中,經過調用getSqlSession().getMapper(this.mapperInterface)返回了一個對象。這一行代碼最終會調用到MapperProxyFactory的newInstance()方法,爲每個Mapper建立一個代理對象。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
	@Override
	public T getObject() throws Exception {
		return getSqlSession().getMapper(this.mapperInterface);
	}

	@Override
	public Class<T> getObjectType() {
		return this.mapperInterface;
	}

	@Override
	public boolean isSingleton() {
        // 返回true是爲了讓Mapper接口是一個單例的
		return true;
	}
}
複製代碼
  • MapperProxyFactory類的源碼。最終是調用JDK的動態代理來爲咱們定義的Mapper建立動態代理。(MyBatis框架就是經過動態代理實現的dao層)
public class MapperProxyFactory<T> {

  protected T newInstance(MapperProxy<T> mapperProxy) {
  	// JDK動態代理
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}
複製代碼
  • 這樣咱們寫的每個Mapper接口都會對應一個MapperFactoryBean,每個MapperFactoryBean的getObject()方法最終會採用JDK動態代理建立一個對象,因此每個Mapper接口最後都對應一個代理對象,這樣就實現了Spring和MyBatis的整合。

4. mybatis-spring-boot-starter原理

在SpringBoot中整合MyBatis的原理是同樣的,雖然使用的是mybatis-spring-boot-starter這個依賴,但最終的整合原理和Spring是如出一轍的。SpringBoot的最主要的功能是自動配置,和其餘框架的整合原理與Spring相比幾乎沒變。微信

  • mybatis-spring-boot-starter中,會引入mybatis-spring-boot-autoconfigure這個jar包,MyBatis的自動配置就是經過這個jar包中的MybatisAutoConfiguration類實現的。從MybatisAutoConfiguration的源碼中咱們能夠看到一樣是經過SqlSessionFactoryBean的getObject()方法向容器中註冊了一個SqlSessionBean。
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnBean({DataSource.class})
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MybatisAutoConfiguration {
	@Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        // 省略部分代碼
        return factory.getObject();
    }

}
複製代碼

5. 總結

  • 本文主要介紹了FactoryBean的做用以及使用場景。先經過demo演示了FactoryBean的用法,而後結合Spring源碼分析了FactoryBean的原理。
  • 接着經過源碼分析了FactoryBean在Spring和MyBatis整合過程當中扮演的重要角色。一是提供一個SqlSessionFactory,二是爲每個Mapper建立JDK動態代理對象。
  • 最後經過一小段源碼分析了SpringBoot中整合Mybatis的原理。其實和Spring整合MyBatis的原理如出一轍,因此重點是仍是Spring的源碼理解。

6. 猜你喜歡

掃描下方二維碼便可關注微信公衆號菜鳥飛呀飛,一塊兒閱讀更多Spring源碼。mybatis

微信公衆號
相關文章
相關標籤/搜索