IoC容器初始化过程(一):Resource定位

BeanDefinition

application.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--XML方式配置Bean-->
<bean id="student" class="com.springleanring.Student">
</bean>
</beans>

Spring会把配置在XML中的bean信息转为IoC容器内部的数据结构,这个数据结构就是
BeanDefinition,也就是说在Spring的XML配置文件中存在的元素,在容器初始化时,会读取这些XML文件,从中解析,然后封装为BeanDefinition对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {

String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;

String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;

int ROLE_APPLICATION = 0;

int ROLE_SUPPORT = 1;

int ROLE_INFRASTRUCTURE = 2;

void setParentName(@Nullable String parentName);

@Nullable
String getParentName();

void setBeanClassName(@Nullable String beanClassName);//设置bean的className

@Nullable
String getBeanClassName();

void setScope(@Nullable String scope);//设置bean的scope属性

@Nullable
String getScope();

void setLazyInit(boolean lazyInit);//设置lazyInit

boolean isLazyInit();//

void setDependsOn(@Nullable String... dependsOn);

//省略了其他方法
}

IoC容器初始化过程

(1)Resource定位过程

Resource定位指的是BeanDefinition的资源定位,这个过程类似于容器寻找数据的过程。

以在application.xml中配置的为例:

BeanDefinition是元素在IoC容器内部的存在形式,对BeanDefinition的定位也就是对元素所在资源文件application.xml的定位,定位到资源文件的位置 才能从application.xml中读取信息,因此IoC容器初始化时首先要完成对Resource的定位。

(2)BeanDefinition的载入

这个载入过程是把用户定义好的Bean表示成IoC容器的内部数据结构,这个容器内部数据结构就是BeanDefinition,也就是POJO对象在IoC容器中的抽象,通过BeanDefinition IoC容器可以方便的对Bean进行管理。

(3)向IoC容器注册这些BeanDefinition

BanDefinition注册指的是将第(2)步中得到的每一个BeanDefinition注册到一个HashMap中,IoC容器通过这个HashMap来持有这些BeanDefinition数据。

Spring IoC中的两个主要容器

IoC容器可以理解为spring管理Bean的地方。

(1)BeanFactory

是一个接口,定义了IoC容器最基本的功能规范,提供容器最基本的功能。如getBean方法,通过该方法可以从容器中获取Bean。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

public interface BeanFactory {

/**
* 通过name获取容器中管理的bean
* @param name bean的name
* @return bean的一个实例
*/
Object getBean(String name) throws BeansException;

<T> T getBean(String name, @Nullable Class<T> requiredType)throws BeansException;

Object getBean(String name, Object... args) throws BeansException;

<T> T getBean(Class<T> requiredType) throws BeansException;

<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

boolean containsBean(String name);//是否包含某个bean

//是否是单 模式
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

//是否是原型模式
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

//省略了其他方法
}

(2)ApplicationContext

如果说BeanFactory是IoC容器的基本形式,那么ApplicationContext就是IoC容器的高级表现形式,它除了包含BeanFactory中的基本方法,还提供了一些其他的方法。

ApplicationContext同样是一个接口,它继承了ListableBeanFactory接口,ListableBeanFactory继承了BeanFacotry接口,因此ApplicationContext包含了BeanFactory接口中的所有方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {

String getId();

String getApplicationName();

String getDisplayName();

long getStartupDate();

ApplicationContext getParent();

AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;

}

常用的实现ApplicationContext接口的三种容器:

FileSystemXmlApplicationContext:从文件系统载入Resource

ClassPathXmlApplicationContext:从class path载入Resource

XmlWebApplicationContext:在web容器中载入Resource

通过ApplicationContext从容器中获取一个bean:

1
2
3
4
5
6
7
//使用FileSystemXmlApplicationContext加载配置文件
ApplicationContext context=new FileSystemXmlApplicationContext("classpath*:/spring/applicationContext.xml");
//ClassPathXmlApplicationContext加载
//ApplicationContext context=new ClassPathXmlApplicationContext("classpath*:/spring/applicationContext.xml");
//从容器中获取bean
Student student= (Stuedent) context.getBean("student");//根据id从配置文件中获取bean
student.getName();

那么就以FileSystemXmlApplicationContext为例,分析ApplicationContext的实现过程。

1.FileSystemXmlApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public FileSystemXmlApplicationContext {
}

public FileSystemXmlApplicationContext(ApplicationContext parent) {
super(parent);
}

/**
* 构造函数:需要传入XML资源文件的路径
*/
public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[]{configLocation}, true, (ApplicationContext)null);
}

/**
* 构造函数:传入多个资源文件
*/
public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, (ApplicationContext)null);
}

/**
* 构造函数
* @param configLocations 资源文件数组
* @param parent 双亲IoC容器
*/
public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
this(configLocations, true, parent);
}

/**
* 构造函数
* @param configLocations 资源文件路径
* @param refresh 是否调用refresh方法载入BeanDefinition
*/
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
this(configLocations, refresh, (ApplicationContext)null);
}

/**
* 构造函数,其他构造函数最终调用的都是该函数
* @param configLocations 资源文件路径
* @param refresh 是否调用refresh方法载入BeanDefinition
*/
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
super(parent);
this.setConfigLocations(configLocations);
if(refresh) {
this.refresh();//分析容器初始化的一个重要入口
}

}
/**
* 通过资源文件路径获取Resource对象,返回的是一个FileSystemResource对象,通过这个对象可以进行相关I/O操作,完成BeanDefinition定位
* @param path 资源文件路径
* @return Resource spring中的资源对象
*/
protected Resource getResourceByPath(String path) {
if(path.startsWith("/")) {
path = path.substring(1);
}

return new FileSystemResource(path);
}
  • 从第二个构造函数开始,实际上都是调用的 FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)这个构造函数。
  • 对BeanDefinition资源定位的过程是从refresh方法开始的,refresh方法是分析容器初始化的重要入口。

2.AbstractApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
public void refresh() throws BeansException, IllegalStateException {
Object var1 = this.startupShutdownMonitor;
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
// 调用obtainFreshBeanFactory()方法获取IoC容器,容器的创建和对Resource的定位就在该方法中
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);

try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if(this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}

this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}

}
}
}
/**
* 获取BeanFactory
*/
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//refreshBeanFactory中会创建IoC容器
this.refreshBeanFactory();
//获取创建的IoC容器
ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
if(this.logger.isDebugEnabled()) {
this.logger.debug("Bean factory for " + this.getDisplayName() + ": " + beanFactory);
}

return beanFactory;
}

public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
  • refreshBeanFactory()方法,在AbstractApplicationContext中,该方法是一个抽象方法,具体的实现由AbstractApplicationContext的子类AbstractRefreshableApplicationContext来实现,该方法中创建了IoC容器BeanFacotry对象。

  • getBeanFactory()方法也是抽象方法,在AbstractRefreshableApplicationContext中实现,其实就是返回refreshBeanFactory方法中创建的BeanFacotry对象。

3.AbstractRefreshableApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {

protected final void refreshBeanFactory() throws BeansException {
//如果已经建立了BeanFactory,销毁并关闭BeanFactory
if(this.hasBeanFactory()) {
this.destroyBeans();
this.closeBeanFactory();
}

try {
//构造了一个BeanFactory,这里使用DefaultListableBeanFactory实现
DefaultListableBeanFactory beanFactory = this.createBeanFactory();
beanFactory.setSerializationId(this.getId());
this.customizeBeanFactory(beanFactory);
//载入BeanDefinition
this.loadBeanDefinitions(beanFactory);
Object var2 = this.beanFactoryMonitor;
synchronized(this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
} catch (IOException var5) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
}
}

/**
* 创建DefaultListableBeanFactory的地方
* getInternalParentBeanFactory()的具体实现可以参看AbstractApplicationContext的实现
*
* @return
*/
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory(this.getInternalParentBeanFactory());
}

/**
* 获取创建的IoC容器
*/
public final ConfigurableListableBeanFactory getBeanFactory() {
Object var1 = this.beanFactoryMonitor;
synchronized(this.beanFactoryMonitor) {
if(this.beanFactory == null) {
throw new IllegalStateException("BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext");
} else {
return this.beanFactory;
}
}
}

/**
* 抽象方法,载入BeanDefinition,由子类AbstractXmlApplicationContext实现
*
* @param beanFactory
* @throws BeansException
* @throws IOException
*/
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws BeansException, IOException;
}
  • refreshBeanFactory()方法中调用了createBeanFactory()方法完成了容器的创建,使用的是DefaultListableBeanFactory类型的容器。
  • 创建容器之后,调用了loadBeanDefinitions()方法载入BeanDefinition,该方法由子类AbstractXmlApplicationContext实现。
  • loadBeanDefinitions()方法中包含了Resource资源定位的过程。

4.AbstractXmlApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//创建XmlBeanDefinitionReader对象,通过回调设置到BeanFactory中
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
//设置ResourceLoader
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
//启动Bean定义信息载入的过程,委托给beanDefinitionReader完成
this.initBeanDefinitionReader(beanDefinitionReader);
//载入BeanDefinition
this.loadBeanDefinitions(beanDefinitionReader);
}

protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
reader.setValidating(this.validating);
}

/**
* 载入BeanDefinition
* @param reader
* @throws BeansException
* @throws IOException
*/
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//获取Reource类型的配置文件的地址
Resource[] configResources = getConfigResources();
if (configResources != null) {
//调用AbstractBeanDefinitionReader中的loadBeanDefinitions方法
reader.loadBeanDefinitions(configResources);
}
//获取字符串类型的配置文件的地址
String[] configLocations = getConfigLocations();
if (configLocations != null) {
//调用AbstractBeanDefinitionReader中的loadBeanDefinitions方法
reader.loadBeanDefinitions(configLocations);
}
}

protected Resource[] getConfigResources() {
return null;
}
}
  • loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法中首先创建了XmlBeanDefinitionReader对象,从名称上可以猜到,它可以读取XML中定义的bean信息。
  • loadBeanDefinitions(XmlBeanDefinitionReader reader)方法中又调用了XmlBeanDefinitionReader中的loadBeanDefinitions()方法,XmlBeanDefinitionReader是AbstractBeanDefinitionReader的子类,该方法在AbstractBeanDefinitionReader中实现。

5.AbstractBeanDefinitionReader

载入BeanDefinition时:

  • AbstractBeanDefinitionReader中实现的是参数类型是String的方法。
  • 如果传入的参数是Resource类型的,先进入到loadBeanDefinitions(Resource… resources)方法中,在该方法中又会调用XmlBeanDefinitionReader中的loadBeanDefinitions方法,也就是如果参数类型是Resource,是由XmlBeanDefinitionReader实现的。

查看上面FileSystemXmlApplicationContext用法的例子,传入的参数是资源文件的地址,因此调用的是AbstractBeanDefinitionReader的loadBeanDefinitions(String… locations)方法:

1
FileSystemXmlApplicationContext("classpath*:/spring/applicationContext.xml");

AbstractBeanDefinitionReader类中实现的方法为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable, BeanDefinitionReader {

/**
* 载入参数类型是Resource对象的BeanDefinition,由子类XmlBeanDefinitionReader实现
*/
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
//对载入Bean的数量进行统计
int counter = 0;
//遍历整个Resource集合,从每个集合中载入所有的BeanDefinition
for (Resource resource : resources) {
//loadBeanDefinitions方法是一个接口方法,在XmlBeanDefinitionReader中有具体的实现
counter += loadBeanDefinitions(resource);
}
return counter;
}

/**
* 根据配置文件的地址,载入BeanDefinition
*/
@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}

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");
}
//这里对Resource的路径模式进行解析,得到Resource集合,这些集合指向我们已经定义好的BeanDefinition信息
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
//调用DefaultResourceLoader的getResource完成Resource定位,将传入的资源文件的地址,转为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.
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;
}
}

/**
* 根据配置文件的地址,载入BeanDefinition
*/
@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int counter = 0;
for (String location : locations) {
counter += loadBeanDefinitions(location);
}
return counter;
}
}

  • 在loadBeanDefinitions(String location, @Nullable Set actualResources)方法中定义了ResourceLoader对象,由ResourceLoader完成资源的加载。ResourceLoader其实是使用了DefaultResourceLoader对象的getResource方法完成Resource对象的定位。
  • 在loadBeanDefinitions(String location, @Nullable Set actualResources)方法中根据location资源文件地址将资源文件转为了Resource对象,然后还是调用的XmlBeanDefinitionReader中的loadBeanDefinitions(Resource resource)完成BeanDefinition的载入。

6.DefaultResourceLoader

进入DefaultResourceLoader看一下如何根据配置文件地址完成对Resource的定位:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class DefaultResourceLoader implements ResourceLoader {  
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
Iterator var2 = this.protocolResolvers.iterator();

Resource resource;
do {
if(!var2.hasNext()) {
//处理带有/反斜杠的resource
if(location.startsWith("/")) {
return this.getResourceByPath(location);
}
//处理带有calsspath标识的resource
if(location.startsWith("classpath:")) {
return new ClassPathResource(location.substring("classpath:".length()), this.getClassLoader());
}

try {
//处理URL标识的resource
URL url = new URL(location);
return (Resource)(ResourceUtils.isFileURL(url)?new FileUrlResource(url):new UrlResource(url));
} catch (MalformedURLException var5) {
return this.getResourceByPath(location);
}
}

ProtocolResolver protocolResolver = (ProtocolResolver)var2.next();
resource = protocolResolver.resolve(location, this);
} while(resource == null);

return resource;
}

protected Resource getResourceByPath(String path) {
return new DefaultResourceLoader.ClassPathContextResource(path, this.getClassLoader());
}
}

到此,IoC容器对Resource的定位过程已经完成。

最后简单总结一下重点流程:

1.初始化时需要创建容器BeanFactory,默认使用的是DefaultListableBeanFactory类型的容器。
2.容器创建之后,会调用loadBeanDefinitions()方法载入BeanDefinition.
载入过程中的流程:
(1)创建XmlBeanDefinitionReader对象,为读取XML资源文件中定义的bean信息做准备。
(2)为XmlBeanDefinitionReader对象设置ResoucrLoader,用于资源文件的加载。
(3)调用XmlBeanDefinitionReader父类AbstractBeanDefinitionReader中实现的loadBeanDefinitions()方法,传入资源文件对象或文件地址,在这一步中,ResourceLoader对象实际使用的是DefaultResourceLoader。
(4)在DefaultResourceLoader中完成了对Resource的定位。

参考:

spring技术内幕:深入解析spring架构与设计原理

竹叶青1986:Spring源码阅读之IoC容 初始化1 – Resource定位

Spring版本:5.0.5