IOC容器的初始化过程主要包括BeanDefinition的Resource定位、载入和注册。在实际项目中我们基本上操作的都是ApplicationContex的实现,我们比较熟悉的ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、XmlWebapplicationContext等。ApplicationContext的具体继承体系如下图所示:

  

  其实,不管是XmlWebApplicationContext还是ClasspathXmlApplicationContext 他们的区别只是Bean的资源信息来源不一样而已,最终都会解析为统一数据结构BeanDefinition。

下面我们源码的解析就从高富帅的ClassPathXmlApplicationContext开始。

        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("classpath*:test.xml");

构造方法:

/**
* Create a new ClassPathXmlApplicationContext, loading the definitions
* from the given XML file and automatically refreshing the context.
* @param configLocation resource location
* @throws BeansException if context creation failed
*/
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}

最终调用构造方法:

/**
* Create a new ClassPathXmlApplicationContext with the given parent,
* loading the definitions from the given XML files.
* @param configLocations array of resource locations
* @param refresh whether to automatically refresh the context,
* loading all bean definitions and creating all singletons.
* Alternatively, call refresh manually after further configuring the context.
* @param parent the parent context
* @throws BeansException if context creation failed
* @see #refresh()
*/
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException { super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}

1.设置父级上下文,最终是给AbstractApplicationContext的parent属性赋值,AbstractApplicationContext是ApplicationContext最顶层的实现类。

2.设置XML文件的位置,调用了AbstractRefreshableConfigApplicationContext的setConfigLocations方法

/**
* Set the config locations for this application context.
* <p>If not set, the implementation may use a default as appropriate.
*/
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++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}

3.刷新容器,调用了AbstractApplicationContext的refresh方法,这是一个模板方法,具体的操作都是有子类去实现的。

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.
//甚至beanFacotry的后置处理
postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context.
//调用BeanFactory的后置处理器,这些后置处理是在Bean定义中想容器注册的
invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation.
//注册Bean的后置处理器,在Bean的创建过程中调用
registerBeanPostProcessors(beanFactory); // Initialize message source for this context.
//对上下文中的消息源进行初始化
initMessageSource(); // Initialize event multicaster for this context.
//初始化上下文的事件
initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses.
//初始化其他特殊的Bean
onRefresh(); // Check for listener beans and register them.
//向容器注册监听Bean
registerListeners(); // Instantiate all remaining (non-lazy-init) singletons.
//实例化所有非延迟加载的Bean
finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event.
//发布容器事件,结束refresh过程
finishRefresh();
} catch (BeansException ex) {
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;
}
}
}
prepareRefresh():主要是设置启动时间、状态等等;
我们着重看一下刷新容器的obtainFreshBeanFactory()方法:
/**
* Tell the subclass to refresh the internal bean factory.
* @return the fresh BeanFactory instance
* @see #refreshBeanFactory()
* @see #getBeanFactory()
*/
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//销毁已有容器,重新创建容器并加载Bean
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
AbstractApplicationConetxt的refreshBeanFactory()方法是一个抽象方法,是由它的子类AbstractRefreshableApplicationContext实现的,从类的命名上可以看出这个类主要就是进行容器Refresh用的。
/**
* This implementation performs an actual refresh of this context's underlying
* bean factory, shutting down the previous bean factory (if any) and
* initializing a fresh bean factory for the next phase of the context's lifecycle.
*/
@Override
protected final void refreshBeanFactory() throws BeansException {
//如果容器已经存在则销毁容器中的bean并关闭容器
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//创建beanFacotry
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
//根据bean定义的方式(XML、注解等)不同,由子类选择相应的BeanDefinitionReader去解析
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}

第一步: 这个方法会判断如果已存在容器,则先销毁所有的Bean并且关闭容器,这也是为了保证容器的唯一性。

第二步:createBeanFactory()创建了一个DefaultListableBeanFactory,这个类是BeanFacotry最高级的实现,有了它就有个容器最基本的功能了。

