溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Spring的FactoryBean有什么作用

發(fā)布時間:2021-12-07 13:42:05 來源:億速云 閱讀:136 作者:iii 欄目:大數(shù)據(jù)

這篇文章主要講解了“Spring的FactoryBean有什么作用”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Spring的FactoryBean有什么作用”吧!

1.一個 Demo

我們先從一個簡單的 Demo 開始。

新建一個 Maven 項目,引入 Spring 依賴,然后創(chuàng)建一個 HelloService,如下:

public class HelloService {
    public String hello() {
        return "hello javaboy";
    }
}
 

然后再創(chuàng)建一個 HelloService 的工廠類:

public class HelloServiceFactoryBean implements FactoryBean<HelloService> {
    @Override
    public HelloService getObject() throws Exception {
        return new HelloService();
    }

    @Override
    public Class<?> getObjectType() {
        return HelloService.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}
 

我們新建的 HelloServiceFactoryBean 類實現(xiàn)了 FactoryBean 接口,并指定了 HelloService 泛型。

接下來我們在 beans.xml 文件中配置 HelloServiceFactoryBean 實例:

<?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.xsd">

    <bean class="org.javaboy.springdemo03.HelloServiceFactoryBean" id="helloService"/>
</beans>
 

加載該配置文件:

@Test
void contextLoads() {
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
    Object helloService = ctx.getBean("helloService");
    System.out.println(helloService.getClass().toString());
}
 

加載 XML 配置文件,并獲取 Bean 實例,然后將 Bean 實例的類型打印出來。

按照我們之前的理解,這里獲取到的 Bean 應(yīng)該是 HelloServiceFactoryBean 的實例,但是實際打印結(jié)果如下:

class org.javaboy.springdemo03.HelloService
 

竟然是 HelloService!

到底怎么回事?我們得從 FactoryBean 接口開始講起。

 

2.FactoryBean 接口

FactoryBean 在 Spring 框架中具有重要地位,Spring 框架本身也為此提供了多種不同的實現(xiàn)類:

Spring的FactoryBean有什么作用  

按理說我們配置一個 Bean,直接在 XML 文件中進(jìn)行配置即可。但是有的時候一個類的屬性非常多,在 XML 中配置起來反而非常不方便,這個時候 Java 代碼配置的優(yōu)勢就體現(xiàn)出來了,使用 FactoryBean 就可以通過 Java 代碼配置 Bean 的相關(guān)屬性。

從 Spring3.0 開始,F(xiàn)actoryBean 開始支持泛型,就是大家所看到的 FactoryBean形式,F(xiàn)actoryBean接口中一共有三個方法:

public interface FactoryBean<T> {
 @Nullable
 T getObject() throws Exception;
 @Nullable
 Class<?> getObjectType();
 default boolean isSingleton() {
  return true;
 }

}
 
