【Spring 源码分析】GenericApplicationContext 详解

GenericApplicationContext 详解

通用应用程序上下文实现,该实现内部有一个 DefaultListableBeanFactory 实例。

这里所谓的通用指的是,可以采用混合方式处理bean的定义,而不是采用特定的bean定义方式来创建bean。

下面是官方提供的一个使用例子,分别通过xml、properties方式来创建bean。

GenericApplicationContext ctx = new GenericApplicationContext();

XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
xmlReader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));

PropertiesBeanDefinitionReader propReader = new PropertiesBeanDefinitionReader(ctx);
propReader.loadBeanDefinitions(new ClassPathResource("otherBeans.properties"));

// refresh 只能调用一次
ctx.refresh();
  
MyBean myBean = (MyBean) ctx.getBean("myBean");

对于XML Bean定义的典型情况,虽然可以使用ClassPathXmlApplicationContextFileSystemXmlApplicationContext类代替,但是这两个类的主要区别在于加载XML定义文件的路径不同(都是通过XML加载bean定义),不能使用混合Bean定义,而且文件路径的表示方式单一,不能灵活改动。

下面这种情况就展示了XML和注解混合使用来定义Bean。

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestBean.class);
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(context);
xmlReader.loadBeanDefinitions(new ClassPathResource("normalBean.xml"));

下图是创建 IoC 容器的几种方式:


1. 构造方法

该类中加上无惨构造方法一共有四个,但是只看其中一个就可以。

public GenericApplicationContext(DefaultListableBeanFactory beanFactory, ApplicationContext parent) {
    this(beanFactory);
    setParent(parent);
}

构造方法的第二个参数需要传一个ApplicationContext,来当做当前容器的父容器。也就是说,Spring中的每个容器都是都是独立的,在一个应用程序中可以创建多个容器并相互关联。

而第一个参数 DefaultListableBeanFactory 是整个bean加载的核心部分,是Spring注册及加载bean的默认实现。


2. 方法

GenericApplicationContext 类中的所有方法基本上都是通过 DefaultListableBeanFactory 来完成的,所以这里就主要了解 registerBean 方法。

/**
 * Register a bean from the given bean class, using the given supplier for
 * obtaining a new instance (typically declared as a lambda expression or
 * method reference), optionally customizing its bean definition metadata
 * (again typically declared as a lambda expression).
 * <p>This method can be overridden to adapt the registration mechanism for
 * all {@code registerBean} methods (since they all delegate to this one).
 * @param beanName bean 名字。如果是 null 则会使用类名。
 * @param beanClass 要创建的 bean 类。
 * @param supplier 如果为 null,这会通过 beanClass 构造函数自动装配,否则会直接回调获取 bean 实例。
 * @param customizers 定制的意思。也就是说在 bean 还没有被注册之前可以通过该接口做定制化,例如:强制要求该 Bean 为单例或多例。
 */
public <T> void registerBean(@Nullable String beanName, Class<T> beanClass,
		@Nullable Supplier<T> supplier, BeanDefinitionCustomizer... customizers) {

    ClassDerivedBeanDefinition beanDefinition = new ClassDerivedBeanDefinition(beanClass);
    if (supplier != null) {
        beanDefinition.setInstanceSupplier(supplier);
    }
    for (BeanDefinitionCustomizer customizer : customizers) {
        // 如果你是使用某个构造方法来创建 Bean,这里的回调就会
        // 按照你的参数顺序将参数类型和值保存到当前的 BeanDefinition 中。
        //
        // 参数顺序指的是 constructorArgs 的顺序。
        // registerBean(@Nullable String beanName, Class<T> beanClass, Object... constructorArgs)
        customizer.customize(beanDefinition);
    }

    String nameToUse = (beanName != null ? beanName : beanClass.getName());
    
    // 该方法中会调用 DefaultListableBeanFactory#registerBeanDefinition 方法完成 Bean 注册。
    registerBeanDefinition(nameToUse, beanDefinition);
}

该方法体中用到了 ClassDerivedBeanDefinition 这个类,它是 GenericApplicationContext 的私有内部类并继承了 RootBeanDefinition 类。该类主要目的就是重写了getPreferredConstructors方法,用来获取对应类的构造方法。

在 AnnotationConfigApplicationContext 中重写了 registerBean 方法;这个注册方法最终会调用 AnnotatedBeanDefinitionReader#registerBean 注册 bean。


关于 BeanDefinitionRegistry 接口在 DefaultListableBeanFactory 源码分析 中有提到。



讨论

1. AnnotatedBeanDefinitionReader#registerBean 和 DefaultListableBeanFactory#registerBeanDefinition 有什么不同?

先占地后面再说。


参考资料

简析GenericApplicationContex

Spring 源码学习 03:创建 IoC 容器的几种方式

posted @ 2021-01-04 14:17  用户TT  阅读(1605)  评论(0)    收藏  举报