1、容器初始化,ApplicationContext.xml的解析

Spring提供了很多种配置的方式,有xml,基于代码,基于注解。自然的,也就有很多种不同的初始化容器的方式。这里以使用最广泛的xml为例。

Spring源码阅读笔记之容器初始化 - 图1

(图片来自网络)ApplicationContext有一张庞大的继承网络,源接口来自BeanFactory,即创建bean的工厂。因此,继承自ApplicationContext的ClassPathXmlApplicationContext类,也是Bean工厂的一种,它解析xml文件,创建配置好的bean。

首先来看它的构造方法。若有父AppllicationContext传入,则先调用父方法。随后设置配置路径。最后调用refresh方法。

Spring源码阅读笔记之容器初始化 - 图2

setConfigLocations方法,设置配置路径。通过断点我们可以清晰的看到,这个路径就是我们项目里配置的applicationContext.xml的路径。但是这一步并没有对文件做任何的解析,而是将文件名解析出来,放置到一个String类型的数组configLocations当中。

Spring源码阅读笔记之容器初始化 - 图3

refresh方法开始进行实质性的容器刷新。方法比较长,步骤也比较多,属于spring比较核心的方法。

  1. @Override
  2. public void refresh() throws BeansException, IllegalStateException {
  3. //开始刷新之前需要加锁
  4. synchronized (this.startupShutdownMonitor) {
  5. // 刷新前的准备工作,记录启动时间
  6. //设置一些boolean的状态位,处理配置文件里的占位符
  7. prepareRefresh();
  8. // 这一步的核心是refreshBeanFactory方法,在方法中加载了bean的定义信息,loadBeanDefinitions
  9. //方法,即初始化xml阅读器,然后读取配置文件,将bean的定义,或其他定义,读取成Document
  10. //对象,然后在BeanDenifitionDocumentReader的doRegisterBeanDefinitions
  11. //方法中,将Document对象逐行解析,判断是import还是bean或者是其他,随即调用对应的
  12. //处理逻辑,processBeanDefinition,或importBeanDenifitionResource方法等,以
  13. //processBeanDefinition方法为例,首先获取BeanDefinitionHolder和BeanDefinitionRegistry
  14. //两个对象,随即对bean进行注册,注册的核心是registry.registerBeanDefinition
  15. //方法,其实非常简单,就是把bean的名称和一些其他的信息,放到一个map里
  16. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
  17. //对BeanFactory做一些准备工作,例如设置BeanClassLoader,BeanPostPocesser
  18. //,装载一些特殊的bean,例如environment, systemProperties等等
  19. prepareBeanFactory(beanFactory);
  20. try {
  21. //bean如果是实现了BeanFactoryPostProcessor 接口,那么在这里,可以调用bean的方法
  22. //来让bean提前做一些事
  23. postProcessBeanFactory(beanFactory);
  24. // Invoke factory processors registered as beans in the context.
  25. invokeBeanFactoryPostProcessors(beanFactory);
  26. //注册BeanPostProcessor 实现类,这类似于bean初始化的一个切面,分别在bean初始化之前
  27. //和之后执行方法,则对应的实现类需要给出两个方法的具体实现,postProcessBeforeInitialization和postProcessAfterInitialization
  28. registerBeanPostProcessors(beanFactory);
  29. // Initialize message source for this context.
  30. initMessageSource();
  31. // Initialize event multicaster for this context.
  32. initApplicationEventMulticaster();
  33. // 模板方法,让子类去做一些自定义的操作。
  34. onRefresh();
  35. //注册事件监听器,事件监听需要实现ApplicationListener接口
  36. //例如dubbo的ServiceBean就实现了这个接口
  37. registerListeners();
  38. //初始化所有的单例bean。这里同样的也做了很多事情,例如注册注解替换处理器等
  39. finishBeanFactoryInitialization(beanFactory);
  40. // 广播事件,刷新完成
  41. finishRefresh();
  42. }
  43. catch (BeansException ex) {
  44. if (logger.isWarnEnabled()) {
  45. logger.warn("Exception encountered during context initialization - " +
  46. "cancelling refresh attempt: " + ex);
  47. }
  48. // Destroy already created singletons to avoid dangling resources.
  49. destroyBeans();
  50. // Reset 'active' flag.
  51. cancelRefresh(ex);
  52. // Propagate exception to caller.
  53. throw ex;
  54. }
  55. finally {
  56. // Reset common introspection caches in Spring's core, since we
  57. // might not ever need metadata for singleton beans anymore...
  58. resetCommonCaches();
  59. }
  60. }
  61. }

