spring源码解析之IOC容器(一)

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

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

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

 1 public interface BeanFactory {
 2     Object getBean(String name) throws BeansException;
 3     <T> T getBean(String name, Class<T> requiredType) throws BeansException;
 4     Object getBean(String name, Object... args) throws BeansException;
 5     <T> T getBean(Class<T> requiredType) throws BeansException;
 6     <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
 7     boolean containsBean(String name);
 8     boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
 9     boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
10     boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
11     boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
12     Class<?> getType(String name) throws NoSuchBeanDefinitionException;
13     String[] getAliases(String name);
14 }

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

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

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

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

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

1 public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
2             throws BeansException {
3
4         super(parent);
5         setConfigLocations(configLocations);
6         if (refresh) {
7             refresh();
8         }
9     }

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

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

 1 @Override
 2     public void refresh() throws BeansException, IllegalStateException {
 3         synchronized (this.startupShutdownMonitor) {
 4             // Prepare this context for refreshing.
 5             //准备要进行刷新的上下文对象
 6             //例如对系统环境进行准备和验证
 7             prepareRefresh();
 8
 9             // Tell the subclass to refresh the internal bean factory.
10             ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
11
12             // Prepare the bean factory for use in this context.
13             prepareBeanFactory(beanFactory);
14
15             try {
16                 // Allows post-processing of the bean factory in context subclasses.
17                 postProcessBeanFactory(beanFactory);
18
19                 // Invoke factory processors registered as beans in the context.
20                 invokeBeanFactoryPostProcessors(beanFactory);
21
22                 // Register bean processors that intercept bean creation.
23                 registerBeanPostProcessors(beanFactory);
24
25                 // Initialize message source for this context.
26                 initMessageSource();
27
28                 // Initialize event multicaster for this context.
29                 initApplicationEventMulticaster();
30
31                 // Initialize other special beans in specific context subclasses.
32                 onRefresh();
33
34                 // Check for listener beans and register them.
35                 registerListeners();
36
37                 // Instantiate all remaining (non-lazy-init) singletons.
38                 finishBeanFactoryInitialization(beanFactory);
39
40                 // Last step: publish corresponding event.
41                 finishRefresh();
42             }
43
44             catch (BeansException ex) {
45                 if (logger.isWarnEnabled()) {
46                     logger.warn("Exception encountered during context initialization - " +
47                             "cancelling refresh attempt: " + ex);
48                 }
49
50                 // Destroy already created singletons to avoid dangling resources.
51                 destroyBeans();
52
53                 // Reset ‘active‘ flag.
54                 cancelRefresh(ex);
55
56                 // Propagate exception to caller.
57                 throw ex;
58             }
59
60             finally {
61                 // Reset common introspection caches in Spring‘s core, since we
62                 // might not ever need metadata for singleton beans anymore...
63                 resetCommonCaches();
64             }
65         }
66     }

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

1 protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
2         refreshBeanFactory();
3         ConfigurableListableBeanFactory beanFactory = getBeanFactory();
4         if (logger.isDebugEnabled()) {
5             logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
6         }
7         return beanFactory;
8     }

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

 1 @Override
 2     protected final void refreshBeanFactory() throws BeansException {
 3         if (hasBeanFactory()) {
 4             destroyBeans();
 5             closeBeanFactory();
 6         }
 7         try {
 8             //创建DefaultListableBeanFactory
 9             DefaultListableBeanFactory beanFactory = createBeanFactory();
10             //指定序列化的id,所以,如果需要反序列化这个BeanFactory,则可以直接根据这个id来进行反序列化
11             beanFactory.setSerializationId(getId());
12             //定制化
13             customizeBeanFactory(beanFactory);
14             //初始化DocumentReader,读取XML
15             loadBeanDefinitions(beanFactory);
16             synchronized (this.beanFactoryMonitor) {
17                 this.beanFactory = beanFactory;
18             }
19         }
20         catch (IOException ex) {
21             throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
22         }
23     }

  这段代码可以看到:

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

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

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

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

 1 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
 2         // Create a new XmlBeanDefinitionReader for the given BeanFactory.
 3         XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
 4
 5         // Configure the bean definition reader with this context‘s
 6         // resource loading environment.
 7         beanDefinitionReader.setEnvironment(this.getEnvironment());
 8         beanDefinitionReader.setResourceLoader(this);
 9         beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