  • getObject:該方法返回 FactoryBean 所創(chuàng)建的實例,如果在 XML 配置文件中,我們提供的 class 是一個 FactoryBean 的話,那么當(dāng)我們調(diào)用 getBean 方法去獲取實例時,最終獲取到的是 getObject 方法的返回值。
  • getObjectType:返回對象的類型。
  • isSingleton:getObject 方法所返回的對象是否單例。

所以當(dāng)我們調(diào)用 getBean 方法去獲取 Bean 實例的時候,實際上獲取到的是 getObject 方法的返回值,那么是不是就沒有辦法獲取 HelloServiceFactoryBean 呢?當(dāng)然是有的!

@Test
void contextLoads() {
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
    Object helloService = ctx.getBean("&helloService");
    System.out.println(helloService.getClass().toString());
}
 

打印結(jié)果如下:

class org.javaboy.springdemo03.HelloServiceFactoryBean
 

小伙伴們可以看到,只需要在 Bean 的名字前面加上一個 & 符號,獲取到的就是 HelloServiceFactoryBean 實例了。

 

3.源碼分析

根據(jù) Spring 源碼第六彈!松哥和大家聊聊容器的始祖 DefaultListableBeanFactory 一文中的介紹,在 DefaultListableBeanFactory 中還有一個 preInstantiateSingletons 方法可以提前注冊 Bean,該方法是在 ConfigurableListableBeanFactory 接口中聲明的,DefaultListableBeanFactory 類實現(xiàn)了 ConfigurableListableBeanFactory 接口并實現(xiàn)了接口中的方法:

@Override
public void preInstantiateSingletons() throws BeansException {
 if (logger.isTraceEnabled()) {
  logger.trace("Pre-instantiating singletons in " + this);
 }
 // Iterate over a copy to allow for init methods which in turn register new bean definitions.
 // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
 List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
 // Trigger initialization of all non-lazy singleton beans...
 for (String beanName : beanNames) {
  RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
  if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
   if (isFactoryBean(beanName)) {
    Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
    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);
     }
    }
   }
   else {
    getBean(beanName);
   }
  }
 }
 // Trigger post-initialization callback for all applicable beans...
 for (String beanName : beanNames) {
  Object singletonInstance = getSingleton(beanName);
  if (singletonInstance instanceof SmartInitializingSingleton) {
   final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
   if (System.getSecurityManager() != null) {
    AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
     smartSingleton.afterSingletonsInstantiated();
     return null;
    }, getAccessControlContext());
   }
   else {
    smartSingleton.afterSingletonsInstantiated();
   }
  }
 }
}
 

preInstantiateSingletons 方法的整體邏輯比較簡單,就是遍歷 beanNames,對符合條件的 Bean 進(jìn)行實例化,而且大家注意,這里所謂的提前初始化其實就是在我們調(diào)用 getBean 方法之前,它自己先調(diào)用了一下 getBean。

這里有幾個比較關(guān)鍵的點(diǎn)。

第一個就是 isFactoryBean 方法的調(diào)用,該方法就是根據(jù) beanName 去獲取 Bean 實例,進(jìn)而判斷是不是一個 FactoryBean,如果沒有 Bean 實例(還沒創(chuàng)建出來),則根據(jù) BeanDefinition 去判斷是不是一個 FactoryBean。

如果是 FactoryBean,則在 getBean 時自動加上了 FACTORY_BEAN_PREFIX 前綴,這個常量其實就是 &,這樣獲取到的實例實際上就是 FactoryBean 的實例。獲取到 FactoryBean 實例之后,接下來判斷是否需要在容器啟動階段,調(diào)用 getObject 方法初始化 Bean,如果 isEagerInit 為 true,則去初始化。

按照我們前面的定義,這里獲取到的 isEagerInit 屬性為 false,即不提前加載 Bean,而是在開發(fā)者手動調(diào)用 getBean 的方法的時候才去加載。如果希望這里能夠提前加載,需要重新定義 HelloServiceFactoryBean,并使之實現(xiàn) SmartFactoryBean 接口,如下:

public class HelloServiceFactoryBean2 implements SmartFactoryBean<HelloService> {
    @Override
    public HelloService getObject() throws Exception {
        return new HelloService();
    }

