說明:
??1. 本文基于Spring-Framework 5.1.x版本講解
??2. 建議讀者對Mybatis有基本的使用經驗
概述
這一篇我們講講org.springframework.beans.factory.FactoryBean介面,這個介面功能非常強大,可以集成不同的中間件或組件到Spring容器中來,可以說該介面是打通Spring與外界溝通的重要橋梁,是Spring非常重要的一個拓展點, 不少人會拿BeanFactory與FactoryBean做比較,其實這兩個介面根本就沒有可比性,完全不是一個’層次‘的產物,廢話不多說,讓我們開始吧,
FactoryBean
先看下FatoryBean介面在原始碼中的定義
/**
* Interface to be implemented by objects used within a {@link BeanFactory} which
* are themselves factories for individual objects. If a bean implements this
* interface, it is used as a factory for an object to expose, not directly as a
* bean instance that will be exposed itself.
*
* <p><b>NB: A bean that implements this interface cannot be used as a normal bean.</b>
* A FactoryBean is defined in a bean style, but the object exposed for bean
* references ({@link #getObject()}) is always the object that it creates.
*
* <p>FactoryBeans can support singletons and prototypes, and can either create
* objects lazily on demand or eagerly on startup. The {@link SmartFactoryBean}
* interface allows for exposing more fine-grained behavioral metadata.
*
* <p>This interface is heavily used within the framework itself, for example for
* the AOP {@link org.springframework.aop.framework.ProxyFactoryBean} or the
* {@link org.springframework.jndi.JndiObjectFactoryBean}. It can be used for
* custom components as well; however, this is only common for infrastructure code.
*
* <p><b>{@code FactoryBean} is a programmatic contract. Implementations are not
* supposed to rely on annotation-driven injection or other reflective facilities.</b>
* {@link #getObjectType()} {@link #getObject()} invocations may arrive early in the
* bootstrap process, even ahead of any post-processor setup. If you need access to
* other beans, implement {@link BeanFactoryAware} and obtain them programmatically.
*
* <p><b>The container is only responsible for managing the lifecycle of the FactoryBean
* instance, not the lifecycle of the objects created by the FactoryBean.</b> Therefore,
* a destroy method on an exposed bean object (such as {@link java.io.Closeable#close()}
* will <i>not</i> be called automatically. Instead, a FactoryBean should implement
* {@link DisposableBean} and delegate any such close call to the underlying object.
*
* <p>Finally, FactoryBean objects participate in the containing BeanFactory's
* synchronization of bean creation. There is usually no need for internal
* synchronization other than for purposes of lazy initialization within the
* FactoryBean itself (or the like).
*
* @author Rod Johnson
* @author Juergen Hoeller
* @since 08.03.2003
* @param <T> the bean type
* @see org.springframework.beans.factory.BeanFactory
* @see org.springframework.aop.framework.ProxyFactoryBean
* @see org.springframework.jndi.JndiObjectFactoryBean
*/
public interface FactoryBean<T> {
T getObject() throws Exception;
Class<?> getObjectType();
default boolean isSingleton() { return true; }
}
從介面描述資訊中我們可以得到以下幾點關鍵資訊:
1. 實作了BeanFactory介面的Bean,實際上對外暴露的是getObject方法回傳的物件;
2. FactoryBean支持創建單例和原型Bean,可以通過懶加載或容器啟動時加載的方式創建Bean,可以實作SmartFactoryBean介面來控制更多的Bean創建方式;
3. Spring只會管理FactoryBean物件本身,通過FactoryBean#getObject創建出來的物件的生命周期則不會由Spring管理, 也就是說通過getObject回傳的物件本身即使實作了生命周期介面,也不會被呼叫;
第2、3點不在我們的討論范圍之內,也比較好理解,我們把關注點放在第1點這上, 通過getObject方法來創建物件? 我直接用@Bean的方式不行嗎? 其實是可以的,在創建簡單物件的場景中,直接使用@Bean甚至比使用BeanFactory的方式更合理、更簡單, 那BeanFactory的使用場景到底是什么? 如果實作一個需求,@Bean與BeanFactory兩種創建Bean的方式如何選擇?
提供下我的思路供參考:
1. 如果你已經明確了要創建的Bean物件的Class的時候,這時候直接使用@Bean即可,如果無法拿到Class,則使用BeanFactory的方式,例如,你作為一個框架撰寫者,創建哪些Bean往往是由上層的框架使用者來決定的,這種情況下使用@Bean肯定是不行的,所以一定要用BeanFactory
2. 假設你可以拿到要創建的Bean物件的Class,但是不想暴露太多的Bean物件創建細節,如復雜的Bean創建程序,這種情況也可以使用BeanFactory ,但是什么才是復雜的Bean?如何不暴露創建細節?這個就需要你自己有非常足夠多的說服力了,
所以個人認為,第一點才是決策的關鍵
我們在來簡單解釋下為什么說文中開始部分提到的BeanFactory與FactoryBean不是同一個層次的產物,相信你大概有所體會: BeanFactory 是Bean工廠、是容器,我們創建的Bean都在BeanFactory 中;而FactoryBean 只是容器創建Bean提供的一種方式而已, 可以說FactoryBean 就是為BeanFactory 服務的,
好,我們下面再來舉個Spring集成Mybatis的例子加深下對FactoryBean 的理解
Spring集成Mybatis
我們的業務代碼中操作資料庫,少不了使用@Resource、@Autowired等注解注入Mybatis的Mapper物件的場景, 這就說明這些Mapper物件的實體一定在Spring容器的管理之中,再往下想想,這些Mapper物件是如何被Spring納入其中的? 其實就少不了 FactoryBean 介面的功勞, 想象一下你站在Mybatis框架實作者的角度,使用@Bean注解知否能實作?
我們看下框架實際是如何集成的,這里免不了貼一下原始碼,大家耐心看,
一、我們要知道Mapper檔案的位置, Spring才能綁我們掃描, 但是光掃描還不行,因為Spring只會把掃描出來的Class轉換為beanDefinition并放在DefaultListableBeanFactory#beanDefinitionMap容器中,Spring就是根據beanDefinitionMap的內容來幫我們創建Bean的, 可是現階段我們只有Mapper的Class,直接把Mapper的Class放到beanDefinitionMap中,Spring只會創建該Class的實體,那結果肯定是無法實體化的,我們需要找到一種類似于’占位符‘的機制,先把這個’占位符‘放入beanDefinitionMap 中,在實際使用時,在通過’占位符‘把實際的Bean物件構建出來
這時候FactoryBean 就登場了,FactoryBean類似于上述中的’占位符‘, 由于FactoryBean 的特性,我們完全可以把FactoryBean 先注冊到beanDefinitionMap中,在實際使用階段,我們可以通過getObject方法從SqlSessionFactory中拿到Mybatis為我們創建好的的Mapper代理物件了
package org.mybatis.spring.mapper;
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
private String sqlSessionFactoryBeanName;
// MapperFactoryBean 實作了FactoryBean介面
private MapperFactoryBean<?> mapperFactoryBean = new MapperFactoryBean<Object>();
/**
* 實際掃描Mapper的的方法
*/
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 掃描出來的所有Mapper的BeanDefinition集合
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
// 呼叫下面的方法對Mapper對應的BeanDefinition做處理
processBeanDefinitions(beanDefinitions);
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
// 把Mapper的Class以構造方法的形式傳進去
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
//注意這里,是設定的MapperFactoryBean的Class,意味著Spring要幫我們創建MapperFactoryBean物件
definition.setBeanClass(this.mapperFactoryBean.getClass());
//給MapperFactoryBean物件種注入sqlSessionFactory物件,在getObject()方法會用
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
}
}
}
二、 到這里我們已經把MapperFactoryBean注冊到beanDefinitionMap中了,后續我們在業務代碼中注入Mapper的時候,實際注入的是MapperFactoryBean#getObject方法回傳的代理物件,我們在來看下getObject的實作
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
// 有參構造方法的mapperInterface介面在上一步傳入
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public T getObject() throws Exception {
// getSqlSession()實際就是通過我們的SqlSessionFactory物件呼叫openSession獲取出來的
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
@Override
public boolean isSingleton() {
return true;
}
}
好,到了這里相信你已經明白了Spring是如何集成Mybatis的了
小結
這一篇主要講了FactoryBean 的用法以及與BeanFactory的簡單比較,說明了他們的區別,并且借助于Spring集成Mybatis的實際案例了解了FactoryBean 在實際中的用法,其實FactoryBean的身影無處不在,在AOP以及Spring與其他框架的集成中會經常用到,
在最后簡單說明一下:在實際作業中FactoryBean 介面用的并不是很多,因為大部分我們都在實作業務需求,直接使用@Bean方式創建Bean即可; 如果你是一位中間件、框架開發者,亦或是為了代碼的可拓展性實作一個功能,那FactoryBean 將是你的不二之選,
本文來自博客園,作者:林一gg,轉載請注明原文鏈接:https://www.cnblogs.com/linyigg/p/16949933.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/539151.html
標籤:其他