10
11         // Allow a subclass to provide custom initialization of the reader,
12         // then proceed with actually loading the bean definitions.
13         initBeanDefinitionReader(beanDefinitionReader);
14         loadBeanDefinitions(beanDefinitionReader);
15     }

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

 1 protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
 2         Resource[] configResources = getConfigResources();
 3         if (configResources != null) {
 4             reader.loadBeanDefinitions(configResources);
 5         }
 6         String[] configLocations = getConfigLocations();
 7         if (configLocations != null) {
 8             reader.loadBeanDefinitions(configLocations);
 9         }
10     }

  跟到这里,到底是走哪个方法呢?我们再回过头看一下,FileSystemXmlApplicationContext的那个构造器,其中有个setConfigLocations(configLocations)方法,通过这个方法将我们配置的XML文件的路径设置进来了,跟代码,发现它调用的是父类的方法,并将路径赋给了AbstractRefreshableConfigApplicationContext类中的configLocations成员变量,而getConfigLocations()方法也是AbstractRefreshableConfigApplicationContext类中的,它正好获取了configLocations的值,所以configLocations一定不为null,上面方法应该走下面的loadBeanDefinitions()方法。跟进,其代码如下:

1 public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
2         Assert.notNull(locations, "Location array must not be null");
3         int counter = 0;
4         for (String location : locations) {
5             counter += loadBeanDefinitions(location);
6         }
7         return counter;
8     }
1 public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
2         return loadBeanDefinitions(location, null);
3     }
 1 public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
 2         ResourceLoader resourceLoader = getResourceLoader();
 3         if (resourceLoader == null) {
 4             throw new BeanDefinitionStoreException(
 5                     "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
 6         }
 7
 8         if (resourceLoader instanceof ResourcePatternResolver) {
 9             // Resource pattern matching available.
10             try {
11                 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
12                 int loadCount = loadBeanDefinitions(resources);
13                 if (actualResources != null) {
14                     for (Resource resource : resources) {
15                         actualResources.add(resource);
16                     }
17                 }
18                 if (logger.isDebugEnabled()) {
19                     logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
20                 }
21                 return loadCount;
22             }
23             catch (IOException ex) {
24                 throw new BeanDefinitionStoreException(
25                         "Could not resolve bean definition resource pattern [" + location + "]", ex);
26             }
27         }
28         else {
29             // Can only load single resources by absolute URL.
30             Resource resource = resourceLoader.getResource(location);
31             int loadCount = loadBeanDefinitions(resource);
32             if (actualResources != null) {
33                 actualResources.add(resource);
34             }
35             if (logger.isDebugEnabled()) {
36                 logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
37             }
38             return loadCount;
39         }
40     }

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

DefaultListableBeanFactory又是继承了DefaultResourceLoader的,所以,这里的resourceLoader对象是DefaultResourceLoader类型的,所以走下面的逻辑。首先,获取一个resource对象,getResource方法在DefaultResourceLoader中有实现,其代码如下:
 1 public Resource getResource(String location) {
 2         Assert.notNull(location, "Location must not be null");
 3
 4         for (ProtocolResolver protocolResolver : this.protocolResolvers) {
 5             Resource resource = protocolResolver.resolve(location, this);
 6             if (resource != null) {
 7                 return resource;
 8             }
 9         }
10
11         if (location.startsWith("/")) {
12             return getResourceByPath(location);
13         }
14         else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
15             return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
16         }
17         else {
18             try {
19                 // Try to parse the location as a URL...
20                 URL url = new URL(location);
21                 return new UrlResource(url);
22             }
23             catch (MalformedURLException ex) {
24                 // No URL -> resolve as resource path.
25                 //如果都不是,则使用子类重写的方法,例如子类FileSystemXMLApplicationContext中就重写了这个方法
26                 return getResourceByPath(location);
27             }
28         }
29     }

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

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

原文地址:https://www.cnblogs.com/helei123/p/11073373.html

时间: 2024-10-22 05:25:45

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

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

上一篇跟踪了IOC容器对配置文件的定位,现在我们继续跟踪代码,看看IOC容器是怎么加载和注册配置文件中的信息的.开始之前,首先我们先来了解一下IOC容器所使用的数据结构-------BeanDefinition,它是一个上层接口,有很多实现类,分别对应不同的数据载体.我们平时开发的时候,也会定义很多pojo类,来作为获取数据的载体.最常见的就是,从数据库中获取数据之后,使用一个定义的pojo来装载,然后我们就可以在程序中使用这个pojo类来编写各种业务逻辑.同样,IOC容器首先会读取配置的XML

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

