码迷,mamicode.com
首页 > 编程语言 > 详细

【Spring源码分析】Bean加载流程概览

时间:2017-02-03 13:24:31      阅读:326      评论:0      收藏:0      [点我收藏+]

标签:lap   proc   代码实现   获取   gis   color   sof   ati   技术   

代码入口

之前写文章都会啰啰嗦嗦一大堆再开始,进入【Spring源码分析】这个板块就直接切入正题了。

很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事的都是Java Web的工作,对于程序员来说,一个Web项目用到Spring,只是配置一下配置文件而已,Spring的加载过程相对是不太透明的,不太好去找加载的代码入口。

下面有很简单的一段代码可以作为Spring代码加载的入口:

 1 ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
 2 ac.getBean(XXX.class);

ClassPathXmlApplicationContext用于加载CLASSPATH下的Spring配置文件,可以看到,第二行就已经可以获取到Bean的实例了,那么必然第一行就已经完成了对所有Bean实例的加载,因此可以通过ClassPathXmlApplicationContext作为入口。为了后面便于代码阅读,先给出一下ClassPathXmlApplicationContext这个类的继承关系:技术分享

大致的继承关系是如上图所示的,由于版面的关系,没有继续画下去了,左下角的ApplicationContext应当还有一层继承关系,比较关键的一点是它是BeanFactory的子接口。

最后声明一下,本文使用的Spring版本为3.0.7,比较老,使用这个版本纯粹是因为公司使用而已。

 

ClassPathXmlApplicationContext构造函数

看下ClassPathXmlApplicationContext的构造函数:

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

从第二段代码看,总共就做了三件事:

  1、super(parent)

    没什么太大的作用,设置一下父级ApplicationContext,这里是null

  2、setConfigLocations(configLocations)

    代码就不贴了,一看就知道,里面做了两件事情:

    (1)将指定的Spring配置文件的路径存储到本地

    (2)解析Spring配置文件路径中的${PlaceHolder}占位符,替换为系统变量中PlaceHolder对应的Value值,System本身就自带一些系统变量比如class.path、os.name、user.dir等,也可以通过System.setProperty()方法设置自己需要的系统变量

  3、refresh()

    这个就是整个Spring Bean加载的核心了,它是ClassPathXmlApplicationContext的父类AbstractApplicationContext的一个方法,顾名思义,用于刷新整个Spring上下文信息,定义了整个Spring上下文加载的流程。

 

refresh方法

上面已经说了,refresh()方法是整个Spring Bean加载的核心,因此看一下整个refresh()方法的定义:

 1 public void refresh() throws BeansException, IllegalStateException {
 2         synchronized (this.startupShutdownMonitor) {
 3             // Prepare this context for refreshing.
 4             prepareRefresh();
 5 
 6             // Tell the subclass to refresh the internal bean factory.
 7             ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
 8 
 9             // Prepare the bean factory for use in this context.
10             prepareBeanFactory(beanFactory);
11 
12             try {
13                 // Allows post-processing of the bean factory in context subclasses.
14                 postProcessBeanFactory(beanFactory);
15 
16                 // Invoke factory processors registered as beans in the context.
17                 invokeBeanFactoryPostProcessors(beanFactory);
18 
19                 // Register bean processors that intercept bean creation.
20                 registerBeanPostProcessors(beanFactory);
21 
22                 // Initialize message source for this context.
23                 initMessageSource();
24 
25                 // Initialize event multicaster for this context.
26                 initApplicationEventMulticaster();
27 
28                 // Initialize other special beans in specific context subclasses.
29                 onRefresh();
30 
31                 // Check for listener beans and register them.
32                 registerListeners();
33 
34                 // Instantiate all remaining (non-lazy-init) singletons.
35                 finishBeanFactoryInitialization(beanFactory);
36 
37                 // Last step: publish corresponding event.
38                 finishRefresh();
39             }
40 
41             catch (BeansException ex) {
42                 // Destroy already created singletons to avoid dangling resources.
43                 destroyBeans();
44 
45                 // Reset ‘active‘ flag.
46                 cancelRefresh(ex);
47 
48                 // Propagate exception to caller.
49                 throw ex;
50             }
51         }
52     }

