这里先从最简单的一个Spring例子开始。

下面是Spring的context的配置



1 <?xml version="1.0" encoding="UTF-8"?>
  2 <beans xmlns="http://www.springframework.org/schema/beans"
  3        xmlns:context="http://www.springframework.org/schema/context"
  4        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5        xsi:schemaLocation="http://www.springframework.org/schema/context
  6                            http://www.springframework.org/schema/context/spring-context.xsd
  7                            http://www.springframework.org/schema/beans
  8                            http://www.springframework.org/schema/beans/spring-beans.xsd">
  9 
 10     <!-- 自动扫描web包 ,将带有注解的类 纳入spring容器管理 -->
 11     <context:component-scan base-package="com.tuhooo.practice"></context:component-scan>
 12 </beans>



 

一个简单的Service类,MyBean.java



1 @Service
  2 public class MyBean {
  3     public void printStr() {
  4         System.out.println("你好吗......");
  5     }
  6 }



 

main方法中获取bean,并调用MyBean.java中的方法。



1 public class App {
  2     public static void main( String[] args ) {
  3         XmlBeanFactory applicationContext = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
  4         MyBean myBean = (MyBean) applicationContext.getBean("myBean");
  5         myBean.printStr();
  6     }
  7 }



 

当我在使用XmlBeanFactory这个类的时候,已经被标记为@deprecated,也就是这个类已经被抛弃了,但是并不妨碍它作为我们滴入口类进行分析呀。

XmlBeanFactory.java



1 @Deprecated
  2 @SuppressWarnings({"serial", "all"})
  3 public class XmlBeanFactory extends DefaultListableBeanFactory {
  4 
  5     /**
  6      * 加载Bean定义的Reader
  7      */
  8     private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
  9 
 10     /**
 11      * 通过给定的可通过DOM解析的资源来创建一个XmlBeanFactory
 12      * @param resource 用来加载bean定义的XML资源
 13      * @throws BeansException 加载或者解析时候的错误
 14      */
 15     public XmlBeanFactory(Resource resource) throws BeansException {
 16         this(resource, null);
 17     }
 18 
 19     /**
 20      * 通过给定的可通过DOM解析的输入流来创建一个XmlBeanFactory
 21      * @param resource 用来加载bean定义的XML资源
 22      * @param parentBeanFactory 父代的bean factory
 23      * @throws BeansException 加载或者解析时候的错误
 24      */
 25     public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
 26         super(parentBeanFactory);
 27         this.reader.loadBeanDefinitions(resource);   /*这个是我们要进入, 并分析的方法*/
 28     }
 29 }



 

java springbean加载耗时监控 spring的加载_XML

 

 

接下来看一下XmlBeanDefinitionReader中的loadBeanDefinitions(resource)的逻辑是怎样的。

XmlBeanDefinitionReader.loadBeanDefinitions(resource)

XmlBeanDefinitionReader的类图如下:

java springbean加载耗时监控 spring的加载_java_02

下面是XmlBeanDefinitionReader.java这个类截取的一部分:



public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
    /**
     * Load bean definitions from the specified XML file.
     * @param resource the resource descriptor for the XML file
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     */
    @Override
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));
    }

    /**
     * Load bean definitions from the specified XML file.
     * @param encodedResource the resource descriptor for the XML file,
     * allowing to specify an encoding to use for parsing the file
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     */
    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isInfoEnabled()) {
            logger.info("Loading XML bean definitions from " + encodedResource.getResource());
        }

        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                inputStream.close();
            }
        } catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        } finally {
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }
}



 

可以对着这个时序图,看来下面的代码:

java springbean加载耗时监控 spring的加载_加载_03

 

java springbean加载耗时监控 spring的加载_spring_04

 

下面我们进入到XmlBeanDefinitionReader.loadBeanDefinitions(EncodedResource encodedResource)方法内部看一下。

java springbean加载耗时监控 spring的加载_spring_05

 

这里EncodedResource验重的逻辑是:



public class EncodedResource implements InputStreamSource {
    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof EncodedResource)) {
            return false;
        }
        EncodedResource otherResource = (EncodedResource) other;
        return (this.resource.equals(otherResource.resource) &&
                ObjectUtils.nullSafeEquals(this.charset, otherResource.charset) &&
                ObjectUtils.nullSafeEquals(this.encoding, otherResource.encoding));
    }
}



 

这个loadBeanDefinitions方法还是很清晰的,你说像我这种制杖都看懂了,就想说一句,还有谁看不懂~~~

但仍有几个地方需要后续再跟进看一下:

1. 这里有个局部线程变量来存放正在加载的资源,this.resourceCurrentlyBeingLoaded.get(),可以我们看到线程在哪儿?

2. 你说搞encodedResource我理解,但是为什么在doLoadBeanDefinitions方法中既有inputSource,又有encodedResource,不会显得很多余么?

XmlBeanDefinitionReader.doLoadBeanDefinitions(InputSource, Resource)

 



