天天看點

FactoryBean——Spring的擴充點之一

掃描下方二維碼或者微信搜尋公衆号

菜鳥飛呀飛

,即可關注微信公衆号,閱讀更多Spring源碼分析文章
FactoryBean——Spring的擴充點之一

文章目錄

    • 1. FactoryBean的用法
    • 2. FactoryBean的源碼
    • 3. FactoryBean的應用場景——Spring-Mybatis插件原理
    • 4. mybatis-spring-boot-starter原理
    • 5. 總結
    • 6. 猜你喜歡
首先需要說明的是,FactoryBean和BeanFactory雖然名字很像,但是這兩者是完全不同的兩個概念,用途上也是天差地别。BeanFactory是一個Bean工廠,在一定程度上我們可以簡單了解為它就是我們平常所說的Spring容器(注意這裡說的是簡單了解為容器),它完成了Bean的建立、自動裝配等過程,存儲了建立完成的單例Bean。而FactoryBean通過名字看,我們可以猜出它是Bean,但它是一個特殊的Bean,究竟有什麼特殊之處呢?它的特殊之處在我們平時開發過程中又有什麼用處呢?

1. FactoryBean的用法

FactoryBean的特殊之處在于它可以向容器中注冊兩個Bean,一個是它本身,一個是FactoryBean.getObject()方法傳回值所代表的Bean。先通過如下示例代碼來感受下FactoryBean的用處吧。
  • 自定義一個類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);
    }
}
           
  • 控制台列印的結果
    FactoryBean——Spring的擴充點之一
  • 在進行源碼分析之前,我們可以先看下這兩個問題:
    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擷取到原生對象了。
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——Spring的擴充點之一
  • 看完這一段的源碼分析,這個時候能了解demo中列印結果了吧。

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

現在知道了FactoryBean的原理,那麼在平時工作中,你見過哪些FactoryBean的使用場景。如果沒有留意過的話,筆者在這裡就拿Spring整合Mybatis的原理來舉例吧。
  • 我們可以先回憶一下,單獨使用Mybatis時,我們需要做哪些工作。添加依賴,配置資料源,建立SqlSessionFactory,這樣環境就搭建完成了。(如果有朋友記憶比較模糊的話,可以參考下官方文檔:https://mybatis.org/mybatis-3/getting-started.html)。
  • 當我們在将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注解的作用,可以看下筆者的另一篇文章:https://mp.weixin.qq.com/s/y_2Z9m0gevp-cMkEIflrwA)
@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. 猜你喜歡

  • 通過源碼看Bean的建立過程
  • Spring源碼系列之容器啟動流程
  • Spring中最!最!最!重要的後置處理器!沒有之一!!!
  • @Import和@EnableXXX
  • 手寫一個Redis和Spring整合的插件
  • 為什麼JDK的動态代理要基于接口實作而不能基于繼承實作?
掃描下方二維碼即可關注微信公衆号菜鳥飛呀飛,一起閱讀更多Spring源碼。
FactoryBean——Spring的擴充點之一

繼續閱讀