Spring FactoryBean源码解析

FactoryBean

实现FactoryBean接口

FactoryBean的写法有很多,先试一下实现FactoryBean接口的方法 定义一个Book对象

java 复制代码
public class Book {

	private String name;
    
	public void setName(String name) {
		this.name = name;
	}
    
	@Override
	public String toString() {
		return "{\"Book\":{"
				+ "\"name\":\""
				+ name + '\"'
				+ "}}";

	}
    
}

定义一个生产Book的工厂,实现FactoryBean,并通过@Component注解注入到spring容器

java 复制代码
@Component
public class BookFactory implements FactoryBean<Book> {
	@Override
	public Book getObject() {
		Book book = new Book();
		book.setName("factory bean Book");
		return book;
	}

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

	@Override
	public String toString() {
		return "{\"BookFactory\":{"
				+ "}}";

	}
}

简单做一下测试

java 复制代码
@Configuration
// 扫描两个对象所在的包
@ComponentScan("com.pqsir.factoryBean")
public class MainApplication {
	public static void main(String[] args) {
		ApplicationContext context = new AnnotationConfigApplicationContext(MainApplication.class);
	}
}

熟悉源码的都知道ApplicationContext初始化后会提前初始化所有单例(且非懒汉模式)的bean,通过beanFactory.preInstantiateSingletons()方法 debug看一下初始化后的结果,就在preInstantiateSingletons方法最后打个断点,结果如下

可以看到一级缓存中存储了BookFactory的实例,却没有Book,一会儿在想Book,现在向spring容器要BookFactory应该是没有问题的

getBean

1.getBean(BookFactory.class)

java 复制代码
@Configuration
// 扫描两个对象所在的包
@ComponentScan("com.pqsir.factoryBean")
public class MainApplication {
	public static void main(String[] args) {
		ApplicationContext context = new AnnotationConfigApplicationContext(MainApplication.class);
		Object o1 = context.getBean(BookFactory.class);
		System.out.println(o1);
	}
}

输出

json 复制代码
{"BookFactory":{}}

确实没问题,但是拿它有啥用,我们的目的是通过BookFactory向spring容器注册一个Book,肯定想要取得是Book的Bean,那再试一下getByName的方式 2.getBean("bookFactory")

java 复制代码
@Configuration
// 扫描两个对象所在的包
@ComponentScan("com.pqsir.factoryBean")
public class MainApplication {
	public static void main(String[] args) {
		ApplicationContext context = new AnnotationConfigApplicationContext(MainApplication.class);
		Object o2 = context.getBean("bookFactory");
		System.out.println(o2);
	}
}

输出

json 复制代码
{"Book":{"name":"factory bean Book"}}

居然取到了Book的实例!说实话,刚开始走到这步我有点懵,传入的明明是bookFactory,而且一级缓存甚至三级缓存中都没有Book实例,却能取到,调试了一下getBean发现原因了 getBean最终会调用doGetBeandoGetBean会先判断一级缓存中有没有实例,如果有则直接返回

刚才调试已经证明一级缓存里肯定有bookFactory,但是返回的Bean却是Book,于是猜测getObjectForBeanInstance方法中被调包,跟进去看一眼

其中getObjectFromFactoryBean代码如下

也就是说,通过getBean再返回的时候,如果发现beanFactoryBean则不返回一级缓存的实例,而是调用一级缓存实例的getObjects方法,而bookFactorygetObjects返回的是Book,所以或取到的是Book的bean 通过这种方法,有了一个获取Book的方式,但是需要传bookFactory的name,这显然是不太合适,下面试一下直接去取Book3.getBean("book")

java 复制代码
Object o5 = context.getBean("book");
System.out.println(o5);

输出

arduino 复制代码
NoSuchBeanDefinitionException: No bean named 'book' available

看来此路不通,调试了一下错误的位置,再getBean(String name)里会查询bean定义,如果没有,直接报错(再对preInstantiateSingletons的调试过程中发现并没有Book的)beanDefinition 4.getBean(Book.class)

java 复制代码
Object o4 = context.getBean(Book.class);
System.out.println(o4);

输出

json 复制代码
{"Book":{"name":"factory bean Book"}}

取到了哈哈,实际上平时我们用的@Autowired最终走得都会这种方式,那么问题来了为啥"book"取不到,Book.class却能取到,调试一下找到原因,调试路径

  • getBean(Class<T> requiredType) -->resolveBean
  • resolveBean-->resolveNamedBean
  • resolveNamedBean-->getBeanNamesForType

重点就是getBeanNamesForType这个方法,它根据传入的参数Book.class转换为beanName,而这个beanName的值正是"bookFactory",最后再通过getBean("bookFactory")自然取到了Book(第二步已证明) 而getBeanNamesForType是怎么做到的呐,回头看BookFactory有个这个实现

java 复制代码
@Override
public Class<?> getObjectType() {
    return Book.class;
}

getBeanNamesForType就是利用了这个方法,循环所有一级缓存中的FactoryBean,如果getObjectType==requiredType,就返回一级缓存的key,也就是BeanName("bookFactory"),这里代码有点多,截图截不下,纯理解吧

所以getBean(Class<T> requiredType)最终是通过转换还是走Object getBean(String name)

