聊聊Spring的FactoryBean其實沒那麼難
阿新 • • 發佈:2020-12-15
## 前言
談到Spring的FactoryBean,就會知道Spring中經典的面試題:FactoryBean和BeanFactory的區別。我們這裡就簡單概括下: 、
1. BeanFactory是介面,提供了OC容器最基本的形式,給具體的IOC容器的實現提供了規範,FactoryBean也是介面,為IOC容器中Bean的實現提供了更加靈活的方式,FactoryBean在IOC容器的基礎上給Bean的實現加上了一個簡單工廠模式和裝飾模式(如果想了解裝飾模式參考:[修飾者模式(裝飾者模式,Decoration)](https://www.cnblogs.com/aspirant/p/9083082.html) 我們可以在getObject()方法中靈活配置。其實在Spring原始碼中有很多FactoryBean的實現類.
2. BeanFactory是個Factory,也就是IOC容器或物件工廠,FactoryBean是個Bean。在Spring中,**所有的Bean都是由BeanFactory(也就是IOC容器)來進行管理的**。但對FactoryBean而言,**這個Bean不是簡單的Bean,而是一個能生產或者修飾物件生成的工廠Bean,它的實現與設計模式中的工廠模式和修飾器模式類似** 。
## 示例
首先我們定義個普通的java pojo物件名叫Car
```java
public class Car {
private int maxSpeed ;
private String brand ;
private double price ;
public double getPrice() {
return price;
}
public int getMaxSpeed() {
return maxSpeed;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"maxSpeed=" + maxSpeed +
", brand='" + brand + '\'' +
", price=" + price +
'}';
}
}
```
再定義一個實現FactoryBean介面的實現類CarFactoryBean
```java
public class CarFactoryBean implements FactoryBean {
private String carInfo;
public Car getObject() throws Exception {
System.out.println("start new car");
Car car = new Car();
String[] infos = carInfo.split(",");
car.setBrand(infos[0]);
car.setMaxSpeed(Integer.valueOf(infos[1]));
car.setPrice(Double.valueOf(infos[2]));
return car;
}
public Class getObjectType(){
return Car.class ;
}
//返回的例項是同一個
public boolean isSingleton() {
return true;
}
public String getCarInfo() {
return this.carInfo ;
}
public void setCarInfo(String carInfo) {
this.carInfo = carInfo;
}
}
```
再來看看我們的Xml配置是如何配置Bean的
```xml
```
最後我們載入這個配置檔案,啟動Spring容器來獲取car這個Bean
```java
public static void main( String[] args ) {
ClassPathXmlApplicationContext ac =new ClassPathXmlApplicationContext();
ac.setConfigLocations("spring-config-locatorfactory.xml");
ac.refresh();
//getBean的時候才會去getObject
ac.getBean("car", Car.class);
}
```
成功打印出
```java
start new car
Car{maxSpeed=400, brand='超級跑車', price=2000000.0}
```
## 原理
如果我們把```ac.getBean("car", Car.class);```這行程式碼去掉,我們會發現start new car這行是不會列印的,那就是說明**getObject是延遲載入的,只有顯示呼叫才能觸發,spring在初始化啟動單例bean例項化的時候並不會觸發(SmartFactoryBean除外,之後我們會單獨分析下SmartFactoryBean)**。
那我們就到原始碼級別證明下Spring的FactoryBean的getObject是延遲初始化的
### DefaultListableBeanFactory
#### preInstantiateSingletons
```java
@Override
public void preInstantiateSingletons() throws BeansException {
// 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 beanNames = new ArrayList<>(this.beanDefinitionNames);
// Trigger initialization of all non-lazy singleton beans...
//todo 遍歷所有不是延遲初始化的 單例 bean 2020-08-28
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
//不是抽象的 且是單例的 且不是懶載入的
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
//todo 如果是factorybean 那還得去判斷 是不是eagerInit
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)
((SmartFactoryBean>) factory)::isEagerInit,
getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
}
else {
getBean(beanName);
}
}
}
}
```
我們看到Spring會在例項化單例Bean的時候會去判斷是否是Factorybean,如果是的話就會去根據```FACTORY_BEAN_PREFIX + beanName```獲取Factorybean本身,然後再判斷這個FactoryBean是SmartFactoryBean且isEagerInit設定為true才會去獲取beanName對應的Bean,也就是例項化這個bean。
那既然是延遲初始化的,那我們再獲取實際bean的時候Spring內部是怎麼做的呢?
### AbstractBeanFactory
#### doGetBean
```java
protected T doGetBean(final String name, @Nullable final Class requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// Create bean instance.
//從頭開始載入 bean,這個過程由 getSingleton() 實現。
if (mbd.isSingleton()) {
//https://www.cnblogs.com/leihuazhe/p/9481018.html
////todo 如果有單例那直接返回了 , args引數也就沒啥用處了 2020-08-30
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;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
}
```
這裡我們從getObjectForBeanInstance方法可以看出來這裡就是獲取真正bean的方法。
#### getObjectForBeanInstance
```java
protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
// 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.
//我們使用它來建立一個bean例項,除非呼叫者實際上想要引用工廠
//todo 如果是獲取FactoryBean本身 那就直接返回這個工廠
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
return beanInstance;
}
}
```
也就是說不是FactoryBean本身或者根據name判斷就是獲取FactoryBean本身的話,那就直接返回這個beanInstance。
那我們在```getBean("car")```的時候其實獲取到的beanInstance是FactoryBean的例項,所以走到這邊的時候不會返回。
下面Spring就會走到抽象類FactoryBeanRegistrySupport中的getObjectFromFactoryBean函式,因為AbstractBeanFactory繼承自FactoryBeanRegistrySupport,從名字就可以看出這個函式的意義就是呼叫FactoryBean的getObject方法。
### FactoryBeanRegistrySupport
#### getObjectFromFactoryBean
```java
/**
* Obtain an object to expose from the given FactoryBean.
* @param factory the FactoryBean instance
* @param beanName the name of the bean
* @param shouldPostProcess whether the bean is subject to post-processing
* @return the object obtained from the FactoryBean
* @throws BeanCreationException if FactoryBean object creation failed
* @see org.springframework.beans.factory.FactoryBean#getObject()
*/
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);
}
}
//object放入factoryBeanObjectCache快取
if (containsSingleton(beanName)) {
this.factoryBeanObjectCache.put(beanName, object);
}
}
}
return object;
}
}
else {
//非單例
省略部分程式碼
}
}
```
函式內部還會呼叫doGetObjectFromFactoryBean函式
#### doGetObjectFromFactoryBean
```java
/**
* Obtain an object to expose from the given FactoryBean.
* @param factory the FactoryBean instance
* @param beanName the name of the bean
* @return the object obtained from the FactoryBean
* @throws BeanCreationException if FactoryBean object creation failed
* @see org.springframework.beans.factory.FactoryBean#getObject()
*/
private Object doGetObjectFromFactoryBean(final FactoryBean> factory, final String beanName)
throws BeanCreationException {
Object object;
try {
if (System.getSecurityManager() != null) {
AccessControlContext acc = getAccessControlContext();
try {
object = AccessController.doPrivileged((PrivilegedExceptionAction