/**
* Create an internal bean factory for this context.
* Called for each {@link #refresh()} attempt.
* <p>The default implementation creates a
* {@link org.springframework.beans.factory.support.DefaultListableBeanFactory}
* with the {@linkplain #getInternalParentBeanFactory() internal bean factory} of this
* context's parent as parent bean factory. Can be overridden in subclasses,
* for example to customize DefaultListableBeanFactory's settings.
* @return the bean factory for this context
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowBeanDefinitionOverriding
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowEagerClassLoading
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowCircularReferences
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowRawInjectionDespiteWrapping
*/
protected DefaultListableBeanFactory createBeanFactory() {
//新建一个DefaultListableBeanFactory
return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}

DefaultListableBeanFactory的继承关系:

我们看到DefaultListableBeanFactory实现了BeanDefinitionRegistry接口,也就是说最终BeanDefinition的注册工作是由它和它的子类来完成的。

第三步:loadBeanDefinitions(beanFactory),这个方法也是一个抽象方法。因为Bean定义方式不同(XML、注解等),会有多个子类分别去实现具体的解析。

此处,调用的是AbstractXmlApplicationContext的实现:

/**
* Loads the bean definitions via an XmlBeanDefinitionReader.
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
* @see #initBeanDefinitionReader
* @see #loadBeanDefinitions
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
//ApplicationContext继承了ResourceLoader接口,所以this是可以直接使用的
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);
//委派模式,具体事情委派给beanDefinitionReader去做
loadBeanDefinitions(beanDefinitionReader);
}

进入loadBeanDefinitions(XmlBeanDefinitionReader reader):

/**
* Load the bean definitions with the given XmlBeanDefinitionReader.
* <p>The lifecycle of the bean factory is handled by the {@link #refreshBeanFactory}
* method; hence this method is just supposed to load and/or register bean definitions.
* @param reader the XmlBeanDefinitionReader to use
* @throws BeansException in case of bean registration errors
* @throws IOException if the required XML document isn't found
* @see #refreshBeanFactory
* @see #getConfigLocations
* @see #getResources
* @see #getResourcePatternResolver
*/
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}

该方法调用了XmlBeanDefinitionReader父类AbstractBeanDefinitionReader的loadBeanDefinitions方法:

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

l循环加载location并返回加载个数,最终调用了本类的loadBeanDefinitions(String location, Set<Resource> actualResources)方法,actualResources为null:

/**
* Load bean definitions from the specified resource location.
* <p>The location can also be a location pattern, provided that the
* ResourceLoader of this bean definition reader is a ResourcePatternResolver.
* @param location the resource location, to be loaded with the ResourceLoader
* (or ResourcePatternResolver) of this bean definition reader
* @param actualResources a Set to be filled with the actual Resource objects
* that have been resolved during the loading process. May be {@code null}
* to indicate that the caller is not interested in those Resource objects.
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors
* @see #getResourceLoader()
* @see #loadBeanDefinitions(org.springframework.core.io.Resource)
* @see #loadBeanDefinitions(org.springframework.core.io.Resource[])
*/
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
//resourceLoader是ClasspathXmlApplicationContext,ApplicationContext接口本身继承了ResourcePatternResolver接口
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
//location转为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;
}
}
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location)将location转成了Resource[],这一步完成了资源的定位工作。
它调用了PathMatchingResourcePatternResolver的getResources方法:
 public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
//是否以classpath*:开头
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// a class path resource (multiple resources for same name possible)
//是否为Ant-style路径
//? 匹配任何单字符
//* 匹配0或者任意数量的字符
//** 匹配0或者更多的目录
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// a class path resource pattern
return findPathMatchingResources(locationPattern);
}
else {
// all class path resources with the given name
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
// Only look for a pattern after a prefix here
// (to not get fooled by a pattern symbol in a strange prefix).
int prefixEnd = locationPattern.indexOf(":") + 1;
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
}
else {
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}

根据location写法,解析方式也不同:

1、前缀为classpath*

  1)文件路径路径中包含*和?

    调用findPathMatchingResources方法

  2)文件路径中不含*和?

    调用findAllClassPathResources方法

2.、前缀为classpath

  1)文件路径路径中包含*和?

  调用findPathMatchingResources方法
 2)文件路径中不含*和?
  调用DefaultResourceLoader的getResource方法new一个ClasspathResource并返回,如果资源文件根本就不存在,此处也不会校验。
