掃描下方二維碼或者微信搜尋公衆号 菜鳥飛呀飛
,即可關注微信公衆号,閱讀更多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);
}
}
- 控制台列印的結果
- 在進行源碼分析之前,我們可以先看下這兩個問題:
-
- 在AppConfig類中我們隻掃描了
這個包下的類,按照我們的正常了解,這個時候應該隻會有CustomerFactoryBean這個類被放進Spring容器中了,UserService并沒有被掃描。而我們在測試時卻可以通過com.tiantang.study.component
從容器中擷取到Bean,為什麼?applicationContext.getBean(UserService.class)
- 在AppConfig類中我們隻掃描了
-
- 我們知道預設情況下,在我們沒有自定義命名政策的情況下,我們自定義的類被Spring掃描進容器後,Bean在Spring容器中的beanName預設是類名的首字母小寫,是以這本次demo中,
類的單例對象在容器中的beanName是customerFactoryBean。是以這個時候我們調用方法getBean(beanName)通過beanName去擷取Bean,這個時候理論上應該傳回的是CustomerFactoryBean類的單例對象。然而,我們将結果列印出來,卻發現,這個對象的hashCode竟然和userService對象的hashCode一模一樣,這說明這兩個對象是同一個對象,為什麼會出現這種情況呢?為什麼不是CustomerFactoryBean類的執行個體對象呢?CustomerFactoryBean
- 我們知道預設情況下,在我們沒有自定義命名政策的情況下,我們自定義的類被Spring掃描進容器後,Bean在Spring容器中的beanName預設是類名的首字母小寫,是以這本次demo中,
-
- 既然通過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的建立流程。
- 看完這一段的源碼分析,這個時候能了解demo中列印結果了吧。
3. FactoryBean的應用場景——Spring-Mybatis插件原理
現在知道了FactoryBean的原理,那麼在平時工作中,你見過哪些FactoryBean的使用場景。如果沒有留意過的話,筆者在這裡就拿Spring整合Mybatis的原理來舉例吧。
- 我們可以先回憶一下,單獨使用Mybatis時,我們需要做哪些工作。添加依賴,配置資料源,建立SqlSessionFactory,這樣環境就搭建完成了。(如果有朋友記憶比較模糊的話,可以參考下官方文檔:https://mybatis.org/mybatis-3/getting-started.html)。
- 當我們在将Mybatis整合到Spring中時,也是添加mybatis的依賴,但還需要額外添加一個jar包:
,然後是配置資料源。最後還需要一個配置,如果你是通過XML配置的話,還需要如下配置:mybatis-spring
<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()方法中,通過調用
傳回了一個對象。這一行代碼最終會調用到MapperProxyFactory的newInstance()方法,為每一個Mapper建立一個代理對象。getSqlSession().getMapper(this.mapperInterface)
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源碼。