另外一种获取BeanFactory的方式

java 复制代码
Object o2 = context.getBean("&bookFactory");
System.out.println(o2);

输出

json 复制代码
{"BookFactory":{}}

单例

刚才说获取Book是通过BookFactorygetObjects()方法,但如果每次都走的话,那相当于获取一次就new一个,这肯定不行,spring是怎么解决的呐,其实解决方案很简单,第一次生成之后存起来,以后再获取就直接返回存的值即可实现单例 重点还是再调包方法getObjectForBeanInstance

也就是生成之后存入factoryBeanObjectCache

java 复制代码
/** Cache of singleton objects created by FactoryBeans: FactoryBean name to object. */
private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<>(16);

所以spring容器存储bean的地方除了三级缓存,还有这个factoryBeanObjectCache存放了使用FactoryBean生成的bean

小结

继承FactoryBean方式实现工厂bean注入,画个图梳理下整个过程 工厂方法创建的bean,文字总结一下: 工厂创建的bean,实际上bean定义&三级缓存中都是存放着其工厂对象,只是在获取时发现如果工厂对象,通过调用getObject获取实际的bean对象

@Bean

另外一种定义FactoryBean的方法是最常见的@Configuration+@Bean 写个例子,一个类:

java 复制代码
public class Bird {
	private String name;
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public String toString() {
		return "{\"Bird\":{"
				+ "\"name\":\""
				+ name + '\"'
				+ "}}";

	}	
}

通过@Configuration+@Bean加入spring容器

java 复制代码
@Configuration
public class BirdConfig {
	@Bean
	public Bird bird() {
		Bird bird = new Bird();
		bird.setName("factory bean Bird");
		return bird;
	}
}

直接先做个测试

java 复制代码
@Configuration
// 扫描两个对象所在的包
@ComponentScan("com.pqsir.factoryBean")
public class MainApplication {
	public static void main(String[] args) {
		ApplicationContext context = new AnnotationConfigApplicationContext(MainApplication.class);
		Object o1 = context.getBean(Bird.class);
		System.out.println(o1);
		Object o2 = context.getBean("bird");
		System.out.println(o2);
	}
}

这里就不去获取config了,也没啥实用意义,输出:

json 复制代码
{"Bird":{"name":"factory bean Bird"}}
{"Bird":{"name":"factory bean Bird"}}

两种方式都取到了,这个确实比实现FactoryBean接口的做法好一点,context.getBean("bird")也可以获取到Bean,那这种方式spring内部如何实现的呐? 按照之前的逻辑,在preInstantiateSingletons方法最后打个断点,看下结果

可以看到,预初始化后,bird的实例就已经生成在一级缓存了,而且beanDefinition中也有对应的bean定义 这和实现FactoryBean接口的方式就完全不一样了!!! 可以看出,spring是对待@Bean注解是直接扫描生成bean定义,然后预初始化时候直接生成实例存入一级缓存,完全不是上面那种依靠Factory去getObject这种方式了 这种固然非常好,生成的bean和普通的bean存在一起,而且获取方法完全一样,但是如何实现的呐,我们都知道普通的bean是通过反射实例化的,这种@Bean已经提供了初始化的方法,那肯定要走这个方法生成bean而不是反射了 看了下源码找到了关键点,所有预初始化的bean肯定要走createBeanInstance(实例化)这个方法

原来通过@Bean注入的bean,在bean定义中就存入了工厂方法(FactoryMethod),再实例化时判断如果存在工厂方法,直接使用工厂方法实例化并返回即可(不需要再往下走反射了)

总结

虽然这两种方式都是为了实现自定义实例化bean,外界都统称为FactoryBean,但通过以上的对比和源码分析,发现两种方式除了目的基本一样,实现的方式和实例化的实际种种都不同,甚至没有什么交集,严格来讲@Bean不能叫做FactoryBean,但如果把FactoryBean理解为通过工厂模式生成bean的一种方式,那二者确实可以混为一谈

相关推荐
其实防守也摸鱼1 小时前
无线网络安全--实验 规避WLAN验证之发现隐藏的SSID
java·开发语言·网络·安全·web安全·智能路由器·无线网络安全
pq2171 小时前
spring如何扫描解析bean(注册bean的多种方式)
spring
书源丶2 小时前
四十三、网络编程(下)——TCP 编程与 HTTP 入门
java·网络·tcp/ip·http
木井巳2 小时前
【递归算法】单词搜索
java·算法·leetcode·决策树·深度优先
幸运的大号暖贴2 小时前
解决Vibe Coding时Idea经常不自动git add问题
java·人工智能·git·intellij-idea·claudecode·opencode
m0_716255002 小时前
第一部分 数据开发 面试全题 模拟口述版(自问自答)
java·数据库·面试
azhou的代码园2 小时前
基于SpringBoot+Vue的家教小程序
vue.js·spring boot·小程序·毕业设计·家教小程序
SuperherRo2 小时前
服务攻防-Java组件安全&FastJson&高版本JNDI&不出网C3P0&编码绕WAF&写入文件CI链
java·安全·fastjson·waf·不出网·高版本·写入文件
彭于晏Yan2 小时前
Spring Boot 聚合MongoDB查询
spring boot·后端·mongodb