学习优秀框架的源码,是提升个人技术水平必不可少的一个环节。如果只是停留在知道怎么用,但是不懂其中的来龙去脉,在技术的道路上注定走不长远。最近,学习了一段时间的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. request.setAttribute()、session.setAttribute()和request.getParameter()的联系与区别

    1.session.setAttribute()和session.getAttribute()配对使用,作用域是整个会话期间,在所有的页面都使用这些数据的时候使用. 2.request.setAttr ...

  2. mariadb中执行数据库脚本的方法

    为了项目需求,写如下sql数据库脚本: SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for ...

  3. javascript 之 className属性

    Javascript 可以通过className 属性灵活的更改一个标签元素的CSS 类选择器来实现样式的变化. 1.用className 属性修改节点的css类别 代码如下: <html> ...

  4. web项目中引入jquery easyui

    jQuery easyui是一个基于jquery的用户界面插件集合,可以做出各种炫酷页面效果,大中型项目都可以使用些框架,非常好用,而且它有中文网,提供了大量的demo.下面我们看怎么将它引入到项目中 ...

  5. getchar吸收回车

    #include "stdio.h" int main() { int a; char b,c; scanf("%d",&a); c = getchar ...

  6. FZU 1056 扫雷游戏【搜索】

    Accept: 2584    Submit: 6790Time Limit: 1000 mSec    Memory Limit : 32768 KB Problem Description 扫雷是 ...

  7. 集合点-Jmeter-集合点详解

    集合点:简单来理解一下,虽然我们的“性能测试”理解为“多用户并发测试”,但真正的并发是不存在的,为了更真实的实现并发这感念,我们可以在需要压力的地方设置集合点, 还拿那个用户和密码的地方,每到输入用户 ...

  8. Algorithm | hash

    A basic requirement is that the function should provide a uniform distribution of hash values. A non ...

  9. POJ 2057 The Lost House [树状DP]

    题意:一只蜗牛将壳忘在了一棵树的某一个末结点(叶子)上.它想找回自己的壳,但忘记是丢在哪个结点上了,只好从树根开始网上爬,一个结点一个结点地找.在一些结点上居住着毛毛虫,它们会告诉蜗牛该结点以及它的子 ...

  10. 2016集训测试赛(二十)Problem A: Y队列

    Solution 考虑给定一个\(n\), 如何求\(1\)到\(n\)的正整数中有多少在队列中. 不难注意到我们只需要处理质数次方的情况即可, 因为合数次方会被其因数处理到. 同时我们考虑到可能存在 ...