findPathMatchingResources和findAllClassPathResources具体都干了什么呢?
先看一下findAllClassPathResources:
/**
* Find all class location resources with the given location via the ClassLoader.
* @param location the absolute path within the classpath
* @return the result as Resource array
* @throws IOException in case of I/O errors
* @see java.lang.ClassLoader#getResources
* @see #convertClassLoaderURL
*/
protected Resource[] findAllClassPathResources(String location) throws IOException {
String path = location;
if (path.startsWith("/")) {
path = path.substring(1);
}
ClassLoader cl = getClassLoader();
Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
Set<Resource> result = new LinkedHashSet<Resource>(16);
while (resourceUrls.hasMoreElements()) {
URL url = resourceUrls.nextElement();
result.add(convertClassLoaderURL(url));
}
return result.toArray(new Resource[result.size()]);
}
   protected Resource convertClassLoaderURL(URL url) {
return new UrlResource(url);
}
这个方法很简单,根据具体的location通过classLoader的getResources方法返回RUL集合,根据URL创建UrlResource并返回UrlResource的集合。
再来看一下findPathMatchingResources方法:
/**
* Find all resources that match the given location pattern via the
* Ant-style PathMatcher. Supports resources in jar files and zip files
* and in the file system.
* @param locationPattern the location pattern to match
* @return the result as Resource array
* @throws IOException in case of I/O errors
* @see #doFindPathMatchingJarResources
* @see #doFindPathMatchingFileResources
* @see org.springframework.util.PathMatcher
*/
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
String rootDirPath = determineRootDir(locationPattern);
String subPattern = locationPattern.substring(rootDirPath.length());
Resource[] rootDirResources = getResources(rootDirPath);
Set<Resource> result = new LinkedHashSet<Resource>(16);
for (Resource rootDirResource : rootDirResources) {
rootDirResource = resolveRootDirResource(rootDirResource);
if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
}
else if (isJarResource(rootDirResource)) {
result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
}
else {
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
if (logger.isDebugEnabled()) {
logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
}
return result.toArray(new Resource[result.size()]);
}
1.String rootDirPath = determineRootDir(locationPattern),获取location前缀classpath*/classpath
2.Resource[] rootDirResources = getResources(rootDirPath),调用的上面讲到的getResources方法,返回classpath根路径的Resource[],如果是classpath会返回一个Resource,
如果是classpath*会放回所有的classpath路径。
3.遍历根路径Resource[],doFindPathMatchingFileResources方法就是获取给定路径下的所有文件,根据指定的文件名test*.xml去模糊匹配,返回的是FileSystemResource。所以location为classpath:test*.xml可能会找不到文件。

												

