美文网首页一些收藏
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