前言

谈到Spring的FactoryBean,就会知道Spring中经典的面试题:FactoryBean和BeanFactory的区别。我们这里就简单概括下: 、

  1. BeanFactory是接口,提供了OC容器最基本的形式,给具体的IOC容器的实现提供了规范,FactoryBean也是接口,为IOC容器中Bean的实现提供了更加灵活的方式,FactoryBean在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式(如果想了解装饰模式参考:修饰者模式(装饰者模式,Decoration) 我们可以在getObject()方法中灵活配置。其实在Spring源码中有很多FactoryBean的实现类.

  2. BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似

示例

首先我们定义个普通的java pojo对象名叫Car

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

public class CarFactoryBean  implements FactoryBean<Car> {
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<Car> 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 version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="car" name="car1 car2" class="com.john.factorybean.CarFactoryBean">
<property name="carInfo" value="超级跑车,400,2000000"/>
</bean>
</beans>

最后我们加载这个配置文件,启动Spring容器来获取car这个Bean

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);
}

成功打印出

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

@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<String> 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<Boolean>)
((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

protected <T> T doGetBean(final String name, @Nullable final Class<T> 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

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

/**
* 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

/**
* 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<Object>) factory::getObject, acc);
}
catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
//最重要的调用getObject方法
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;
}

从这个函数中我们看到了FactoryBean自身的getObject方法。调用完getObject方法后,Spring就会把这个对象放入factoryBeanObjectCache缓存对象中。之后再获取的时候直接从factoryBeanObjectCache缓存中拿就是了。

聊聊Spring的FactoryBean其实没那么难的更多相关文章

  1. block没那么难(二):block和变量的内存管理

    本系列博文总结自<Pro Multithreading and Memory Management for iOS and OS X with ARC> 了解了 block的实现,我们接着 ...

  2. .NET C#到Java没那么难,MVC篇

    最典型的JAVA MVC就是JSP + servlet + javabean的模式.比较好的MVC,老牌的有Struts.Webwork.新兴的MVC 框架有Spring MVC.Tapestry.J ...

  3. 从架构演进的角度聊聊Spring Cloud都做了些什么?

    Spring Cloud作为一套微服务治理的框架,几乎考虑到了微服务治理的方方面面,之前也写过一些关于Spring Cloud文章,主要偏重各组件的使用,本次分享主要解答这两个问题:Spring Cl ...

  4. 聊聊 Spring Boot 2.x 那些事儿

    本文目录: 即将的 Spring 2.0 - Spring 2.0 是什么 - 开发环境和 IDE - 使用 Spring Initializr 快速入门 Starter 组件 - Web:REST ...

  5. 没那么难,谈CSS的设计模式

    没那么难,谈CSS的设计模式 来源: 灵感的小窝  发布时间: 2016-09-09 16:46  阅读: 8949 次  推荐: 27   原文链接   [收藏]   什么是设计模式? 曾有人调侃, ...

  6. 【小家Spring】聊聊Spring中的数据绑定 --- BeanWrapper以及内省Introspector和PropertyDescriptor

    #### 每篇一句 > 千古以来要饭的没有要早饭的,知道为什么吗? #### 相关阅读 [[小家Spring]聊聊Spring中的数据转换:Converter.ConversionService ...

  7. 【小家Spring】聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)

    每篇一句 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 相关阅读 [小家Spring]聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和 ...

  8. Spring事务专题(五)聊聊Spring事务到底是如何实现的

    前言 本专题大纲: 本文为本专题倒数第二篇文章. 在上篇文章中我们一起学习了Spring中的事务抽象机制以及动手模拟了一下Spring中的事务管理机制,那么本文我们就通过源码来分析一下Spring中的 ...

  9. block没那么难(一):block的实现

    本系列博文总结自<Pro Multithreading and Memory Management for iOS and OS X with ARC> block 顾名思义就是代码块,将 ...

随机推荐

  1. python程序基础

    高级程序设计语言包括Python.C/C++.Java等 低级程序设计语言包括汇编语言和机器语言   Python是一种解释型语言,但为了提高运行效率,Python程序在 执行一次之后会自动生成扩展名 ...

  2. 在iOS 4中创建一个LDGradientView样式的渐变视图

    本教程将演示如何在 Swift 4 中创建一个多功能的.@IBDesignable 样式的渐变视图类.你可以将 CAGradientView 放到 storyboard 中,并在设计时预览,或者以编程 ...

  3. 阿里云的nginx的https配置问题

    server { listen 443 ssl; server_name www.xxx域名.com; root html; index index.html index.html; ssl_cert ...

  4. C# 9 record 并非简单属性 POCO 的语法糖

    C# 9 record 并非简单属性 POCO 的语法糖 最近升级专案到大统一 .NET 5 并使用 C#9 语法尝试改写套件,发现之前以为 record 只是简单属性 POCO 的简化语法糖的认知是 ...

  5. DocView 现在支持自定义 Markdown 模版了!

    前言 有小伙伴反馈说希望可以自定义 Markdown 模版,这样就可以导出自己想要的样式了!这个功能可以有,毕竟大家不可能都生成一模一样的文档.现在来一起看看如何实现自定义模版吧! 设置模版 Sett ...

  6. 不会吧!做了这么久开发还有不会NIO的,看看BAT大佬是怎么用的吧

    前言 在将NIO之前,我们必须要了解一下Java的IO部分知识. BIO(Blocking IO) 阻塞IO,在Java中主要就是通过ServerSocket.accept()实现的. NIO(Non ...

  7. How to realize one's ambition

    Work Overtime Can it work? To some extent, it parhaps works very well. What if you do little job and ...

  8. 使用ABBYY FineReader 手动校正文档复杂结构

    ABBYY FineReader 15(Windows系统)拥有强大的OCR识别功能,能对扫描仪或者数码相机等光学工具获取的图像进行识别,解析其中的文本.图像.表格.条形码等,方便用户进一步获取图像中 ...

  9. vulnhub: DC 3

    通过nmap扫描,只开放了80端口,并且该web服务是基于Joomla搭建: root@kali:~# nmap -A 192.168.74.140 Starting Nmap 7.80 ( http ...

  10. 宝塔Linux面板基础命令

    安装宝塔Centos安装脚本 yum install -y wget && wget -O install.sh http://download.bt.cn/install/insta ...