至此,spring就完成了容器的初始化,bean的初始化,以及一系列相关工具的初始化。下面对其中的方法进行进一步的解析。

prepareRefresh

  1. protected void prepareRefresh() {
  2. this.startupDate = System.currentTimeMillis();
  3. this.closed.set(false);
  4. this.active.set(true);
  5. if (logger.isInfoEnabled()) {
  6. logger.info("Refreshing " + this);
  7. }
  8. // Initialize any placeholder property sources in the context environment
  9. //从这句话的注解也可以看得出,这个方法就是处理配置文件中的占位符
  10. initPropertySources();
  11. // Validate that all properties marked as required are resolvable
  12. // see ConfigurablePropertyResolver#setRequiredProperties
  13. getEnvironment().validateRequiredProperties();
  14. // Allow for the collection of early ApplicationEvents,
  15. // to be published once the multicaster is available...
  16. this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
  17. }

initPropertySources方法的默认实现在AbstraceRefreshableWebApplicationCOntext类中,在方法中,获取了当前的ConfigurableEnvironment对象,通过断点可以看到,env环境对象中,包含了一个

propertyResolver类,他的一些属性显而易见,是为了处理${}形式的占位符。

Spring源码阅读笔记之容器初始化 - 图4

进入到env对象的initPropertySources方法里,最终是来到了WebApplicationContextUtils.initServletPropertySources方法来处理占位符。

obtainFreshBeanFactory

准备完毕,接下来进入obtainFreshBeanFactory方法,在这个方法里做了很多事情,例如创建BeanFactory,将bean注册(但是不初始化)等等。obtainFreshBeanFactory的核心是refreshBeanFactory方法。直接进入refreshBeanFactory方法

  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

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

首先判断beanFactory是不是为null,如果不是,会把单例的bean全部清理,然后将beanFactory置为null。既然要刷新,就得把之前的先清理干净。

随后createBeanFactory方法,newl了一个DefaultListableBeanFactory,这是ApplicationContext持有的BeanFactory的默认实现。

接下来一行,通过获取类的id来作为序列化ID

接下来一行调用customizeBeanFactory方法,这个方法设置了两个属性,是否允许同名bean覆盖,是否允许循环依赖。

接下来一行loadBeanDefinitions方法,这是解析xml,初始化Bean的核心方法。在方法中先实例化一个XmlBeanDefinitionReader,xml的解析和bean的加载都在reader中完成。重点在loadBeanDefinitions方法里。

  1. protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
  2. String[] configLocations = getConfigLocations();
  3. if (configLocations != null) {
  4. for (String configLocation : configLocations) {
  5. reader.loadBeanDefinitions(configLocation);
  6. }
  7. }
  8. }

getConfigLocations获取一个String数组。这个数组里装的就是配置文件的路径了,随后放入reader进行解析。loadBeanDefinitions代码如下,核心的逻辑是获取了一个Resource数组然后再次调用了loadBeanDefinitions的重载方法。

  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. if (resourceLoader instanceof ResourcePatternResolver) {
  8. // Resource pattern matching available.
  9. try {
  10. Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
  11. //将资源进行加载,也就是将xml文件进行解析
  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. }
  1. public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
  2. //...多余代码不复制
  3. //核心逻辑,h获取了资源对象的inputStream,开始读取resource里保存的xml文件路径
  4. try {
  5. InputStream inputStream = encodedResource.getResource().getInputStream();
  6. try {
  7. InputSource inputSource = new InputSource(inputStream);
  8. if (encodedResource.getEncoding() != null) {
  9. inputSource.setEncoding(encodedResource.getEncoding());
  10. }
  11. //读到文件流之后调用doLoadBeanDefinitions方法
  12. return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
  13. }
  14. finally {
  15. inputStream.close();
  16. }
  17. }
  18. }
  19. //不核心的代码省略

