美文网首页一些收藏
Spring FactoryBean源码解析

Spring FactoryBean源码解析

作者: pq217 | 来源:发表于2022-01-26 11:11 被阅读0次

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方法最后打个断点,结果如下

一级缓存

可以看到一级缓存中存储了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最终会调用doGetBeandoGetBean会先判断一级缓存中有没有实例,如果有则直接返回

doGetBean

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

getObjectForBeanInstance

其中getObjectFromFactoryBean代码如下

doGetObjectFromFactoryBean

也就是说,通过getBean再返回的时候,如果发现beanFactoryBean则不返回一级缓存的实例,而是调用一级缓存实例的getObjects方法,而bookFactorygetObjects返回的是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是通过BookFactorygetObjects()方法,但如果每次都走的话,那相当于获取一次就new一个,这肯定不行,spring是怎么解决的呐,其实解决方案很简单,第一次生成之后存起来,以后再获取就直接返回存的值即可实现单例
重点还是再调包方法getObjectForBeanInstance

getObjectForBeanInstance getObjectFromFactoryBean

也就是生成之后存入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
画个图梳理下整个过程

流程图

@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方法最后打个断点,看下结果

一级缓存 bean定义集合

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

createBeanInstance

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

总结

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

相关文章

网友评论

    本文标题:Spring FactoryBean源码解析

    本文链接:https://www.haomeiwen.com/subject/tjhnhrtx.html