一、Spring 容器高层视图
Spring 启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。
IoC文英全称Inversion of Control,即控制反转,可以这么理解IoC容器:把某些业务对象的的控制权交给一个平台或者框架来同一管理,这个同一管理的平台可以称为IoC容器。()
示例代码(详见):
//在传统 XML 方法中,使用 ClassPathXmlApplicationContext 类来加载外部 XML 上下文文件
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
TestService testService = (TestService) context.getBean("testService");
System.out.println("testService.name = " + testService.getName());
//基于 Java 的配置 或者注解(AnnotatedBeanDefinitionReader)、类路径(ClassPathBeanDefinitionScanner)
context = new AnnotationConfigApplicationContext(TestService.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println("BeanDefinitionNames:" + beanName);
}
testService = context.getBean(TestService.class);
System.out.println("testService.age = " + testService.getAge());
//此处的配置类是使用 @Configuration 注释声明的 AppContext
context = new AnnotationConfigApplicationContext(AppContext.class);
Course course = context.getBean(Course.class);
System.out.println("Course.name = " + course.getName());
Module module = context.getBean(Module.class);
System.out.println("module.assignment = " + module.getAssignment());
上面代码中,在创建ApplicationContext实例对象过程中会创建一个spring容器,该容器会读取配置文件"beans.xml",并统一管理由该文件中定义好的所有bean实例对象,如果要获取某个bean实例,使用getBean方法就行了。例如我们只需要将TestService 提前配置在beans.xml文件中(可以理解为注入),之后我们可以不需使用new TestService ()的方式创建实例,而是通过容器来获取TestService 实例,这就相当于将TestService 的控制权交由spring容器了,差不多这就是控制反转的概念。
那在创建IoC容器时经历了哪些呢?为此,先来了解下Spring中IoC容器分类,继而根据一个具体的容器来讲解IoC容器初始化的过程。
Spring中有两个主要的容器系列:
- 实现BeanFactory接口的简单容器;
- 实现ApplicationContext接口的高级容器。
我们可以认为直接的BeanFactory实现是IoC容器的基本形式,而各种ApplicationContext的实现是IoC容器的高级表现形式。ApplicationContext比较复杂,它不但继承了BeanFactory的大部分属性,还继承其它可扩展接口,扩展的了许多高级的属性,其接口定义如下:
public interface ApplicationContext extends EnvironmentCapable,
ListableBeanFactory, //继承于BeanFactory
HierarchicalBeanFactory,//继承于BeanFactory
MessageSource, //
ApplicationEventPublisher,//
ResourcePatternResolver //继承ResourceLoader,用于获取resource对象
在BeanFactory子类中有一个DefaultListableBeanFactory类,它包含了基本Spirng IoC容器所具有的重要功能,开发时不论是使用BeanFactory系列还是ApplicationContext系列来创建容器基本都会使用到DefaultListableBeanFactory类,可以这么说,在spring中实际上把它当成默认的IoC容器来使用。下文在源码实例分析时你将会看到这个类。
关于Spirng IoC容器的初始化过程在《Spirng技术内幕:深入解析Spring架构与设计原理》一书中有明确的指出,IoC容器的初始化过程可以分为三步:
- Resource定位(Bean的定义文件定位):Resouce定位指的是BeanDefinition的资源定位,它由ResouceLoader通过统一的Resouce接口完成,Resouce对各种形式的BeanDefinition提供了统一接口。比如:在文件系统中Bean的定义信息可以使用FileSystemResouce来进行抽象,在类路径中的Bean的定义信息可以使用ClassPathResouce来使用等。
- 将Resource定位好的资源载入到BeanDefinition:这个过程是把用户定义好的Bean表示成IOC容器内部的数据结构,而这个容器内部数据结构就是BeanDefinition。BeanDefinition实际上就是POJO对象在IOC容器中的抽象,通过对BeanDefinition定义的数据结构,使IOC容器能够方便地对POJO对象进行管理。
- 将BeanDefiniton注册到容器中:这个过程通过调用BeanDefinitionRepository接口的实现来完成。这个注册过程把载入过程中解析得到的BeanDefinition向IOC容器进行注册,在IOC容器的内部将BeanDefinition注入到ConcurrentHashMap对象中,通过KEY获取指定Bean的信息。
- 第一步 Resource定位
Resource是Sping中用于封装I/O操作的接口。正如前面所见,在创建spring容器时,通常要访问XML配置文件,除此之外还可以通过访问文件类型、二进制流等方式访问资源,还有当需要网络上的资源时可以通过访问URL,Spring把这些文件统称为Resource,Resource的体系结构如下:
常用的resource资源类型如下:
FileSystemResource:以文件的绝对路径方式进行访问资源,效果类似于Java中的File;
ClassPathResourcee:以类路径的方式访问资源,效果类似于this.getClass().getResource("/").getPath();
ServletContextResource:web应用根目录的方式访问资源,效果类似于request.getServletContext().getRealPath("");
UrlResource:访问网络资源的实现类。例如file: http: ftp:等前缀的资源对象;
ByteArrayResource: 访问字节数组资源的实现类。
那如何获取上图中对应的各种Resource对象呢?
Spring提供了ResourceLoader接口用于实现不同的Resource加载策略,该接口的实例对象中可以获取一个resource对象,也就是说将不同Resource实例的创建交给ResourceLoader的实现类来处理。ResourceLoader从存储介质中加载Spring配置信息,并使用Resource表示这个配置文件的资源。ResourceLoader接口中只定义了两个方法:
Resource getResource(String location); //通过提供的资源location参数获取Resource实例
ClassLoader getClassLoader(); // 获取ClassLoader,通过ClassLoader可将资源载入JVM
注:ApplicationContext的所有实现类都实现ResourceLoader接口,因此可以直接调用getResource(参数)获取Resoure对象。不同的ApplicatonContext实现类使用getResource方法取得的资源类型不同,例如:FileSystemXmlApplicationContext.getResource获取的就是FileSystemResource实例;ClassPathXmlApplicationContext.gerResource获取的就是ClassPathResource实例;
XmlWebApplicationContext.getResource获取的就是ServletContextResource实例,不需要通过xml直接使用注解@Configuation方式加载资源的AnnotationConfigApplicationContext等等。
在资源定位过程完成以后,就为资源文件中的bean的载入创造了I/O操作的条件,如何读取资源中的数据将会在下一步介绍的BeanDefinition的载入过程中描述。
- 第二步 通过返回的resource对象,进行BeanDefinition的载入
1、什么是BeanDefinition? BeanDefinition与Resource的联系呢?官方文档中对BeanDefinition的解释如下:
A BeanDefinition describes a bean instance, which has property values, constructor argument values, and further information supplied by concrete implementations.
它们之间的联系从官方文档描述的一句话:Load bean definitions from the specified resource 中可见一斑。BeanDefinition是通过BeanDefinitionReader进行载入,BeanDefinitionReader读取Resource所指向的配置文件资源,然后解析配置文件。配置文件中每一个<bean>解析成一个BeanDefinition对象,并保存到BeanDefinitionRegistry中:
/**
* Load bean definitions from the specified resource.
* @param resource the resource descriptor
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors
*/
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
总之,BeanDefinition相当于一个数据结构,这个数据结构的生成过程是根据定位的resource资源对象中的bean而来的,这些bean在Spirng IoC容器内部表示成了的BeanDefintion这样的数据结构,IoC容器对bean的管理和依赖注入的实现都是通过操作BeanDefinition来进行的。
2、如何将BeanDefinition载入到容器?
在Spring中配置文件主要格式是XML,对于用来读取XML型资源文件来进行初始化的IoC 容器而言,该类容器会使用到AbstractXmlApplicationContext类,该类定义了一个名为loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 的方法用于获取BeanDefinition:
// 该方法属于AbstractXmlApplicationContect类
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
this.initBeanDefinitionReader(beanDefinitionReader);
// 用于获取BeanDefinition
this.loadBeanDefinitions(beanDefinitionReader);
}
此方法在具体执行过程中首先会new一个与容器对应的BeanDefinitionReader型实例对象,然后将生成的BeanDefintionReader实例作为参数传入loadBeanDefintions(XmlBeanDefinitionReader),继续往下执行载入BeanDefintion的过程。例如AbstractXmlApplicationContext有两个实现类:FileSystemXmlApplicationContext、ClassPathXmlApplicationContext,这些容器在调用此方法时会创建一个XmlBeanDefinitionReader类(AnnotationConfigApplicationContext 容器对应AnnotatedBeanDefinitionReader类)对象专门用来载入所有的BeanDefinition。
下面以XmlBeanDefinitionReader对象载入BeanDefinition为例,使用源码说明载入BeanDefinition的过程:
// 该方法属于AbstractXmlApplicationContect类
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();//获取所有定位到的resource资源位置(用户定义)
if (configResources != null) {
reader.loadBeanDefinitions(configResources);//载入resources
}
String[] configLocations = getConfigLocations();//获取所有本地配置文件的位置(容器自身)
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);//载入resources
}
}
经过了一次重载的方法,我们最终可以看到这个方法: AbstractBeanDefinitionReader的loadBeanDefinitions方法
/**
* Load bean definitions from the specified resource location.
*/
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
//这里得到当前定义的ResourceLoader,默认的使用DefaultResourceLoader
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
//将指定位置的Bean定义资源文件解析为Spring IoC容器封装的资源(Resource)
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.这里通过ResourceLoader来完成位置定位
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
那么对于取得Resource的具体过程,((ResourcePatternResolver) resourceLoader).getResources(location)这个方法大家点开看看就可以知道,是PathMatchingResourcePatternResolver中的实现的,它具体里面就是针对我们配置的是否以“classpath*:” 开头分别处理。对于resourceLoader.getResource(location)方法,具体是交给继承ResourceLoader的子类完成的。这里不再多述了。
将xml文件转换成DOM对象
通过getResouce()方法得到Resouce之后,那么就开始了真正加载Bean资源。因为Spring可以对应不同形式的BeanDefition,我们这里讲的是xml的形式,所以就需要到xmlBeanDefinitonReader的实现中去看源码。
按照Spring的Bean规则对Document对象进行解析
走主线:得到dom对象后,开始启动对bean定义的详细解析,来看registerBeanDefinition()方法
XmlBeanDefinitionReader的loadBeanDefinitions()方法
- 第三步,将BeanDefiniton注册到容器中
最终Bean配置会被解析成BeanDefinition并与beanName,Alias一同封装到BeanDefinitionHolder类中, 之后beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition()),注册到DefaultListableBeanFactory.beanDefinitionMap中。之后客户端如果要获取Bean对象,Spring容器会根据注册的BeanDefinition信息进行实例化。DefaultListableBeanFactory就是所谓我们平常所说的 bean工厂,其父类就是 BeanFactory,BeanFactory有很多子类,DefaultListableBeanFactory就是其中一个子类。BeanDefinitionReaderUtils类:
/**
* Register the given bean definition with the given bean factory.
* @param definitionHolder the bean definition including name and aliases
* @param registry the bean factory to register with
* @throws BeanDefinitionStoreException if registration failed
*/
public static void registerBeanDefinition(
BeanDefinitionHolder bdHolder, BeanDefinitionRegistry beanFactory) throws BeansException {
// Register bean definition under primary name.
String beanName = bdHolder.getBeanName();
// 注册beanDefinition!!!
beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition());
// 如果有别名的话也注册进去,Register aliases for bean name, if any.
String[] aliases = bdHolder.getAliases();
if (aliases != null) {
for (int i = 0; i < aliases.length; i++) {
beanFactory.registerAlias(beanName, aliases[i]);
}
}
}
DefaultListableBeanFactory实现了上面调用BeanDefinitionRegistry接口的 registerBeanDefinition( beanName, bdHolder.getBeanDefinition())方法,这一部分的主要逻辑是向DefaultListableBeanFactory对象的beanDefinitionMap中存放beanDefinition,当初始化容器进行bean初始化时,在bean的生命周期分析里必然会在这个beanDefinitionMap中获取beanDefition实例,有机会成文分析一下bean的生命周期,到时可以分析一下如何使用这个beanDefinitionMap。bean的整个生命周期是围绕AbstractApplicationContext.refresh() 来进行的。
/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);
registerBeanDefinition( beanName, bdHolder.getBeanDefinition() )方法具体方法如下:
//---------------------------------------------------------------------
// Implementation of BeanDefinitionRegistry interface
//---------------------------------------------------------------------
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
} catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
// beanDefinitionMap是个ConcurrentHashMap类型数据,用于存放beanDefinition,它的key值是beanName
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
/*...此处去掉了各种IF ELSE 校验... */
//覆盖
this.beanDefinitionMap.put(beanName, beanDefinition);
} else {
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
// 将获取到的BeanDefinition放入Map中,容器操作使用bean时通过这个HashMap找到具体的BeanDefinition
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
} else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}
容器的初始化是通过AbstractApplicationContext的refresh()实现的。整个过程可以理解为容器的初始化过程。bean的整个生命周期是围绕refresh() 来进行的,那就先来看下代码吧:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//1.容器预先准备好刷新上下文,记录容器启动时间和标记等.
prepareRefresh();
//2.创建内部Bean工厂,如果已有则销毁,没有则创建;里面实现对beanDefinition的装载,
//Bean定义资源的Resource定位、载入解析和注册
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//3.配置bean工厂的标准上下文特性,如类装载器、PostProcessor等以备使用。
prepareBeanFactory(beanFactory);
try {
//4.模板方法,在所有beanDefinition被装载后,提供一个修改beanFactory的入口
postProcessBeanFactory(beanFactory);
//5.在spring的环境中去执行已经被注册的 Factory processors,设置执行自定义的
//postProcessBeanFactory和spring内部自己定义的,常见的PropertyPlaceholderConfigurer就是在这里被调用的
invokeBeanFactoryPostProcessors(beanFactory);
//6.注册用于拦截bean 创建过程的postProcessor
registerBeanPostProcessors(beanFactory);
//7.初始化MessageSource(国际化资源文件等)上下文的消息源。
initMessageSource();
//8.初始化此上下文的事件多播程序。
initApplicationEventMulticaster();
//9.在特定上下文子类中初始化其他特殊bean。
onRefresh();
//10.检查侦听器bean并注册它们。
registerListeners();
//11.完成实例化所有剩余的(非懒加载)单例。
//里面的beanFactory.preInstantiateSingletons()方法完成单例对象的创建
finishBeanFactoryInitialization(beanFactory);
//12.最后一步:发布相应的事件
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
该图描述了Spring容器从加载配置文件到创建出一个完整Bean的作业流程:
1、ResourceLoader从存储介质中加载Spring配置信息,并使用Resource表示这个配置文件的资源;
2、BeanDefinitionReader读取Resource所指向的配置文件资源,然后解析配置文件。配置文件中每一个<bean>解析成一个BeanDefinition对象,并保存到BeanDefinitionRegistry中;
3、容器扫描BeanDefinitionRegistry中的BeanDefinition,使用Java的反射机制自动识别出Bean工厂后处理后器(实现BeanFactoryPostProcessor接口)的Bean,然后调用这些Bean工厂后处理器对BeanDefinitionRegistry中的BeanDefinition进行加工处理。主要完成以下两项工作:
1)对使用到占位符的<bean>元素标签进行解析,得到最终的配置值,这意味对一些半成品式的BeanDefinition对象进行加工处理并得到成品的BeanDefinition对象;
2)对BeanDefinitionRegistry中的BeanDefinition进行扫描,通过Java反射机制找出所有属性编辑器的Bean(实现java.beans.PropertyEditor接口的Bean),并自动将它们注册到Spring容器的属性编辑器注册表中(PropertyEditorRegistry)
4.Spring容器从BeanDefinitionRegistry中取出加工后的BeanDefinition,并调用InstantiationStrategy着手进行Bean实例化的工作;
5.在实例化Bean时,Spring容器使用BeanWrapper对Bean进行封装,BeanWrapper提供了很多以Java反射机制操作Bean的方法,它将结合该Bean的BeanDefinition以及容器中属性编辑器,完成Bean属性的设置工作;
6.利用容器中注册的Bean后处理器(实现BeanPostProcessor接口的Bean)对已经完成属性设置工作的Bean进行后续加工,直接装配出一个准备就绪的Bean。
Spring容器确实堪称一部设计精密的机器,其内部拥有众多的组件和装置。Spring的高明之处在于,它使用众多接口描绘出了所有装置的蓝图,构建好Spring的骨架,继而通过继承体系层层推演,不断丰富,最终让Spring成为有血有肉的完整的框架。所以查看Spring框架的源码时,有两条清晰可见的脉络:
1)接口层描述了容器的重要组件及组件间的协作关系;
2)继承体系逐步实现组件的各项功能。
接口层清晰地勾勒出Spring框架的高层功能,框架脉络呼之欲出。有了接口层抽象的描述后,不但Spring自己可以提供具体的实现,任何第三方组织也可以提供不同实现, 可以说Spring完善的接口层使框架的扩展性得到了很好的保证。纵向继承体系的逐步扩展,分步骤地实现框架的功能,这种实现方案保证了框架功能不会堆积在某些类的身上,造成过重的代码逻辑负载,框架的复杂度被完美地分解开了。
Spring组件按其所承担的角色可以划分为两类:
1)物料组件:Resource、BeanDefinition、PropertyEditor以及最终的Bean等,它们是加工流程中被加工、被消费的组件,就像流水线上被加工的物料;
2)加工设备组件:ResourceLoader、BeanDefinitionReader、BeanFactoryPostProcessor、InstantiationStrategy以及BeanWrapper等组件像是流水线上不同环节的加工设备,对物料组件进行加工处理。