    @Override
    public Class<?> getObjectType() {
        return HelloService.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public boolean isEagerInit() {
        return true;
    }
}
 

實現(xiàn)了 SmartFactoryBean 接口之后,重寫 isEagerInit 方法并返回 true,其他方法不變,重新配置 beans.xml 文件,然后啟動容器。此時在容器啟動時,就會提前調(diào)用 getBean 方法完成 Bean 的加載。

接下來我們來看 getBean 方法。

getBean 方法首先調(diào)用 AbstractBeanFactory#doGetBean,在該方法中,又會調(diào)用到 AbstractBeanFactory#getObjectForBeanInstance 方法:

protected Object getObjectForBeanInstance(
  Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
 // Don't let calling code try to dereference the factory if the bean isn't a factory.
 if (BeanFactoryUtils.isFactoryDereference(name)) {
  if (beanInstance instanceof NullBean) {
   return beanInstance;
  }
  if (!(beanInstance instanceof FactoryBean)) {
   throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
  }
  if (mbd != null) {
   mbd.isFactoryBean = true;
  }
  return beanInstance;
 }
 // Now we have the bean instance, which may be a normal bean or a FactoryBean.
 // If it's a FactoryBean, we use it to create a bean instance, unless the
 // caller actually wants a reference to the factory.
 if (!(beanInstance instanceof FactoryBean)) {
  return beanInstance;
 }
 Object object = null;
 if (mbd != null) {
  mbd.isFactoryBean = true;
 }
 else {
  object = getCachedObjectForFactoryBean(beanName);
 }
 if (object == null) {
  // Return bean instance from factory.
  FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
  // Caches object obtained from FactoryBean if it is a singleton.
  if (mbd == null && containsBeanDefinition(beanName)) {
   mbd = getMergedLocalBeanDefinition(beanName);
  }
  boolean synthetic = (mbd != null && mbd.isSynthetic());
  object = getObjectFromFactoryBean(factory, beanName, !synthetic);
 }
 return object;
}
 

這段源碼很有意思:

BeanFactoryUtils.isFactoryDereference 方法用來判斷 name 是不是以 & 開頭的,如果是以 & 開頭的,表示想要獲取的是一個 FactoryBean,那么此時如果 beanInstance 剛好就是一個 FactoryBean,則直接返回。并將 mbd 中的 isFactoryBean 屬性設(shè)置為 true。

如果 name 不是以 & 開頭的,說明用戶就是想獲取 FactoryBean 所構(gòu)造的 Bean,那么此時如果 beanInstance 不是 FactoryBean 實例,則直接返回。

如果當(dāng)前的 beanInstance 是一個 FactoryBean,而用戶想獲取的只是一個普通 Bean,那么就會進(jìn)入到接下來的代碼中。

首先調(diào)用 getCachedObjectForFactoryBean 方法去從緩存中獲取 Bean。如果是第一次獲取 Bean,這個緩存中是沒有的數(shù)據(jù)的,getObject 方法調(diào)用過一次之后,Bean 才有可能被保存到緩存中了。

為什么說有可能呢?Bean 如果是單例的,則會被保存在緩存中,Bean 如果不是單例的,則不會被保存在緩存中,而是每次加載都去創(chuàng)建新的。

如果沒能從緩存中加載到 Bean,則最終會調(diào)用 getObjectFromFactoryBean 方法去加載 Bean。

protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
 if (factory.isSingleton() && containsSingleton(beanName)) {
  synchronized (getSingletonMutex()) {
   Object object = this.factoryBeanObjectCache.get(beanName);
   if (object == null) {
    object = doGetObjectFromFactoryBean(factory, beanName);
    // Only post-process and store if not put there already during getObject() call above
    // (e.g. because of circular reference processing triggered by custom getBean calls)
    Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
    if (alreadyThere != null) {
     object = alreadyThere;
    }
    else {
     if (shouldPostProcess) {
      if (isSingletonCurrentlyInCreation(beanName)) {
       // Temporarily return non-post-processed object, not storing it yet..
       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;
 }
}
 

在 getObjectFromFactoryBean 方法中,首先通過 isSingleton 和 containsSingleton 兩個方法判斷 getObject 方法返回值是否是單例的,單例的走一條路,非單例的走另外一條路。

如果是單例的:

先去緩存中再拿一次,看能不能拿到。如果緩存中沒有,調(diào)用 doGetObjectFromFactoryBean 方法去獲取,這是真正的獲取方法。獲取到之后,進(jìn)行 Bean 的后置處理,處理完成后,如果 Bean 是單例的,就緩存起來。

如果不是單例的:

不是單例就簡單,直接調(diào)用 doGetObjectFromFactoryBean 方法獲取 Bean 實例,然后進(jìn)行后置處理就完事,也不用緩存。

接下來我們就來看看 doGetObjectFromFactoryBean 方法:

private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException {
 Object object;
 try {
  if (System.getSecurityManager() != null) {
   AccessControlContext acc = getAccessControlContext();
   try {
    object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) 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) {
  if (isSingletonCurrentlyInCreation(beanName)) {
   throw new BeanCurrentlyInCreationException(
     beanName, "FactoryBean which is currently in creation returned null from getObject");
  }
  object = new NullBean();
 }
 return object;
}
 

做了一些判斷之后,最終通過 factory.getObject(); 方法獲取我們想要的實例。

這就是整個 FactoryBean 的加載流程。

 

4.應(yīng)用

FactoryBean 在 Spring 框架中有非常廣泛的應(yīng)用,即使我們不寫上面那段代碼,也還是使用了不少的 FactoryBean。

接下來松哥隨便舉兩個例子。

 

4.1 SqlSessionFactoryBean

這可能是大家接觸最多的 FactoryBean,當(dāng) MyBatis 整合 Spring 時,我們少不了如下一行配置:

<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="typeAliasesPackage" value="org.javaboy.meeting.model"/>
    <property name="mapperLocations">
        <value>
            classpath*:org/javaboy/meeting/mapper/*.xml
        </value>
    </property>
</bean>
 

這就是在配置 FactoryBean。當(dāng)我們單獨(dú)使用 MyBatis 時候,需要有一個 SqlSessionFactory,現(xiàn)在整合之后,沒有 SqlSessionFactory 了,我們有理由相信是 SqlSessionFactoryBean 的 getObject 方法提供了 SqlSessionFactory,我們來看下其源碼:

@Override
public SqlSessionFactory getObject() throws Exception {
  if (this.sqlSessionFactory == null) {
    afterPropertiesSet();
  }
  return this.sqlSessionFactory;
}
 

確實如此!

 

4.2 Jackson2ObjectMapperFactoryBean

這也是一個大家使用相對較多的 FactoryBean。

如果項目中使用了 Jackson,同時希望在全局層面做一些配置,一般來說我們可能會用到這個類:

<mvc:annotation-driven conversion-service="conversionService">
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper">
                <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                    <property name="simpleDateFormat" value="yyyy-MM-dd"/>
                </bean>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>
 

MappingJackson2HttpMessageConverter 類的 objectMapper 屬性實際上是需要一個 ObjectMapper 對象,但是我們這里卻提供了一個 Jackson2ObjectMapperFactoryBean,這是因為 Jackson2ObjectMapperFactoryBean 的 getObject 方法就是我們需要的 ObjectMapper:

@Override
@Nullable
public ObjectMapper getObject() {
 return this.objectMapper;
}
   

4.3 FormattingConversionServiceFactoryBean

FormattingConversionServiceFactoryBean 也曾經(jīng)出現(xiàn)在松哥的 SpringMVC 教程中(公眾號江南一點(diǎn)雨后臺回復(fù) springmvc 獲取教程)。

如果前端傳遞的參數(shù)格式是 key-value 形式,那么日期類型的參數(shù)需要服務(wù)端提供一個日期類型轉(zhuǎn)換器,像下面這樣:

@Component
public class DateConverter implements Converter<String, Date> {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    public Date convert(String source) {
        try {
            return sdf.parse(source);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }
}
 

然后在 XML 文件中對此進(jìn)行配置:

<mvc:annotation-driven conversion-service="conversionService"/>
<bean class="org.springframework.format.support.FormattingConversionServiceFactoryBean" id="conversionService">
    <property name="converters">
        <set>
            <ref bean="dateConverter"/>
        </set>
    </property>
</bean>
 

FormattingConversionServiceFactoryBean 中的 getObject 方法最終返回的是 FormattingConversionService。

類似的例子還有很多,例如 EhCacheManagerFactoryBean、YamlPropertiesFactoryBean 等。

感謝各位的閱讀,以上就是“Spring的FactoryBean有什么作用”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Spring的FactoryBean有什么作用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI