【Spring Framework 深入】—— IoC容器初始化 -> Bean定义资源的Resource定位

时间:2022-08-26 19:41:28

基本概念

ApplicationContext 继承体系

本文主要关注ApplicationContext的继承体系,至于BeanFactory的分支,以后再研究。
【Spring Framework 深入】—— IoC容器初始化 -> Bean定义资源的Resource定位

BeanFactory or ApplicationContext?

BeanFactory和ApplicationContext都是实现IoC容器的基础接口。Application是BeanFactory的子接口,包含了BeanFactory的功能,同时增加了对Transactions和AOP的支持。所以官方更推荐开发者使用ApplicationContext及其子类实现IoC容器。特别地,Spring在实现时,大量使用ApplicationContext实现BeanPostProcessor extension point。

官方文档有如下阐述:
The BeanFactory provides the underlying basis for Spring’s IoC functionality but it is only used directly in integration with other third-party frameworks and is now largely historical in nature for most users of Spring.
只有在与第三方框架集成时,才推荐使用BeanFactory。
【Spring Framework 深入】—— IoC容器初始化 -> Bean定义资源的Resource定位

org.springframework.context.Interface ApplicationContext

All Superinterfaces:(继承接口)
ApplicationEventPublisher, BeanFactory, EnvironmentCapable, HierarchicalBeanFactory, ListableBeanFactory, MessageSource, ResourceLoader, ResourcePatternResolver
ApplicationContext实现了上述接口,丰富了基本IoC容器(BeanFactory)的行为,可以说是一个高级的IoC容器。
从ApplicationContext接口的实现,我们看出其特点:
1. 支持信息源,可以实现国际化。(实现MessageSource接口)
2. 访问资源。(实现ResourcePatternResolver接口)
3. 支持应用事件。(实现ApplicationEventPublisher接口)
它的常用具体类有ClasspathXmlApplicationContext和FileSystemXmlApplicationContext,Web 项目方面有XmlWebApplicationContext。

IoC容器的初始化

IoC容器的初始化主要包括BeanDefinition的Resource定位、载入解析和注册这三个基本的过程。我们以ApplicationContext为例讲解。由于篇幅过大,这篇文章先详细讲解Resource定位
【Spring Framework 深入】—— IoC容器初始化 -> Bean定义资源的Resource定位

创建:

ApplicationContext =new FileSystemXmlApplicationContext(xmlPath);

构造函数:分3步,分别是调用父类构造函数、setConfigLocations和refresh

public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)  
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}

1.super(parent)的作用是为容器设置Bean资源加载器,通过debug,可知实际是由其父类AbstractApplicationContext 完成设置。注意,从这里可以看到AbstractApplicationContext 继承了DefaultResourceLoader,所以实际上它自身也作为资源加载器。

AbstractApplicationContext.java

public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext, DisposableBean

public AbstractApplicationContext(ApplicationContext parent) {

this();
setParent(parent);
}
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}

2.接下来,setConfigLocations(configLocations)的作用是设置Bean定义资源文件的路径,实际是由其父类AbstractRefreshableConfigApplicationContext完成设置

AbstractRefreshableConfigApplicationContext.java

public abstract class AbstractRefreshableConfigApplicationContext extends AbstractRefreshableApplicationContext
implements BeanNameAware, InitializingBean

//从这里可以看到配置Bean定义资源文件可以使用两种方式,字符串和字符串数组
//字符串会以,;
/t/n这些分隔符分割
//location="a.xml,b.xml,..."
public void setConfigLocation(String location) {
//String CONFIG_LOCATION_DELIMITERS = ",; /t/n";
//即多个资源文件路径之间用” ,; /t/n”分隔,解析成数组形式
setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS));
}
//location=new String[]{“a.xml”,”b.xml”,……}
public void setConfigLocations(String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
// resolvePath为同一个类中将字符串解析为路径的方法
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}

3.接下来是开始进行Bean定义资源文件加载,由AbstractApplicationContext的refresh函数完成。
refresh函数是一个模板方法,执行多个方法,而且提供了各(protected)方法的(默认)实现,其子类可以重写它们
模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类(使用protected方法)可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
refresh函数中调用了多个方法,这里先不详细讲解每一个方法,可以先通过英文注释大概了解各方法的作用。

AbstractApplicationContext.java

public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
initMessageSource();

// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// Check for listener beans and register them.
registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
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();
}
}
}

refresh()核心调用方法1:obtainFreshBeanFactory函数调用,完成了容器初始化的最重要最基础的功能,Bean定义资源的Resource定位、载入解析和注册。

AbstractApplicationContext.java

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

这里使用了委派设计模式,obtainFreshBeanFactory中调用了两个抽象方法,定义了obtainFreshBeanFactory的算法骨架,实际的行为交给其子类(AbstractRefreshableApplicationContext)实现

AbstractRefreshableApplicationContext.java

@Override
protected final void refreshBeanFactory() throws BeansException {
//如果已经有容器,销毁容器中的bean,关闭容器,以保证在refresh之后使用的是新建立起来的IoC容器
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//创建IoC容器
DefaultListableBeanFactory beanFactory = createBeanFactory();
//对IoC容器进行定制化,如设置启动参数,开启注解的自动装配等
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
//调用载入Bean定义的方法,这里又使用了委派模式,在当前类中只定义了抽象的loadBeanDefinitions方法,具体的实现调用子类容器
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}

在这个方法中,先判断BeanFactory是否存在,如果存在则先销毁beans并关闭beanFactory,接着创建DefaultListableBeanFactory,并调用loadBeanDefinitions(beanFactory)装载bean
使用了委派模式,在当前类中只定义了抽象的loadBeanDefinitions方法,具体的实现调用子类容器(AbstractXmlApplicationContext)

AbstractXmlApplicationContext.java

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
//创建XmlBeanDefinitionReader,即创建Bean读取器,并通过回调设置到容器中去,容器使用该读取器读取Bean定义资源
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
//为Bean读取器设置资源加载器,
//AbstractXmlApplicationContext的祖先父类AbstractApplicationContext继承DefaultResourceLoader,
//因此,容器本身也是一个资源加载器
//所以,这个资源加载器由始至终都是容器自身
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}

调用了另一个重载函数loadBeanDefinitions(beanDefinitionReader),委托给了XmlBeanDefinitionReader

AbstractXmlApplicationContext.java

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//获取Bean定义资源的定位
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
//如果configResources为空,则获取FileSystemXmlApplicationContext构造方法中setConfigLocations方法设置的资源
String[] configLocations = getConfigLocations();
if (configLocations != null) {
//XmlBeanDefinitionReader调用其父类AbstractBeanDefinitionReader读取定位的Bean定义资源
reader.loadBeanDefinitions(configLocations);
}
}

这里也使用了委托模式,调用子类的获取Bean定义资源定位的方法(getConfigResources()),该方法在ClassPathXmlApplicationContext中实现,FileSystemXmlApplicationContext默认返回null。

由于FileSystemXmlApplicationContext的getConfigResources返回null,因此程序执行configLocations分支,调用XmlBeanDefinitionReader的父类AbstractBeanDefinitionReader的loadBeanDefinitions(String… locations)方法

AbstractBeanDefinitionReader.java

@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;
}

对每一个location调用loadBeanDefinitions,其抽象父类AbstractBeanDefinitionReader定义了方法骨架

AbstractBeanDefinitionReader.java

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
//1.获取在IoC容器初始化过程中设置的资源加载器,调用
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 {
//2.将指定位置的Bean定义资源文件解析为Spring IoC容器封装的资源(Resource)
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//3.委派调用其子类XmlBeanDefinitionReader的方法,加载Resource
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.
//和上面步骤2一样,获得Resource。实际调用的是DefaultResourceLoader中的getSource()方法定位Resource
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;
}
}

FileSystemXmlApplicationContext本身就是DefaultResourceLoader的实现类
【Spring Framework 深入】—— IoC容器初始化 -> Bean定义资源的Resource定位
意思是,AbstractBeanDefinitionReader中ResourceLoader resourceLoader = getResourceLoader(); 得到的是FileSystemXmlApplicationContext(AbstractApplicationContext)。
还记得在AbstractXmlApplicationContext中beanDefinitionReader.setResourceLoader(this); 为Bean读取器设置的资源加载器,正是AbstractApplicationContext,因为继承了DefaultResourceLoader,因此容器本身也是一个资源加载器

将指定位置的Bean定义资源文件解析为IoC容器封装的资源(Resource)的语句

Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
or
Resource resource = resourceLoader.getResource(location);

资源加载器获取要读入的资源(Resource)
实际上,调用了DefaultResourceLoader的getResource方法获取Resource。

DefaultResourceLoader

@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
if (location.startsWith("/")) {
return getResourceByPath(location);
}
//如果是类路径的方式,那需要使用ClassPathResource 来得到bean 文件的资源对象
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}

至此,完成了Bean定义资源的Resource定位

总结一下从创建容器之后各个父类方法调用,不然就有点懵逼了!
【Spring Framework 深入】—— IoC容器初始化 -> Bean定义资源的Resource定位
【Spring Framework 深入】—— IoC容器初始化 -> Bean定义资源的Resource定位

接下来,开始Bean定义资源(已封装成Resource)的载入解析

回到XmlBeanDefinitionReader的loadBeanDefinitions方法

XmlBeanDefinitionReader.java

@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//将读入的XML资源进行特殊编码处理
return loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}

Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}

未完。。。待续
Reference:
Spring Framework Reference Documentation
http://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/htmlsingle/
博客园 —— 牛奶、不加糖
http://www.cnblogs.com/ITtangtang/p/3978349.html#a4