Spring源码解析(二)BeanDefinition的Resource定位的更多相关文章

  1. Spring源码解析二:IOC容器初始化过程详解

    IOC容器初始化分为三个步骤,分别是: 1.Resource定位,即BeanDefinition的资源定位. 2.BeanDefinition的载入 3.向IOC容器注册BeanDefinition ...

  2. spring源码解析(二) 结合源码聊聊FactoryBean

    一.什么是FactoryBean FactoryBean是由spring提供的用来让用户可以自定bean创建的接口:实现该接口可以让你的bean不用经过spring复杂的bean创建过程,但同时也能做 ...

  3. Spring源码解析之ConfigurationClassPostProcessor(二)

    上一个章节,笔者向大家介绍了spring是如何来过滤配置类的,下面我们来看看在过滤出配置类后,spring是如何来解析配置类的.首先过滤出来的配置类会存放在configCandidates列表, 在代 ...

  4. 剑指Spring源码(二)

    这是春节后的第一篇博客,我在构思这篇博客的时候,一度想放弃,想想要不要换个东西写,因为毕竟个人水平有限,Spring源码实在博大精深,不是我这个菜的抠脚的菜鸡可以驾驭的,怕误人子弟,还有就是源码分析类 ...

  5. spring 源码解析

    1. [文件] spring源码.txt ~ 15B     下载(167) ? 1 springн┤┬вио╬Ш: 2. [文件] spring源码分析之AOP.txt ~ 15KB     下载( ...

  6. Spring源码解析-ioc容器的设计

    Spring源码解析-ioc容器的设计 1 IoC容器系列的设计:BeanFactory和ApplicatioContext 在Spring容器中,主要分为两个主要的容器系列,一个是实现BeanFac ...

  7. Spring源码解析系列汇总

    相信我,你会收藏这篇文章的 本篇文章是这段时间撸出来的Spring源码解析系列文章的汇总,总共包含以下专题.喜欢的同学可以收藏起来以备不时之需 SpringIOC源码解析(上) 本篇文章搭建了IOC源 ...

  8. Spring源码解析之BeanFactoryPostProcessor(三)

    在上一章中笔者介绍了refresh()的<1>处是如何获取beanFactory对象,下面我们要来学习refresh()方法的<2>处是如何调用invokeBeanFactor ...

  9. Spring源码分析——资源访问利器Resource之实现类分析

    今天来分析Spring的资源接口Resource的各个实现类.关于它的接口和抽象类,参见上一篇博文——Spring源码分析——资源访问利器Resource之接口和抽象类分析 一.文件系统资源 File ...

  10. Spring源码解析——循环依赖的解决方案

    一.前言 承接<Spring源码解析--创建bean>.<Spring源码解析--创建bean的实例>,我们今天接着聊聊,循环依赖的解决方案,即创建bean的ObjectFac ...

随机推荐

  1. 讨论CSS中的各类居中方式

    今天主要谈一谈CSS中的各种居中的办法. 首先是水平居中,最简单的办法当然就是 margin:0 auto; 也就是将margin-left和margin-right属性设置为auto,从而达到水平居 ...

  2. Invalidate()函数

    Invalidate( ) :使整个窗口客户区无效, 并进行更新显示的函数 介绍 void Invalidate( BOOL bErase = TRUE ); 参数: bErase 决定了是否要在WM ...

  3. Windows路径操作API函数学习

    前言 在VC++开发过程中,经常需要用到一些路径操作,比如拼需要的文件路径,搜索路径中的内容等等.Windows提供了一套关于路径操作的API帮助我们更好的执行这些操作. 路径截断与合并API Pat ...

  4. hbase shell中执行list命令报错:ERROR: org.apache.hadoop.hbase.PleaseHoldException: Master is initializing

    问题描述: 今天在测试环境中,搭建hbase环境,执行list命令之后,报错: hbase(main):001:0> list TABLE ERROR: org.apache.hadoop.hb ...

  5. Makefile--伪目标 (三)

    原创博文,转载请标明出处--周学伟http://www.cnblogs.com/zxouxuewei/ 一般情况下,Makefile都会有一个clean目标,用于清除编译过程中产生的二进制文件.我们在 ...

  6. C# 温故而知新:Stream篇(二)

    TextReader 和StreamReader 目录: 为什么要介绍 TextReader? TextReader的常用属性和方法 TextReader 示例 从StreamReader想到多态 简 ...

  7. JBPM4.4_jBPM4.4的流程定义语言(设计流程)

    1. jBPM4.4的流程定义语言(设计流程) 1.1. process(流程) 是.jpdl.xml的根元素,可以指定的属性有: 属性名 作用说明 name 流程定义的名称,用于显示. key 流程 ...

  8. The content of element type "struts" must match "((package|include|bean|constant)*,unknown-handler-s

    <struts> <!-- 配置为开发模式 -->     <constant name="struts.devMode" value="t ...

  9. thinkjs之页面跳转-同步异步

    对于刚入手thinkjs项目的新手来说,时常会犯的一个错误就是“混用”各种代码逻辑,比如:我们经常在做后台管理系统的时候用到的登录框, , 其实它原本是有一个路由专门存放自己的代码逻辑,而在点击提交按 ...

  10. 如何使用css影藏滚动条

    1.单纯的一句代码: div ::-webkit-scrollbar {width: 0px;}//或者display:none 但是这代码最大的弊端就是只能在webkit内核的浏览器上进行显示,无法 ...