学习优秀框架的源码,是提升个人技术水平必不可少的一个环节。如果只是停留在知道怎么用,但是不懂其中的来龙去脉,在技术的道路上注定走不长远。最近,学习了一段时间的spring源码,现在整理出来,以便日后温故知新。

  IOC容器是spring最核心的模块之一,是整个spring体系的基石,spring其他模块中,都需要用到IOC容器的功能。spring框架为我们提供了多种IOC容器,DefaultableBeanFact

ory、FileSystemXmlApplicationContext、ClassPathXmlApplicationContext、XmlWebApplicationContext等。虽然我们平时很少在项目中使用这种硬编码的方式来获取IOC容器,继而获取IOC容器中的bean,但是研究这些IOC容器的源码,对我们理解IOC容器的原理还是很有必要的。BeanFactory这个接口是spring所有IOC容器最上层的接口,getBean()这个方法就是在这个接口中定义的,下面是其中定义的方法:

 public interface BeanFactory {
Object getBean(String name) throws BeansException;
<T> T getBean(String name, 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);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
String[] getAliases(String name);
}

  可以看到其中定义了获取bean的多种方式,和各种对bean的判断,以及获取bean的类型和别名的方法。这个接口是spring框架IOC容器的入口。下面以FileSystemXmlApplicatio

nContext为例,深入源码探究IOC容器的实现原理。IOC容器的初始化过程分为三个阶段:定位、载入和注册。接下来一一进行分析,先从XML的定位开始。

  相信我们大家都使用以下代码获取过IOC容器,获取IOC容器之后,我们就可以得到想要的bean,然后进行操作了:

 FileSystemXmlApplicationContext  context = new FileSystemXmlApplicationContext("bean.xml");

  进入FileSystemXmlApplicationContext这个类,发现它定义了各种构造器,但最终都会调用下面这个构造器:

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

  在分析它的流程之前,有必要给一下它的UML图,上面标注了它的继承体系结构:

  FileSystemXmlApplicationContext的构造器中有个重要的方法refresh(),这是IOC容器的启动方法,在它的父类AbstractXmlApplicationContext中有实现,其代码如下:

 @Override
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();
}
}
}

  进入obtainFreshBeanFactory()方法,其代码如下:

 protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}

  继续跟,进入refreshBeanFactory()方法,在父类AbstractRefreshableApplicationContext中有实现,其代码如下:

 @Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//创建DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
//指定序列化的id,所以,如果需要反序列化这个BeanFactory,则可以直接根据这个id来进行反序列化
beanFactory.setSerializationId(getId());
//定制化
customizeBeanFactory(beanFactory);
//初始化DocumentReader,读取XML
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}

  这段代码可以看到:

  1、首先,创建了一个DefaultListableBeanFactory的IOC容器;

  2、对容器进行了一些设置;

  3、调用loadBeanDefinitions()方法对XML文件进行定位和加载。

  所以,进入loadBeanDefinitions()方法继续探索,在类AbstractXmlApplicationContext中有实现,它是FileSystemXmlApplicationContext的父类,其代码如下:

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

  这个方法中,使用XmlBeanDefinitionReader类来加载XML文件,最后经过一系列的设置,调用了loadBeanDefinitions(beanDefinitionReader)这个方法,进入:

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

  跟到这里,到底是走哪个方法呢?我们再回过头看一下,FileSystemXmlApplicationContext的那个构造器,其中有个setConfigLocations(configLocations)方法,通过这个方法将我们配置的XML文件的路径设置进来了,跟代码,发现它调用的是父类的方法,并将路径赋给了AbstractRefreshableConfigApplicationContext类中的configLocations成员变量,而getConfigLocations()方法也是AbstractRefreshableConfigApplicationContext类中的,它正好获取了configLocations的值,所以configLocations一定不为null,上面方法应该走下面的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;
}
 public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
 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");
} if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
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;
}
}

  这里先得到一个ResourceLoader对象。在类AbstractXmlApplicationContext中的loadBeanDefinitions()方法中有beanDefinitionReader.setResourceLoader(this)这段代码,而

DefaultListableBeanFactory又是继承了DefaultResourceLoader的,所以,这里的resourceLoader对象是DefaultResourceLoader类型的,所以走下面的逻辑。首先,获取一个resource
对象,getResource方法在DefaultResourceLoader中有实现,其代码如下:
 public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null"); for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
} if (location.startsWith("/")) {
return getResourceByPath(location);
}
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.
//如果都不是,则使用子类重写的方法,例如子类FileSystemXMLApplicationContext中就重写了这个方法
return getResourceByPath(location);
}
}
}

  根据不同的情况,生成一个ResourceLoader对象,这样就完成了对配置的xml文件的定位。

  经过这么一长条的跟踪,终于完成了XML资源的定位工作。spring的继承体系特别深,刚开始的时候感觉特别绕,但是多跟着代码跟几遍,基本就清晰了,关键是要有耐心。在上面的分析中,我们发现,spring使用了很多的模板方法,比如getResource方法,还有就是单一职责原则,每个类很清晰,每个方法中都是一个一个方法的调用,而不是代码的堆砌,这点是值得我们平时好好学习和运用的。

