扫描下方二维码或者微信搜索公众号
菜鸟飞呀飞
,即可关注微信公众号,阅读更多Spring源码分析文章
文章目录
首先需要说明的是,FactoryBean和BeanFactory虽然名字很像,但是这两者是完全不同的两个概念,用途上也是天差地别。BeanFactory是一个Bean工厂,在一定程度上我们可以简单理解为它就是我们平常所说的Spring容器(注意这里说的是简单理解为容器),它完成了Bean的创建、自动装配等过程,存储了创建完成的单例Bean。而FactoryBean通过名字看,我们可以猜出它是Bean,但它是一个特殊的Bean,究竟有什么特殊之处呢?它的特殊之处在我们平时开发过程中又有什么用处呢?
1. FactoryBean的用法
FactoryBean的特殊之处在于它可以向容器中注册两个Bean,一个是它本身,一个是FactoryBean.getObject()方法返回值所代表的Bean。先通过如下示例代码来感受下FactoryBean的用处吧。
- 自定义一个类CustomerFactoryBean,让它实现了FactoryBean接口,重写了接口中的两个方法,在getObejct()方法中,返回了一个UserService的实例对象;在getObjectType()方法中返回了UserService.class。然后在CustomerFactoryBean添加了注解@Component注解,意思是将CustomerFactoryBean类交给Spring管理。
package com.tiantang.study.component;
import com.tiantang.study.service.UserService;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;
@Component
public class CustomerFactoryBean implements FactoryBean<UserService> {
@Override
public UserService getObject() throws Exception {
return new UserService();
}
@Override
public Class<?> getObjectType() {
return UserService.class;
}
}
- 定义了一个UserService类,在构造方法中打印了一行日志。
package com.tiantang.study.service;
public class UserService {
public UserService(){
System.out.println("userService construct");
}
}
- 定义了一个配置类AppConfig,在类中指明了Spring需要扫描包
com.tiantang.study.component
package com.tiantang.study.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.tiantang.study.component")
public class AppConfig {
}
- 启动类
public class MainApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println("容器启动完成");
UserService userService = applicationContext.getBean(UserService.class);
System.out.println(userService);
Object customerFactoryBean = applicationContext.getBean("customerFactoryBean");
System.out.println(customerFactoryBean);
}
}
-
控制台打印的结果
-
在进行源码分析之前,我们可以先看下这两个问题:
-
- 在AppConfig类中我们只扫描了
com.tiantang.study.component
这个包下的类,按照我们的常规理解,这个时候应该只会有CustomerFactoryBean这个类被放进Spring容器中了,UserService并没有被扫描。而我们在测试时却可以通过applicationContext.getBean(UserService.class)
从容器中获取到Bean,为什么?
- 在AppConfig类中我们只扫描了
-
- 我们知道默认情况下,在我们没有自定义命名策略的情况下,我们自定义的类被Spring扫描进容器后,Bean在Spring容器中的beanName默认是类名的首字母小写,所以这本次demo中,
CustomerFactoryBean
类的单例对象在容器中的beanName是customerFactoryBean。所以这个时候我们调用方法getBean(beanName)通过beanName去获取Bean,这个时候理论上应该返回的是CustomerFactoryBean类的单例对象。然而,我们将结果打印出来,却发现,这个对象的hashCode竟然和userService对象的hashCode一模一样,这说明这两个对象是同一个对象,为什么会出现这种情况呢?为什么不是CustomerFactoryBean类的实例对象呢?
- 我们知道默认情况下,在我们没有自定义命名策略的情况下,我们自定义的类被Spring扫描进容器后,Bean在Spring容器中的beanName默认是类名的首字母小写,所以这本次demo中,
-
- 既然通过customerFactoryBean这个beanName无法获取到CustomerFactoryBean的单例对象,那么应该怎么获取呢?
以上3个问题的答案可以用一个答案解决,那就是FactoryBean是一个特殊的Bean。我们自定义的CustomerFactoryBean实现了FactoryBean接口,所以当CustomerFactoryBean被扫描进Spring容器时,实际上它向容器中注册了两个bean,一个是CustomerFactoryBean类的单例对象;另外一个就是getObject()方法返回的对象,在demo中,我们重写的getObject()方法中,我们通过new UserService()返回了一个UserService的实例对象,所以我们从容器中能获取到UserService的实例对象。如果我们想通过beanName去获取CustomerFactoryBean的单例对象,需要在beanName前面添加一个
&
符号,如下代码,这样就能根据beanName获取到原生对象了。
public class MainApplication {
public static void main(String[] args) {
CustomerFactoryBean rawBean = (CustomerFactoryBean) applicationContext.getBean("&customerFactoryBean");
System.out.println(rawBean);
}
}
2. FactoryBean的源码
通过上面的示例代码,我们知道了FactoryBean的作用,也知道该如何使用FactoryBean,那么接下来我们就通过源码来看看FactoryBean的工作原理。
- 在Spring容器启动阶段,会调用到refresh()方法,在refresh()中有调用了finishBeanFactoryInitialization()方法,最终会调用到beanFactory.preInstantiateSingletons()方法。所以我们先看下这个方法的源码。(对refresh()方法不太熟悉的朋友,可以去看下笔者的另外两篇文章:Spring源码系列之容器启动流程 ,通过源码看Bean的创建过程)。
p