每个子方法的功能之后一点一点再分析,首先refresh()方法有几点是值得我们学习的:

  1、方法是加锁的,这么做的原因是避免多线程同时刷新Spring上下文

  2、尽管加锁可以看到是针对整个方法体的,但是没有在方法前加synchronized关键字,而使用了对象锁startUpShutdownMonitor,这样做有两个好处:

    (1)refresh()方法和close()方法都使用了startUpShutdownMonitor对象锁加锁,这就保证了在调用refresh()方法的时候无法调用close()方法,反之亦然,避免了冲突

    (2)另外一个好处不在这个方法中体现,但是提一下,使用对象锁可以减小了同步的范围,只对不能并发的代码块进行加锁,提高了整体代码运行的效率

  3、方法里面使用了每个子方法定义了整个refresh()方法的流程,使得整个方法流程清晰易懂。这点是非常值得学习的,一个方法里面几十行甚至上百行代码写在一起,在我看来会有三个显著的问题:

    (1)扩展性降低。反过来讲,假使把流程定义为方法,子类可以继承父类,可以根据需要重写方法

    (2)代码可读性差。很简单的道理,看代码的人是愿意看一段500行的代码,还是愿意看10段50行的代码?

    (3)代码可维护性差。这点和上面的类似但又有不同,可维护性差的意思是,一段几百行的代码,功能点不明确,不易后人修改,可能会导致“牵一发而动全身”

 

prepareRefresh方法

下面挨个看refresh方法中的子方法,首先是prepareRefresh方法,看一下源码:

 1 /**
 2  * Prepare this context for refreshing, setting its startup date and
 3  * active flag.
 4  */
 5 protected void prepareRefresh() {
 6     this.startupDate = System.currentTimeMillis();
 7         synchronized (this.activeMonitor) {
 8         this.active = true;
 9     }
10 
11     if (logger.isInfoEnabled()) {
12         logger.info("Refreshing " + this);
13     }
14 }

这个方法功能比较简单,顾名思义,准备刷新Spring上下文,其功能注释上写了:

1、设置一下刷新Spring上下文的开始时间

2、将active标识位设置为true

另外可以注意一下12行这句日志,这句日志打印了真正加载Spring上下文的Java类。

 

obtainFreshBeanFactory方法

obtainFreshBeanFactory方法的作用是获取刷新Spring上下文的Bean工厂,其代码实现为:

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和GenericApplicationContext这两个子类实现了这个方法,看一下上面ClassPathXmlApplicationContext的继承关系图即知,调用的应当是AbstractRefreshableApplicationContext中实现的refreshBeanFactory,其源码为:

 1 protected final void refreshBeanFactory() throws BeansException {
 2     if (hasBeanFactory()) {
 3         destroyBeans();
 4         closeBeanFactory();
 5     }
 6     try {
 7         DefaultListableBeanFactory beanFactory = createBeanFactory();
 8         beanFactory.setSerializationId(getId());
 9         customizeBeanFactory(beanFactory);
10         loadBeanDefinitions(beanFactory);
11         synchronized (this.beanFactoryMonitor) {
12             this.beanFactory = beanFactory;
13         }
14     }
15     catch (IOException ex) {
16         throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
17     }
18 }

这段代码的核心是第7行,这行点出了DefaultListableBeanFactory这个类,这个类是构造Bean的核心类,这个类的功能会在这一篇文章中详细解读,首先给出DefaultListableBeanFactory的继承关系图:技术分享

AbstractAutowireCapableBeanFactory这个类的继承层次比较深,版面有限,就没有继续画下去了,本图基本上清楚地展示了DefaultListableBeanFactory的层析结构。

 

XML文件解析

另外一个核心是第10行,loadBeanDefinitions(beanFactory)方法,为什么我们配置的XML文件最后能转成Java Bean,首先就是由这个方法处理的。该方法最终的目的是将XML文件进行解析,以Key-Value的形式,Key表示BeanName,Value为BeanDefinition,最终存入DefaultListableBeanFactory中:

1 /** Map of bean definition objects, keyed by bean name */
2 private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
3 
4 /** List of bean definition names, in registration order */
5 private final List<String> beanDefinitionNames = new ArrayList<String>();

最终DefaultListableBeanFactory会先遍历beanDefinitionNames,从beanDefinitionMap中拿到对应的BeanDefinition,最终转为具体的Bean对象。BeanDefinition本身是一个接口,AbstractBeanDefinition这个抽象类存储了Bean的属性,看一下AbstractBeanDefinition这个抽象类的定义:

技术分享

这个类的属性与方法很多,这里就列举了一些最主要的方法和属性,可以看到包含了bean标签中的所有属性,之后就是根据AbstractBeanDefinition中的属性值构造出对应的Bean对象。

Spring没有直接拿到XML中的bean定义就直接转为具体的Bean对象,就是给Spring开发者留下了扩展点,比如之前BeanPostProcessor,在最后一部分会简单提及。接着看一下XML是如何转为Bean的,首先在AbstractXmlApplicationContext中将DefaultListableBeanFactory转换为XmlBeanDefinitionReader:

 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.setResourceLoader(this);
 8     beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
 9 
