Spring之FactoryBean的使用与源码解析

Spring之FactoryBean的使用与源码解析,第1张

文章目录
      • FactoryBean介绍
      • FactoryBean的使用
        • 使用示例
        • 配置
        • 运行结果
        • 获取FactoryBean自身工厂对象
      • FactoryBean的好处
      • FactoryBean与BeanFactory的区别
      • 源码分析
        • FactoryBean的缓存区
        • 执行流程
        • 源码解析
          • FactoryBean的初始化过程
          • 获取FactoryBean实际生产对象过程
      • 总结

FactoryBean介绍

FactoryBean是Spring提供的一个可以由用户自定义的Bean实例工厂接口,FactoryBean的对象本身由Spring容器管理,但FactoryBean仅作为对象工厂暴露,其生产出的Bean的过程由用户控制。

FactoryBean接口提供了如下方法:

方法描述
T getObject()获取该FactoryBean所生产的实际对象, BeanFactory在获取FactoryBean生产的实际对象时,会根据其isSingleton()方法来判定是否创建的对象属于singleton或是prototype
Class getObjectType();返回工厂生产的实际对象类型。
boolean isSingleton();所生产的对象是否是单例;此方法告知beanFactory,每次获取该工厂的bean对象时是每次调用getObject()来获取新的对象,还是始终获取唯一的单例对象。
FactoryBean的使用 使用示例
package com.baiyang.factorybean;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.io.Serializable;

class Book implements Serializable {
    private static final long serialVersionUID = -672687543464668808L;

    private String name;
    private String author;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
}
public class BookFactory implements FactoryBean<Book> {
    public static boolean isSingleton = true;

    @Override
    public Book getObject() throws Exception {
        Book book = new Book();
        book.setName("《非暴力沟通》");
        book.setAuthor("马歇尔·卢森堡");
        return book;
    }

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

    @Override
    public boolean isSingleton() {
        return isSingleton;
    }

    public static void main(String[] args) {
        System.out.println("-------工厂singleton模式------");
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("factorybean.xml");
        Book book1 = (Book) context.getBean("book");
        System.out.println("book1:" + book1);
        Book book2 = (Book) context.getBean("book");
        System.out.println("book2:" + book2);
        System.out.println("book1 == book2:" + (book1 == book2));

        System.out.println("-------工厂prototype模式------");
        BookFactory.isSingleton = false;
        context = new ClassPathXmlApplicationContext("factorybean.xml");
        book1 = (Book) context.getBean("book");
        System.out.println("book1:" + book1);
        book2 = (Book) context.getBean("book");
        System.out.println("book2:" + book2);
        System.out.println("book1 == book2:" + (book1 == book2));
    }
}

配置

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="book" class="com.baiyang.factorybean.BookFactory"/>

beans>
运行结果
-------工厂singleton模式------
book1:com.baiyang.factorybean.Book@5bcab519
book2:com.baiyang.factorybean.Book@5bcab519
book1 == book2:true
-------工厂prototype模式------
book1:com.baiyang.factorybean.Book@73846619
book2:com.baiyang.factorybean.Book@4bec1f0c
book1 == book2:false

可以从结果中看出:

  • 尽管在xml中配置的id为"book"的对象是BookFactory类型,但通过getBean("book")获取的类型是Book类型,非BookFactory类型
  • FactoryBean#isSingleton()返回true时多次获取的book对象都是同一个对象。
  • FactoryBean#isSingleton()返回false时多次获取的book对象都是不同的对象。
获取FactoryBean自身工厂对象

如果需要获取FactoryBean自身的工厂对象需要在BeanId前加上"&“符号;”&“符号可以理解为C语言中获取变量的地址;在此处可以理解为"beanName"如果是工厂bean,则获取的是对应工厂生产的Bean,而”&beanName"获取的是生产该"beanName"的工厂;

System.out.println("--------获取FactoryBean自身对象-------");
System.out.println("BookFactory:" + context.getBean("&book"));
FactoryBean的好处

FactoryBean与BeanFactory的区别

两者都是用来创建对象的,当使用BeanFactory对象的时候必须遵循完整的创建过程,这个过程是由Spring容器来管理控制的。而FactoryBean只需要调用getObject方法就可以返回对象了,整个对象的创建过程是由用户来控制的。

源码分析 FactoryBean的缓存区


FactoryBeanRegistrySupport类实现了对FactoryBean的支持;我们熟知的最常用的Bean工厂DefaultListableBeanFactory继承了FactoryBeanRegistrySupport,从而也获得了FactoryBean的支持。