在这个方法里,Resource里保存的xml路径被转换为一个imputStream对象,文件流被读进来并且封装成一个InputResource对象。通过断点可以看到这个对象里啥都没有,只有一个byteStream

Spring源码阅读笔记之容器初始化 - 图5

随后调用doLoadBeanDefinitions方法,代码如下

  1. protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
  2. throws BeanDefinitionStoreException {
  3. try {
  4. Document doc = doLoadDocument(inputSource, resource);
  5. return registerBeanDefinitions(doc, resource);
  6. }
  7. }

doLoadDocument将文件流解析成了Document对象。随后调用registerBeanDefinitions方法对bean进行注册。registerBeanDefinitions最终来到DefaultBeanDefinitaionDocumentReader的doRegisterBeanDefinitions方法,代码如下

  1. protected void doRegisterBeanDefinitions(Element root) {
  2. // 这里有一大堆注解,被我删掉了
  3. // 大意是说<beans>标签里可以递归的定义<beans>,所以为了解决递归问题
  4. // 需要传递一个parent
  5. BeanDefinitionParserDelegate parent = this.delegate;
  6. this.delegate = createDelegate(getReaderContext(), root, parent);
  7. //如果是默认的namespace
  8. //默认的namespace是http://www.springframework.org/schema/beans
  9. if (this.delegate.isDefaultNamespace(root)) {
  10. //然后是一大段关于profile配置的解析。profile就是环境之类的,不常用
  11. String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
  12. if (StringUtils.hasText(profileSpec)) {
  13. String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
  14. profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
  15. if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
  16. if (logger.isInfoEnabled()) {
  17. logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
  18. "] not matching: " + getReaderContext().getResource());
  19. }
  20. return;
  21. }
  22. }
  23. }
  24. preProcessXml(root);
  25. parseBeanDefinitions(root, this.delegate);
  26. postProcessXml(root);
  27. this.delegate = parent;
  28. }

preProcessXml和postProcessXml都是模板方法,留给子类实现的,从断点可以看到,其实是没有提供实现。可以跳过。重点关注parseBeanDifinitions方法

  1. protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
  2. if (delegate.isDefaultNamespace(root)) {
  3. NodeList nl = root.getChildNodes();
  4. for (int i = 0; i < nl.getLength(); i++) {
  5. Node node = nl.item(i);
  6. if (node instanceof Element) {
  7. Element ele = (Element) node;
  8. if (delegate.isDefaultNamespace(ele)) {
  9. parseDefaultElement(ele, delegate);
  10. }
  11. else {
  12. delegate.parseCustomElement(ele);
  13. }
  14. }
  15. }
  16. }
  17. else {
  18. delegate.parseCustomElement(root);
  19. }
  20. }

首先判断是不是默认的namespace,也就是说如果是beans标签,自然是要把子标签的bean都一一解析出来的。贴一张项目里的配置文件作为例子

Spring源码阅读笔记之容器初始化 - 图6

Spring源码阅读笔记之容器初始化 - 图7

可以看到例子中的applicationContext.xml文件是以beans标签开头的,因此进入一个for循环,开始解析标签下的子node。由断点的截图可以看到,当前解析出了Element对象类型的node,即图一中的第一个标签context:annotation-config。解析出Element对象以后又判断了一次是不是默认的namespace,如果是那么递归解析。如果不是,那就默认解析,调用的是parseCustomElement方法,在这个方法中,根据其namespace来找到对应的namespaceHandler,所有的handlerMapping都被保存在一个map当中,以namespaceUri为key,查找对应的namespaceHandler。这也是spring的扩展性所在,自定义实现namespaceHandler就可以解析配置文件中的自定义标签了,例如dubbo的dubbo:xxx标签。每一个标签对应的bean,最后都被解析成了BeanDefinition对象。

搬运自我的简书 https://www.jianshu.com/p/0b740732b9ae