【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定义的典型情况,虽然可以使用ClassPathXmlApplicationContext或FileSystemXmlApplicationContext类代替,但是这两个类的主要区别在于加载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 有什么不同?
先占地后面再说。

浙公网安备 33010602011771号