1 public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
  2 
  3     /**
  4      * Actually load bean definitions from the specified XML file.
  5      * @param inputSource the SAX InputSource to read from
  6      * @param resource the resource descriptor for the XML file
  7      * @return the number of bean definitions found
  8      * @throws BeanDefinitionStoreException in case of loading or parsing errors
  9      * @see #doLoadDocument
 10      * @see #registerBeanDefinitions
 11      */
 12     protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
 13             throws BeanDefinitionStoreException {
 14         try {
 15             Document doc = doLoadDocument(inputSource, resource);
 16             return registerBeanDefinitions(doc, resource);
 17         }
 18         catch (BeanDefinitionStoreException ex) {
 19             throw ex;
 20         }
 21         catch (SAXParseException ex) {
 22             throw new XmlBeanDefinitionStoreException(resource.getDescription(),
 23                     "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
 24         }
 25         catch (SAXException ex) {
 26             throw new XmlBeanDefinitionStoreException(resource.getDescription(),
 27                     "XML document from " + resource + " is invalid", ex);
 28         }
 29         catch (ParserConfigurationException ex) {
 30             throw new BeanDefinitionStoreException(resource.getDescription(),
 31                     "Parser configuration exception parsing XML from " + resource, ex);
 32         }
 33         catch (IOException ex) {
 34             throw new BeanDefinitionStoreException(resource.getDescription(),
 35                     "IOException parsing XML document from " + resource, ex);
 36         }
 37         catch (Throwable ex) {
 38             throw new BeanDefinitionStoreException(resource.getDescription(),
 39                     "Unexpected exception parsing XML document from " + resource, ex);
 40         }
 41     }
 42 }



最终把硬盘上的配置文件以Document的形式读到了内存中。

java springbean加载耗时监控 spring的加载_spring_06

XmlBeanDefinitionReader.registerBeanDefinitions(Document, Resource)

 



1 public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
  2 
  3     public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
  4         BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
  5         int countBefore = getRegistry().getBeanDefinitionCount();
  6         documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
  7         return getRegistry().getBeanDefinitionCount() - countBefore;
  8     }
  9 
 10     /**
 11      * Create the {@link BeanDefinitionDocumentReader} to use for actually
 12      * reading bean definitions from an XML document.
 13      * <p>The default implementation instantiates the specified "documentReaderClass".
 14      * @see #setDocumentReaderClass
 15      */
 16     protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
 17         return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
 18     }
 19 
 20     /**
 21      * Create the {@link XmlReaderContext} to pass over to the document reader.
 22      */
 23     public XmlReaderContext createReaderContext(Resource resource) {
 24         return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
 25                 this.sourceExtractor, this, getNamespaceHandlerResolver());
 26     }
 27 }
 28



首先不看别的,直接看这段代码就行:

1. 构造一个BeanDefinitionDocumentReader,然后用它来注册BeanDefinition,它还需要一个读取时候的上下文;

2. 返回的是新加入Registry的BeanDefinition的个数

3. createBeanDefinitionDocumentReader的作用还没看太明白

java springbean加载耗时监控 spring的加载_spring_07

DefaultBeanDefinitionDocumentReader.registerBeanDefinitions

java springbean加载耗时监控 spring的加载_XML_08

再去看一下doRegisterBeanDefinitions方法

java springbean加载耗时监控 spring的加载_java_09

真正处理的方法又流转到了parseBeanDefinitions中,但是我没明白delegate是干啥啊?

 

Resource的类图

java springbean加载耗时监控 spring的加载_加载_10



public class ClassPathResource extends AbstractFileResolvingResource {

    /**
     * Create a new {@code ClassPathResource} for {@code ClassLoader} usage.
     * A leading slash will be removed, as the ClassLoader resource access
     * methods will not accept it.
     * <p>The thread context class loader will be used for
     * loading the resource.
     * @param path the absolute path within the class path
     * @see java.lang.ClassLoader#getResourceAsStream(String)
     * @see org.springframework.util.ClassUtils#getDefaultClassLoader()
     */
    public ClassPathResource(String path) {
        this(path, (ClassLoader) null);
    }

    /**
     * Create a new {@code ClassPathResource} for {@code ClassLoader} usage.
     * A leading slash will be removed, as the ClassLoader resource access
     * methods will not accept it.
     * @param path the absolute path within the classpath
     * @param classLoader the class loader to load the resource with,
     * or {@code null} for the thread context class loader
     * @see ClassLoader#getResourceAsStream(String)
     */
    public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
        Assert.notNull(path, "Path must not be null");
        String pathToUse = StringUtils.cleanPath(path);
        if (pathToUse.startsWith("/")) {
            pathToUse = pathToUse.substring(1);
        }
        this.path = pathToUse;
        this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
    }
}



在代码new ClassPathResource("beanFactoryTest.xml");中其实没有啥动作,就是对ClassPathResource的几个属性赋值了,尤其是类加载器。也就是说这个时候其实并没有读取文件内容。