10     // Allow a subclass to provide custom initialization of the reader,
11     // then proceed with actually loading the bean definitions.
12     initBeanDefinitionReader(beanDefinitionReader);
13     loadBeanDefinitions(beanDefinitionReader);
14 }

XmlBeanDefinitionReader顾名思义,一个XML文件中读取Bean定义的工具,然后追踪13行的代码,先追踪到DefaultBeanDefinitionDocumentReader的parseDefaultElement方法:

 1 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
 2     if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
 3         importBeanDefinitionResource(ele);
 4     }
 5     else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
 6         processAliasRegistration(ele);
 7     }
 8     else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
 9         processBeanDefinition(ele, delegate);
10     }
11 }

XML文件的节点import、alias、bean分别有自己对应的方法去处理,以最常见的Bean为例,即第9行:

 1 protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
 2     BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
 3     if (bdHolder != null) {
 4         bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
 5         try {
 6             // Register the final decorated instance.
 7             BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
 8         }
 9         catch (BeanDefinitionStoreException ex) {
10             getReaderContext().error("Failed to register bean definition with name ‘" +
11                     bdHolder.getBeanName() + "‘", ele, ex);
12         }
13         // Send registration event.
14         getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
15     }
16 }

核心的解析bean节点的代码为第2行,看下是怎么做的:

 1 public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
 2     String id = ele.getAttribute(ID_ATTRIBUTE);
 3     String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
 4 
 5     List<String> aliases = new ArrayList<String>();
 6     if (StringUtils.hasLength(nameAttr)) {
 7         String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, BEAN_NAME_DELIMITERS);
 8         aliases.addAll(Arrays.asList(nameArr));
 9     }
10 
11     String beanName = id;
12     if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
13         beanName = aliases.remove(0);
14         if (logger.isDebugEnabled()) {
15             logger.debug("No XML ‘id‘ specified - using ‘" + beanName +
16                     "‘ as bean name and " + aliases + " as aliases");
17         }
18     }
19 
20     if (containingBean == null) {
21         checkNameUniqueness(beanName, aliases, ele);
22     }
23 
24     AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
25     if (beanDefinition != null) {
26         if (!StringUtils.hasText(beanName)) {
27             try {
28                 if (containingBean != null) {
29                     beanName = BeanDefinitionReaderUtils.generateBeanName(
30                             beanDefinition, this.readerContext.getRegistry(), true);
31                 }
32                 else {
33                     beanName = this.readerContext.generateBeanName(beanDefinition);
34                     // Register an alias for the plain bean class name, if still possible,
35                     // if the generator returned the class name plus a suffix.
36                     // This is expected for Spring 1.2/2.0 backwards compatibility.
37                     String beanClassName = beanDefinition.getBeanClassName();
38                     if (beanClassName != null &&
39                                 beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
40                                 !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
41                         aliases.add(beanClassName);
42                     }
43                 }
44                 if (logger.isDebugEnabled()) {
45                     logger.debug("Neither XML ‘id‘ nor ‘name‘ specified - " +
46                             "using generated bean name [" + beanName + "]");
47                 }
48             }
49             catch (Exception ex) {
50                 error(ex.getMessage(), ele);
51                 return null;
52             }
53         }
54         String[] aliasesArray = StringUtils.toStringArray(aliases);
55         return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
56     }
57 
58     return null;
59 }

总结一下代码逻辑:

(1)第2行和第3行,获取id属性和name属性

(2)第5行~第9行,如果填写了name属性的话,将name属性以",;",分割出来的字符串全部认为这个bean的别名,这里我们可以学到Spring的StringUtils的tokenizeToStringArray方法,可以将字符串按照指定分割符分割为字符串数组

(3)第11行~第18行,默认beanName为id属性,如果bean有配置别名(即上面的name属性的话),以name属性的第一个值作为beanName

(4)第20行~第22行,这段用于保证beanName的唯一性的,BeanDefinitionParserDelegate中有一个属性usedNames,这是一个Set,强制性地保证了beanName的唯一性

(5)第24行用于解析bean的其他属性,后面的代码不太重要,看一下parseBeanDefinitionElement的实现

 1 public AbstractBeanDefinition parseBeanDefinitionElement(
 2         Element ele, String beanName, BeanDefinition containingBean) {
 3 
 4     this.parseState.push(new BeanEntry(beanName));
 5 
 6     String className = null;
 7     if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
 8         className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
 9     }