spring源码解析之IOC容器(一)的更多相关文章

  1. spring源码解析之IOC容器(二)------加载和注册

    上一篇跟踪了IOC容器对配置文件的定位,现在我们继续跟踪代码,看看IOC容器是怎么加载和注册配置文件中的信息的.开始之前,首先我们先来了解一下IOC容器所使用的数据结构-------BeanDefin ...

  2. spring源码解析之IOC容器(三)——依赖注入

    上一篇主要是跟踪了IOC容器对bean标签进行解析之后存入Map中的过程,这些bean只是以BeanDefinition为载体单纯的存储起来了,并没有转换成一个个的对象,今天继续进行跟踪,看一看IOC ...

  3. spring源码解析之IOC容器(四)——属性注入

    上一篇跟踪了bean的创建过程,接下来,我们继续跟踪bean的属性填充的过程.先回到doCreateBean方法,代码如下: protected Object doCreateBean(final S ...

  4. 【spring源码分析】IOC容器初始化(总结)

    前言:在经过前面十二篇文章的分析,对bean的加载流程大致梳理清楚了.因为内容过多,因此需要进行一个小总结. 经过前面十二篇文章的漫长分析,终于将xml配置文件中的bean,转换成我们实际所需要的真正 ...

  5. 【spring源码分析】IOC容器初始化(二)

    前言:在[spring源码分析]IOC容器初始化(一)文末中已经提出loadBeanDefinitions(DefaultListableBeanFactory)的重要性,本文将以此为切入点继续分析. ...

  6. 【spring源码分析】IOC容器初始化(三)

    前言:在[spring源码分析]IOC容器初始化(二)中已经得到了XML配置文件的Document实例,下面分析bean的注册过程. XmlBeanDefinitionReader#registerB ...

  7. 【spring源码分析】IOC容器初始化(四)

    前言:在[spring源码分析]IOC容器初始化(三)中已经分析了BeanDefinition注册之前的一些准备工作,下面将进入BeanDefinition注册的核心流程. //DefaultBean ...

  8. 【spring源码分析】IOC容器初始化(十)

    前言:前文[spring源码分析]IOC容器初始化(九)中分析了AbstractAutowireCapableBeanFactory#createBeanInstance方法中通过工厂方法创建bean ...

  9. 转 Spring源码剖析——核心IOC容器原理

    Spring源码剖析——核心IOC容器原理 2016年08月05日 15:06:16 阅读数:8312 标签: spring源码ioc编程bean 更多 个人分类: Java https://blog ...

随机推荐

  1. 【02】 Vue 之 数据绑定

    2.1. 什么是双向绑定? Vue框架很核心的功能就是双向的数据绑定. 双向是指:HTML标签数据 绑定到 Vue对象,另外反方向数据也是绑定的.通俗点说就是,Vue对象的改变会直接影响到HTML的标 ...

  2. (转载--修改)使用Xcode9的Instruments检测解决iOS内存泄露

    作为一名iOS开发攻城狮,在苹果没有出ARC(自动内存管理机制)时,我们几乎有一半的开发时间都耗费在这么管理内存上.后来苹果很人性的出了ARC,虽然在很大程度上,帮助我们开发者节省了精力和时间.但是我 ...

  3. cf 235C 后缀自动机

    题目大意 给定字符串\(S\)与\(n<=10^5\)个串\(x_1,x_2...x_n\)(总长\(\le10^6\)) 对于每个\(x_i\),输出有多少个\(S\)的子串与\(x_i\)循 ...

  4. 全球主要城市经纬度api

    原文发布时间为:2011-06-23 -- 来源于本人的百度文章 [由搬家工具导入] http://www.google.com/ig/cities?country=cn http://www.goo ...

  5. practical system design with mef & mef[ trans from arup.codeplex.com/]

    Practical System Design using MEF MVVM RX MOQ Unit Tests in WPF Posted on May 21, 2015 by Arup Baner ...

  6. [LeetCode] Longest Common Prefix 字符串公有前序

    Write a function to find the longest common prefix string amongst an array of strings. Hide Tags Str ...

  7. 【Visual Studio】Visual Studio 2010 "LNK1123: 转换到 COFF 期间失败: 文件无效或损坏" 的解决方法

    1.将 项目|项目属性|配置属性|连接器|清单文件|嵌入清单 “是”改为“否”. 2.找到 C:\Windows\winsxs\x86_netfx-cvtres_for_vc_and_vb_b03f5 ...

  8. linux中的strip命令简介------给文件脱衣服【转】

    转自:http://blog.csdn.net/stpeace/article/details/47090255 版权声明:本文为博主原创文章,转载时请务必注明本文地址, 禁止用于任何商业用途, 否则 ...

  9. cisco packet 实验教程(一)

    01. 开篇:组建小型局域网 实验任务 1.利用一台型号为2960的交换机将2pc机互连组建一个小型局域网: 2.分别设置pc机的ip地址: 3.验证pc机间可以互通. 实验设备 Switch_296 ...

  10. Codeforces 716C. Plus and Square Root-推公式的数学题

    http://codeforces.com/problemset/problem/716/C codeforces716C. Plus and Square Root 这个题就是推,会推出来规律,发现 ...