FactoryBean
实现FactoryBean接口
FactoryBean的写法有很多,先试一下实现FactoryBean
接口的方法
定义一个Book
对象
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容器
@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\":{"
+ "}}";
}
}
简单做一下测试
@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
方法最后打个断点,结果如下
![](https://img.haomeiwen.com/i9112801/a8ffbbe6ed9c2fe8.png)
可以看到一级缓存中存储了BookFactory
的实例,却没有Book
,一会在想Book
,现在向spring容器要BookFactory
应该是没有问题的
getBean
1.getBean(BookFactory.class)
@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);
}
}
输出
{"BookFactory":{}}
确实没问题,但是拿它有啥用,我们的目的是通过BookFactory
向spring容器注册一个Book
,肯定想要取得是Book
的Bean,那再试一下getByName的方式
2.getBean("bookFactory")
@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);
}
}
输出
{"Book":{"name":"factory bean Book"}}
居然取到了Book
的实例!说实话,刚开始走到这步我有点懵,传入的明明是bookFactory
,而且一级缓存甚至三级缓存中都没有Book
实例,却能取到,调试了一下getBean
发现原因了
getBean
最终会调用doGetBean
,doGetBean
会先判断一级缓存中有没有实例,如果有则直接返回
![](https://img.haomeiwen.com/i9112801/554fcc8040ca6316.png)
刚才调试已经证明一级缓存里肯定有bookFactory
,但是返回的Bean却是Book
,于是猜测getObjectForBeanInstance
方法中被调包,跟进去看一眼
![](https://img.haomeiwen.com/i9112801/fdf47d23c09d4902.png)
其中getObjectFromFactoryBean
代码如下
![](https://img.haomeiwen.com/i9112801/e639c212609d7242.png)
也就是说,通过getBean
再返回的时候,如果发现bean
是FactoryBean
则不返回一级缓存的实例,而是调用一级缓存实例的getObjects
方法,而bookFactory
的getObjects
返回的是Book
,所以或取到的是Book
的bean
通过这种方法,有了一个获取Book
的方式,但是需要传bookFactory
的name,下面试一下直接去取Book
。
3.getBean("book")
Object o5 = context.getBean("book");
System.out.println(o5);
输出
NoSuchBeanDefinitionException: No bean named 'book' available
看来此路不通,调试了一下错误的位置,再getBean(String name)
里会查询bean定义,如果没有,直接报错(再对preInstantiateSingletons
的调试过程中发现并没有Book
的)beanDefinition
4.getBean(Book.class)
Object o4 = context.getBean(Book.class);
System.out.println(o4);
输出
{"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
有个这个实现
@Override
public Class<?> getObjectType() {
return Book.class;
}
getBeanNamesForType
就是利用了这个方法,循环所有一级缓存中的FactoryBean
,如果getObjectType==requiredType
,就返回一级缓存的key,也就是BeanName
("bookFactory"
),这里代码有点多,截图截不下,纯理解吧
所以getBean(Class<T> requiredType)
最终是通过转换还是走Object getBean(String name)
另外一种获取BeanFactory的方式
Object o2 = context.getBean("&bookFactory");
System.out.println(o2);
输出
{"BookFactory":{}}
单例
刚才说获取Book
是通过BookFactory
的getObjects()
方法,但如果每次都走的话,那相当于获取一次就new一个,这肯定不行,spring是怎么解决的呐,其实解决方案很简单,第一次生成之后存起来,以后再获取就直接返回存的值即可实现单例
重点还是再调包方法getObjectForBeanInstance
中
![](https://img.haomeiwen.com/i9112801/f7625b23ce95399a.png)
![](https://img.haomeiwen.com/i9112801/96a4c12bd76bce1f.png)
也就是生成之后存入factoryBeanObjectCache
里
/** 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
画个图梳理下整个过程
![](https://img.haomeiwen.com/i9112801/c0b5e72ce13f6d8a.png)
@Bean
另外一种定义FactoryBean的方法是最常见的@Configuration
+@Bean
写个例子,一个类:
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容器
@Configuration
public class BirdConfig {
@Bean
public Bird bird() {
Bird bird = new Bird();
bird.setName("factory bean Bird");
return bird;
}
}
直接先做个测试
@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了,也没啥实用意义,输出:
{"Bird":{"name":"factory bean Bird"}}
{"Bird":{"name":"factory bean Bird"}}
两种方式都取到了,这个确实比实现FactoryBean接口的做法好一点,context.getBean("bird")也可以获取到Bean,那这种方式spring内部如何实现的呐?
按照之前的逻辑,在preInstantiateSingletons
方法最后打个断点,看下结果
![](https://img.haomeiwen.com/i9112801/4f94f2de3d74d28f.png)
![](https://img.haomeiwen.com/i9112801/2eabcc0ca4900c2c.png)
可以看到,预初始化后,bird的实例就已经生成在一级缓存了,而且beanDefinition中也有对应的bean定义
这和实现FactoryBean接口的方式就完全不一样了!!!
可以看出,spring是对待@Bean
注解是直接扫描生成bean定义,然后预初始化时候直接生成实例存入一级缓存,完全不是上面那种依靠Factory去getObject这种方式了
这种固然非常好,生成的bean和普通的bean存在一起,而且获取方法完全一样,但是如何实现的呐,我们都知道普通的bean是通过反射实例化的,这种@Bean
已经提供了初始化的方法,那肯定要走这个方法生成bean而不是反射了
看了下源码找到了关键点,所有预初始化的bean肯定要走createBeanInstance
(实例化)这个方法
![](https://img.haomeiwen.com/i9112801/096636d9c8e903d6.png)
原来通过@Bean
注入的bean,在bean定义中就存入了工厂方法(FactoryMethod),再实例化时判断如果存在工厂方法,直接使用工厂方法实例化并返回即可(不需要再往下走反射了)
总结
虽然这两种方式都是为了实现自定义实例化bean,外界都统称为FactoryBean
,但通过以上的对比和源码分析,发现两种方式除了目的基本一样,实现的方式和实例化的实际种种都不同,甚至没有什么交集,严格来讲@Bean
不能叫做FactoryBean
,但如果把FactoryBean理解为通过工厂模式生成bean的一种方式,那二者确实可以混为一谈
网友评论