10 
11     try {
12         String parent = null;
13         if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
14             parent = ele.getAttribute(PARENT_ATTRIBUTE);
15         }
16         AbstractBeanDefinition bd = createBeanDefinition(className, parent);
17 
18         parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
19         bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
20 
21         parseMetaElements(ele, bd);
22         parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
23         parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
24 
25         parseConstructorArgElements(ele, bd);
26         parsePropertyElements(ele, bd);
27         parseQualifierElements(ele, bd);
28         bd.setResource(this.readerContext.getResource());
29         bd.setSource(extractSource(ele));
30 
31         return bd;
32     }
33     catch (ClassNotFoundException ex) {
34         error("Bean class [" + className + "] not found", ele, ex);
35     }
36     catch (NoClassDefFoundError err) {
37         error("Class that bean class [" + className + "] depends on not found", ele, err);
38     }
39     catch (Throwable ex) {
40         error("Unexpected failure during bean definition parsing", ele, ex);
41     }
42     finally {
43         this.parseState.pop();
44     }
45 
46     return null;
47 }

这里会取class属性、parent属性,18行的代码可以跟进去看一下这里就不贴了,会取得scope、lazy-init、abstract、depends-on属性等等,设置到BeanDefinition中,这样大致上,一个Bean的定义就被存入了BeanDefinition中。最后一步追溯到之前DefaultBeanDefinitionDocumentReader的processBeanDefinition方法:

1 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());

这句语句将BeanDefinition存入DefaultListableBeanFactory的beanDefinitionMap中,追踪一下代码最终到DefaultListableBeanFactory的registerBeanDefinition方法中:

 1 public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
 2     throws BeanDefinitionStoreException {
 3 
 4     Assert.hasText(beanName, "Bean name must not be empty");
 5     Assert.notNull(beanDefinition, "BeanDefinition must not be null");
 6 
 7     if (beanDefinition instanceof AbstractBeanDefinition) {
 8         try {
 9             ((AbstractBeanDefinition) beanDefinition).validate();
10         }
11         catch (BeanDefinitionValidationException ex) {
12             throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
13                     "Validation of bean definition failed", ex);
14         }
15     }
16 
17     synchronized (this.beanDefinitionMap) {
18         Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
19         if (oldBeanDefinition != null) {
20             if (!this.allowBeanDefinitionOverriding) {
21                 throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
22                         "Cannot register bean definition [" + beanDefinition + "] for bean ‘" + beanName +
23                         "‘: There is already [" + oldBeanDefinition + "] bound.");
24             }
25             else {
26                 if (this.logger.isInfoEnabled()) {
27                     this.logger.info("Overriding bean definition for bean ‘" + beanName +
28                             "‘: replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
29                 }
30             }
31         }
32         else {
33             this.beanDefinitionNames.add(beanName);
34             this.frozenBeanDefinitionNames = null;
35         }
36         this.beanDefinitionMap.put(beanName, beanDefinition);
37 
38         resetBeanDefinition(beanName);
39     }
40 }

大致上就是beanDefinitionNames中增加一个beanName,beanDefinitionMap将老的BeanDefinition替换(假如不允许BeanDefinition重写的话会抛出异常)。这样一个漫长的流程过后,XML文件中的各个bean节点被转换为BeanDefinition,存入了DefaultListableBeanFactory中,后续DefaultListableBeanFactory可以根据BeanDefinition,构造对应的Bean对象出来。

 

其他部分

上面分析了Spring上下文加载的代码入口以及refresh方法中的一些重点部分,下面还有一些子方法没有分析,这里简单分析一下:

1、prepareBeanFactory方法

  prepareBeanFactory用于配置BeanFactory上下文的标准特征,比如加载Spring上下文使用的ClassLoader以及一些前置处理器

2、postProcessBeanFactory方法

  在标准初始化之后修改应用上下文的内部BeanFactory,所有Bean的定义已经被加载完毕了,但是Bean还没有被初始化,此时允许在特定的ApplicationContext上下文中注册特定的BeanPostProcessor

3、invokeBeanFactoryPostProcessors方法

  初始化并且调用所有已经注册的BeanProcessor接口,按照BeanProcessor接口给定的顺序调用。其实最终就是调用BeanProcessor接口的postProcessBeanFactory方法,传入BeanFactory

4、registerBeanPostProcessors方法

  注册所有的BeanPostProcessor方法

5、initMessageSource方法

  初始化上下文的MessageSource

6、initApplicationEventMulticaster方法

  初始化上下文的多播事件

7、onRefresh方法

  在指定的上下文子类中初始化其他特殊的Bean

8、finishBeanFactoryInitialization方法

  初始化所有非懒加载单例Bean,标红加粗表示这是重点,所有单例Bean通过这个方法加载,后文也是围绕这个方法展开

9、finishRefresh方法

  最后一步,结束刷新上下文,发布正确的事件

 

【Spring源码分析】Bean加载流程概览

标签:lap   proc   代码实现   获取   gis   color   sof   ati   技术   

原文地址:http://www.cnblogs.com/xrq730/p/6285358.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!