执行流程

源码解析 FactoryBean的初始化过程

首先看下BeanFactory实例化所有BeanDefinition的过程,来了解下FactoryBean的初始化过程。
先定位到AbstractApplicationCountext类的refresh()方法的finishBeanFactoryInitialization(beanFactory);位置如下:
可以看到当前BeanDefinitionMap中注册了nameBookBeanDefinition,其beanClass指向的FactoryBean实现类BookFactory

refresh()调用finishBeanFactoryInitialization(beanFactory);的最后会调用DefaultListableBeanFactory#preInstantiateSingletons()方法来对BeanFactory中注册的所有BeanDefinition进行初始化:

如上图可以看到最外层循环所有注册的BeanDefinition的代码有区别BeanName是否是FactoryBean来走不同的逻辑。

看下isFactoryBean()的方法是如何判断其为FactoryBean的:
org.springframework.beans.factory.support.AbstractBeanFactory#isFactoryBean(java.lang.String)

org.springframework.beans.factory.support.AbstractBeanFactory#isFactoryBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition)

然后是对FactoryBean的实例化关键点,由于前面对FactoryBeanname加了"&“前缀,那么注册到BeanFactory中的单例Beankey是否也是加了”&"符号的呢?
继续看下:
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

上面的第一步transformedBeanName(name)方法会将传入的name转成常规name,即:如果有"&“前缀则将”&"前缀去除。
然后对去除了&的name进行初始化,因为只有这样才能通过转成了常规的name找到BeanDefinition

org.springframework.beans.factory.support.AbstractBeanFactory#getObjectForBeanInstance

经过以上过程,最终会将FactoryBean对象注册到BeanFactorysingletonObjects属性中,并且name为设置的id值,并不带&符号;

获取FactoryBean实际生产对象过程

那么问题来了,由于singletonObjects里面注册的BeanFactoryBean对象,并不是实际的bean,那么Spring是怎么调用的是FactoryBeangetObject方法返回的呢?
换句话说,Spring用FactoryBean生产出实际bean对象的过程是怎么样的呢?

我们从getBean方法开始:

一路Debug到下面doGetBean

由于FactoryBean在容器初始化时就已经实例化注册到了BeanFactory中,所以Object sharedInstance = getSingleton(beanName);这行代码是可以找到FactoryBean对象的。

关键在上图的bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);这行代码的调用。
由于是获取FactoryBean的实际Bean,所以是没有BeanDefinition的,所以传入的实参是null。
进入看下实现:

由于传入的name不是以&符号开头,所以获取的是FactoryBean生产的对象,首先是从factoryBeanObjectCache缓存中通过beanName获取,如果获取到则直接返回,如果获取不到,则调用object = getObjectFromFactoryBean(factory, beanName, !synthetic);这行代码获取生产的实际对象。

org.springframework.beans.factory.support.FactoryBeanRegistrySupport#getObjectFromFactoryBean

从上图的过程可以看到factoryisSingleton返回的是true的话,那么会首先从factoryBeanObjectCache中获取,如果缓存中获取不到则调用doGetObjectFromFactoryBean方法来实际调用FactoryBeangetObject来获取对象,并将对象放入到factoryBeanObjectCache中用于后续的直接获取。
如果是非单例的话,则直接调用FactoryBeangetObject来每次都新创建对象返回。

返回实际生产对象:
org.springframework.beans.factory.support.FactoryBeanRegistrySupport#doGetObjectFromFactoryBean

最终在BeanFactory中呈现的内存情况:

可以看到工厂生产出来的实际对象存放在factoryBeanObjectCache变量中,工厂对象在容器初始化时注册在singletonObjects中;
并且两个变量中的key是相同的;

总结
  • SpringFactoryBean的实现关键在FactoryBeanRegistrySupport类,FactoryBeanRegistrySupport提供了一系列对FactoryBean的支持,其内部维护了一个factoryBeanObjectCache变量作为FactoryBean生产的单例bean的支持。
  • FactoryBean在容器初始化时就会被实例化到BeanFactorysingletonObjects中,FactoryBeangetObject生产出的bean如果是单例,则会存放到factoryBeanObjectCache变量中,便于后续的直接获取。
  • 通过getBean("name")获取的是FactoryBean生产的实际bean对象,通过getBean("&name")获取的是FactoryBean自身对象;

注意需要再配置的时候加上id值,没有加id值是不能默认通过首字母小写的类名获取到FactoryBean

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/868374.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-13
下一篇 2022-05-13

发表评论

登录后才能评论

评论列表(0条)

保存