上一篇主要是跟踪了IOC容器对bean标签进行解析之后存入Map中的过程,这些bean只是以BeanDefinition为载体单纯的存储起来了,并没有转换成一个个的对象,今天继续进行跟踪,看一看IOC容器是怎样实例化对象的. 我们都使用过以下代码: 1 FileSystemXmlApplicationContext context=new FileSystemXmlApplicationContext("bean.xml"); 2 User user=context.getBean(&

Spring源码阅读:IOC容器的设计与实现(二)——ApplicationContext

上一主题中,了解了IOC容器的基本概念,以及BeanFactory的设计与实现方式,这里就来了解一下ApplicationContext方式的实现. ApplicationContext 在Spring的参考文档中,为啥要推荐使用ApplicationContext?它能给我们的应用带来什么好处呢?作为BeanFactory的实现之一,它又是如何设计的?在SpringMVC中使用的WebApplictionContext\XmlApplicationContext与之有何关联? Applicat

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

前言:在经过前面十二篇文章的分析,对bean的加载流程大致梳理清楚了.因为内容过多,因此需要进行一个小总结. 经过前面十二篇文章的漫长分析,终于将xml配置文件中的bean,转换成我们实际所需要的真正的bean对象. 总结 [spring源码分析]IOC容器初始化(一):主要分析了Spring是如何解析占位符以及BeanFactory的最终实现类DefaultListableBeanFactory. [spring源码分析]IOC容器初始化(二):以loadBeanDefinitions函数为切

【spring源码分析】IOC容器初始化(一)

前言:spring主要就是对bean进行管理,因此IOC容器的初始化过程非常重要,搞清楚其原理不管在实际生产或面试过程中都十分的有用.在[spring源码分析]准备工作中已经搭建好spring的环境,并利用xml配置形式对类进行了实例化.在test代码中有一个非常关键的类ClassPathXmlApplicationContext,在这个类中实现了IOC容器的初始化,因此我们从ClassPathXmlApplicationContext入手开始研究IOC的初始化过程. 1.ClassPathXm

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

前言:在doCreateBean方法中还遗留一个问题没有分析:循环依赖.循环依赖在Spring中是非常重要的一个知识点,因此单独进行分析. 什么是循环依赖 循环依赖就是循环引用,两个或两个以上的bean互相引用对方,最终形成一个闭环.如A依赖B,B依赖C,C依赖A.如下图所示: 循环依赖其实就是一个死循环的过程,在初始化A的时候发现引用了B,则就会去初始化B,然后发现B又引用C,则又去初始化C,在初始化C的时候,再次发现C引用了A,则又去初始化A,这样就处于死循环,除非有终结条件. Spring

Spring源码分析之IOC容器(一)

Spring作为当今风靡世界的Web领域的第一框架,作为一名Java开发程序员是一定要掌握的,除了需要掌握基本的使用之外,更需要掌握其实现原理,因为我们往往在开发的过程中,会出现各种各样的异常问题.而且这样的问题去百度有时候往往也找不到特别有效的解决方法,因为问题的原因非常多而百度的又不准确,这个时候怎么办呢?在熟练掌握Spring代码的情况下,我们可以根据提示的异常信息,去跟到源代码的地方,可以准确的定位到异常的所在,这就犹如debug调试自己的业务逻辑一样轻松了,所以Spring的源代码非常

spring源码研究之IoC容器在web容器中初始化过程

前段时间在公司做了一个项目,项目用了spring框架实现,WEB容器是Tomct 5,虽然说把项目做完了,但是一直对spring的IoC容器在web容器如何启动和起作用的并不清楚.所以就抽时间看一下spring的源代码,借此了解它的原理. 我们知道,对于使用Spring的web应用,无须手动创建Spring容器,而是通过配置文件,声明式的创建Spring容器.因此在Web应用中创建Spring容器有如下两种方式: 1. 直接在web.xml文件中配置创建Spring容器. 2. 利用第三方MVC

【spring源码分析】IOC容器初始化(六)

前言:经过前几篇文章的讲解,我们已经得到了BeanDefinition,接下来将分析Bean的加载. 获取Bean的入口:AbstractApplicationContext#getBean 1 public Object getBean(String name) throws BeansException { 2 // 检测bean工厂是否存活 3 assertBeanFactoryActive(); 4 return getBeanFactory().getBean(name); 5 } 分