1.概述
目前我們知道,spring創建bean有多種方式,比如xml方式創建,比如@Component,@Service,@Controler,@Repository註解創建,比如@Autowired依賴注入創建,後續還有通過springboot方式的配置註解@Configuration與@Bean方式結合創建,這裏不一一介紹,等分析spring boot源碼的時候再做總結。
就spring本身,提供了一種接口方式創建bean,就是本節要討論的通過FactoryBean
接口方式創建。
2.實例
FactoryBean接口的實現類FactoryBeanDemo:
package com.wzj.FactoryBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Service;
@Service
public class FactoryBeanDemo implements FactoryBean {
@Override
public Object getObject() throws Exception {
return new FactoryB();
}
@Override
public Class<?> getObjectType() {
return FactoryB.class;
}
}
通過FactoryBean實現類,完成自定義類FactoryB的實例化,FactoryB:
package com.wzj.FactoryBean;
import lombok.Data;
@Data
public class FactoryB {
private String name = "wzj";
}
測試類:
public class TestSpring {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testFactoryBean() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
FactoryB factoryB = (FactoryB)applicationContext.getBean("factoryBeanDemo");
System.out.println(factoryB);
FactoryBeanDemo factoryBeanDemo = (FactoryBeanDemo)applicationContext.getBean("&factoryBeanDemo");
System.out.println(factoryBeanDemo);
}
測試結果:
可以看出,當獲取名稱爲factoryBeanDemo
的實例時,得到的是getObject()方法裏創建的FactoryB類型的對象,而獲取加前綴&
的factoryBeanDemo
的實例時,得到的是FactoryBeanDemo本身的實例。
3.源碼
step1
: FactoryBean 接口的調用入口在實例化和 IOC/DI 做完後,就會調用 FactoryBean 類型的接口如下圖所示
// Create bean instance.
// 創建bean實例
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
// FactoryBean的調用入口
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
step2
: 如果要獲取到 FactoryBean 類本身,就必須加上&
符號,比如 beanFactory.getBean("&beanName") ,如下:
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.
// 如果爲name不爲空,且以前綴&打頭,直接返回bean本身
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;
}
public static boolean isFactoryDereference(@Nullable String name) {
return (name != null && name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
}
String FACTORY_BEAN_PREFIX = "&";
stet3
: BeanFactory.getBean("beanName")只能獲取到 getObject()方法返回的實例。getObject 方法返回的實例會有單獨的緩存存儲,跟其他實例不是同一個緩存,對應的緩存是:factoryBeanObjectCache
// 如果是不是以前綴&打頭,並且是FactoryBean類型的
Object object = null;
if (mbd != null) {
mbd.isFactoryBean = true;
}
else {
// 從緩存裏拿FactoryBean實例
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;
}
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
if (factory.isSingleton() && containsSingleton(beanName)) {
synchronized (getSingletonMutex()) {
// 先從緩存factoryBeanObjectCache取
Object object = this.factoryBeanObjectCache.get(beanName);
// 如果緩存爲空,
if (object == null) {
// 調用getObject方法
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;
}
}
......
小結:具體代碼參考 getSingleton 方法之後 getObjectForBeanInstance
- 如果bean實例不是 FactoryBean 類型的或者 name 以&開始的則直接返回實例。
- 如果bean是 FacotyBean 並且不是以&開頭, 會通過方法doGetObjectFromFactoryBean 調用FactoryBean 內部繼承實現的 getObject 方法,並且判斷一級緩存中如果存在該 bean 實例把實例緩存到factoryBeanObjectCache 對應的 map 中,這個是單獨緩存 FactoryBean 類型實例的 map。
4.總結
靈活創建所需實例對象的時候,通過實現FactoryBean
接口的getObject
方法定義實例化過程。
比如MyBatis
提供mybatis-spring
項目中的 org.mybatis.spring.SqlSessionFactoryBean
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();
}
在上面的afterPropertiesSet()方法中,buildSqlSessionFactory()方法會根據mybatis的配置,完成客戶所需要的的sessionFactory的初始化。