天天看點

Spring源碼閱讀-- IOC容器資源定位

1、環境搭建

工程目錄結構:

Spring源碼閱讀-- IOC容器資源定位

首先我們從最基礎的spring開發代碼開始,首先上場的是spring配置檔案:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-4.3.xsd ">

    <!-- 引入配置檔案 -->
    <context:property-placeholder location="classpath:jdbc.properties" />

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!--屬性注入-->
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <!-- 連接配接池最大使用連接配接數 -->
        <property name="maxActive" value="${jdbc.maxActive}"/>
        <!-- 初始化連接配接大小 -->
        <property name="initialSize" value="${jdbc.initialSize}"/>
        <!-- 擷取連接配接最大等待時間 -->
        <property name="maxWait" value="${jdbc.maxWait}"/>
        <!-- 連接配接池最大空閑 -->
        <!--  property name="maxIdle" value="${jdbc.maxIdle}"/>-->
        <!-- 連接配接池最小空閑 -->
        <property name="minIdle" value="${jdbc.minIdle}"/>
        <!-- 自動清除無用連接配接 -->
        <property name="removeAbandoned" value="${jdbc.removeAbandoned}"/>
        <!-- 清除無用連接配接的等待時間 -->
        <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/>
        <!-- 連接配接屬性 -->
        <property name="connectionProperties" value="${jdbc.connectionProperties}"/>
    </bean>

    <bean id="zhangsan"  class="com.igood.entity.User">
        <constructor-arg type="java.lang.String">
            <value>zhangsan</value>
        </constructor-arg>
        <constructor-arg type="java.lang.String">
            <value>123456</value>
        </constructor-arg>
        <constructor-arg type="java.lang.Integer">
            <value>20</value>
        </constructor-arg>
    </bean>
    <!--給zhangsan這個bean起幾個别名,其中有一個别名和原來bean名稱相同-->
    <alias name="zhangsan" alias="zhangsan,zhang3,alias1"/>

    <!--設定名稱為wangwu的bean不是懶加載-->
    <bean id="wangwu"  class="com.igood.entity.User" lazy-init="false" scope="prototype">
        <constructor-arg type="java.lang.String">
            <value>wangwu</value>
        </constructor-arg>
        <constructor-arg type="java.lang.String">
            <value>111111</value>
        </constructor-arg>
        <constructor-arg type="java.lang.Integer">
            <value>25</value>
        </constructor-arg>
    </bean>
</beans>
           

接着是main函數啟動代碼

//代碼片段1
public static void main(String[] args) {

    ClassPathXmlApplicationContext context =
            new ClassPathXmlApplicationContext("classpath:spring-beans.xml");
    User bean = (User)context.getBean("zhang3");
    bean.getUsername();
    context.close();
}
           

在跟蹤源碼前,先來看一下spring IOC 體系結構相關類的繼承關系圖:

Spring源碼閱讀-- IOC容器資源定位

為了思路清晰,類圖中隻列出比較重要的方法和變量。

好了,開始跟蹤spring源碼。首先進入ClassPathXmlApplicationContext的構造函數

//代碼片段2
public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
    this(configLocations, true, null);
}

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
        throws BeansException {

    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        refresh();
    }
}
           

一直沿着繼承關系檢視父類的構造函數,到AbstractApplicationContext

//代碼片段3
public AbstractApplicationContext() {
    this.resourcePatternResolver = getResourcePatternResolver();
}
protected ResourcePatternResolver getResourcePatternResolver() {
    return new PathMatchingResourcePatternResolver(this);
}
           

從ClassPathXmlApplicationContext的類圖可以看出AbstractApplicationContext和PathMatchingResourcePatternResolver實作了ResourceLoader接口,也就是說AbstractApplicationContext也有ResourcePatternResolver的getResource()的能力,但是AbstractApplicationContext不親自具體實作getResource,而是委派給PathMatchingResourcePatternResolver去做。

2、徑路解析

回到“代碼片段2”中,ClassPathXmlApplicationContext的構造函數中setConfigLocations,這個函數在父類AbstractRefreshableConfigApplicationContext中實作

//代碼片段4
 public void setConfigLocations(String... locations) {
    if (locations != null) {
        Assert.noNullElements(locations, "Config locations must not be null");
        this.configLocations = new String[locations.length];
        for (int i = ; i < locations.length; i++) {
            this.configLocations[i] = resolvePath(locations[i]).trim();
        }
    }
    else {
        this.configLocations = null;
    }
}

protected String resolvePath(String path) {
    return getEnvironment().resolveRequiredPlaceholders(path);
}
           

resolvePath的作用就是将建立ApplicationContext傳入進來的locations(classpath: spring-beans.xml)配置檔案路徑轉換為合法的location,就是将路徑中包含環境變量轉成變量的實際值,比如路徑有 占位符中的變量替換成變量實際的值。getEnviroment()拿到StandardEnvironment對象來進行占位符 {}的替換。

//代碼片段5
public class StandardEnvironment extends AbstractEnvironment {
    /** System environment property source name: {@value} */
    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

    /** JVM system properties property source name: {@value} */
    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }

}
           

StandardEnvironment就是讀取作業系統的環境變量和JVM的變量。當傳進來的路徑中包含這些環境變量就替換成系統中變量的實際值。比如我們傳進來的路徑是”classpath: JAVAHOME/spring−beans.xml",經過resolveRequiredPlaceholders函數處理就把 {JAVA_HOME}替換成你電腦組態的JAVA_HOME環境變量的值,如”classpath:C:/Program Files/Java/jdk1.8.0_91/spring-beans.xml”。

3、容器重新整理

好了,資源路徑準備好了,我們回到”代碼片段2”中的refresh()函數,重頭戲都在這個函數裡了。這個函數在父類AbstractApplicationContext中實作。

//代碼片段6
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 重新整理工廠之前需要做一些準備工作的啦,就想你在運動之前要做一些準備運動一樣哦 
        prepareRefresh();

        // 我會告訴我的子類創造一個工廠,來把我需要建立bean的原料BeanDefinition準備好
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // 原料準備好之後呢,我要聲明一些特殊的依賴關系, 所謂依賴,就是我在創造一個bean A的時候,發現它裡面有另外一個屬性B
        // 那麼B就是A的依賴,我在創造A的時候,必須先把B創造好,特殊關系的依賴就是指我遇到B的類型,我該放棄呢,還是告訴他直接用
        // 現成的(也就是不用再去創造B了)
        prepareBeanFactory(beanFactory);

        try {
            // 這裡沒啥,就是留給子類做擴充的啦
            postProcessBeanFactory(beanFactory);

            // 到了這裡,工廠已經準備好了,如果你之前告訴過我工廠準備好之後應該幹什麼事情,這邊我就可以滿足你的需求哦
            // 不信,你去看看BeanFactoryPostProcessors接口是幹嘛用的吧==
            invokeBeanFactoryPostProcessors(beanFactory);

            // 在建立一個bean的前後,我也留給你很多擴充,原理上和上面的工廠擴充差不多的哦
            registerBeanPostProcessors(beanFactory);

            // 就是處理一些國際化的操作啦,啊?什麼是國際化,就是i18n啦,還不懂?你沒救了
            initMessageSource();

            // 我的功能很豐富,除了可以給你建立bean,還可以有事件管理的功能哦,這裡我就建立一個管理器(ApplicationEventMulticaster(),
            // 用來注冊事件(ApplicationEvent)
            // 我會将這些事件廣播給合适的監聽者(ApplicationListener)那邊哦
            initApplicationEventMulticaster();

            // 啥也不幹,留給子類擴充啦
            onRefresh();

            // 前面不是事件管理器搞好了嘛,這邊呢,就是把那些事件監聽器給注冊進來啦,這樣來一個新的事件我就知道該發給誰啦
            registerListeners();

            // 如果某些bean告我我,他想在我工廠建立之初就想初始化(一般要是單件singleton并且lazy-init為false),那麼我在這個函數會滿足他
            finishBeanFactoryInitialization(beanFactory);

            // 終于重新整理完了,我要開始釋出事件了!
            finishRefresh();
        }

        // 什麼?重新整理的時候報錯了?oh my god,我需要做一些清理
        catch (BeansException ex) {
            logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);

            // 我需要将我建立的bean銷毀掉
            destroyBeans();

            // 我不再活躍
            cancelRefresh(ex);

            // 我要告訴你,我出異常了,救我!!
            throw ex;
        }

        finally {
            // 一些通用的緩存清掉!!
            resetCommonCaches();
        }
    }
}

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    refreshBeanFactory();
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (logger.isDebugEnabled()) {
        logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
    }
    return beanFactory;
}
           

refresh()方法主要為 IOC 容器 Bean 的生命周期管理提供條件, Spring IOC 容器載入 Bean 定義資源檔案從其子類容器的 refreshBeanFactory()方法啟動, 是以整個 refresh()中“ConfigurableListableBeanFactory beanFactory =obtainFreshBeanFactory();” 這句以後代碼的都是注冊容器的資訊源和生命周期事件, 載入過程就是從這句代碼啟動。

3.1、建立容器

順着obtainFreshBeanFactory方法,進入到AbstractRefreshableApplicationContext的refreshBeanFactory方法,這個方法的主要功能就是建立預設的IOC容器和Xml配置檔案的相關操作。

//代碼片段7
protected final void refreshBeanFactory() throws BeansException {
    if (hasBeanFactory()) {//如果已經有容器, 銷毀容器中的 bean, 關閉容器,以保證在 refresh 之後使用的是建立立起來的 IOC 容器
        destroyBeans();
        closeBeanFactory();
    }
    try {
        DefaultListableBeanFactory beanFactory = createBeanFactory();//建立 IOC 容器了
        beanFactory.setSerializationId(getId());
        //對 IOC 容器進行定制化, 如設定啟動參數, 開啟注解的自動裝配等
        customizeBeanFactory(beanFactory);
        // 加載bean的定義,我們用xml描述了各種bean的具體定義,是以這個函數
        loadBeanDefinitions(beanFactory);
        synchronized (this.beanFactoryMonitor) {
            this.beanFactory = beanFactory;
        }
    }
    catch (IOException ex) {
        throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
    }
}
           

第7行代碼

DefaultListableBeanFactory beanFactory = createBeanFactory()

為我們新建立一個IOC容器,所謂容器就是一個Map資料結構,進入DefaultListableBeanFactory代碼就發現有

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);

這個成員變量,spring就是将以bean的名字做key,bean的定義作為值存到這個Map中,這就是我們需要建立bean所需要的原材料。

3.2、加載bean定義

既然容器建立好了,那麼第12行代碼

loadBeanDefinitions(beanFactory)

就要為beanFactory容器加載bean定義(BeanDefinition)了。

AbstractRefreshableApplicationContext 中隻定義了抽象的 loadBeanDefinitions 方法, 容器真正調用的是其子類 AbstractXmlApplicationContext 對該方法的實作。

//代碼片段8
//AbstractXmlApplicationContext實作父類的抽象方法
@Override
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());
    //設定資源加載器就是容器本身,1、環境搭建小節中“代碼片段3”說明AbstractApplicationContext也有資源加載器的能力
    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);

就是用這個讀取器來加載bean定義的。

//代碼片段9
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);
    }
}
           

我們會看到有兩種方式加載配置檔案,一種是從Resource類型的路徑中加載,還有一種是從String類型的路徑中加載。 由于我們main函數入口是

new ClassPathXmlApplicationContext("classpath:spring-beans.xml");

是以new出來ClassPathXmlApplicationContext對象裡getConfigResources()是null的,而getConfigLocations()就前面“ 2、徑路解析”小節準備的配置檔案路徑,是以進入到第8行代碼,從從String類型的路徑中加載xml檔案。

進入XmlBeanDefinitionReader的loadBeanDefinitions方法:

//代碼片段10
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
    Assert.notNull(locations, "Location array must not be null");
    int counter = ;
    for (String location : locations) {
        counter += loadBeanDefinitions(location);
    }
    return counter;
}
           

loadBeanDefinitions循環從每個路徑中加載xml檔案。真正加載資源的是它重載函數:

//代碼片段11
//真正幹活的函數
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;
    }
}
           

第4行代碼

ResourceLoader resourceLoader = getResourceLoader();

擷取的ResourceLoader對象就是“代碼片段8”中

beanDefinitionReader.setResourceLoader(this);

,也就是ClassPathXmlApplicationContext類的對象,1、環境搭建小節中“代碼片段3”說明AbstractApplicationContext實作ResourcePatternResolver接口,是以作為AbstractApplicationContext的子類ClassPathXmlApplicationContext對象擁有getResource()的能力,但是ClassPathXmlApplicationContext不親自具體實作getResource,而是委派給PathMatchingResourcePatternResolver去做。是以”代碼片段11”中

Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);

最終調用如下圖:

Spring源碼閱讀-- IOC容器資源定位

4、資源定位

好了,跟蹤這麼久現在才進入本文的标題真正要說的事。進入PathMatchingResourcePatternResolver類的getResource方法:

//代碼片段12
@Override
public Resource[] getResources(String locationPattern) throws IOException {
    Assert.notNull(locationPattern, "Location pattern must not be null");
    if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {//處理以classpath*:開頭的路徑
        // a class path resource (multiple resources for same name possible)
        if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {//路徑中包含*或?這樣的通配符
            // a class path resource pattern
            return findPathMatchingResources(locationPattern);
        }
        else {
        //路徑中不包含通配符
            // all class path resources with the given name
            return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
        }
    }
    else {
    //處理不以classpath*:開頭的路徑
        // Only look for a pattern after a prefix here
        // (to not get fooled by a pattern symbol in a strange prefix).
        int prefixEnd = locationPattern.indexOf(":") + ;
        if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {//路徑中包含*或?這樣的通配符
        // a file pattern
            return findPathMatchingResources(locationPattern);
        }
        else {
        //路徑中不包含通配符
        // a single resource with the given name
            return new Resource[] {getResourceLoader().getResource(locationPattern)};
        }
    }
}
           

由上面代碼我們可以看出在加載配置檔案時,以是否是以classpath*開頭分為2種情況處理場景,每種情況類在又根據路徑中是否包括通配符進行處理。

Spring源碼閱讀-- IOC容器資源定位

4.1、處理以classpath*開頭且不包含通配符

進入

findAllClassPathResources

函數看看

protected Resource[] findAllClassPathResources(String location) throws IOException {
    String path = location;
    if (path.startsWith("/")) {
        path = path.substring();
    }
    Set<Resource> result = doFindAllClassPathResources(path);
    if (logger.isDebugEnabled()) {
        logger.debug("Resolved classpath location [" + location + "] to resources " + result);
    }
    return result.toArray(new Resource[result.size()]);
}
protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
    Set<Resource> result = new LinkedHashSet<Resource>();
    ClassLoader cl = getClassLoader();
    Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
    while (resourceUrls.hasMoreElements()) {
        URL url = resourceUrls.nextElement();
        result.add(convertClassLoaderURL(url));
    }
    if ("".equals(path)) {
        // The above result is likely to be incomplete, i.e. only containing file system references.
        // We need to have pointers to each of the jar files on the classpath as well...
        addAllClassLoaderJarRoots(cl, result);
    }
    return result;
}
           

跟蹤

doFindAllClassPathResources

方法中的

ClassLoader cl = getClassLoader();

進入到

ClassLoader

的getResources()方法:

//ClassLoader.java
public Enumeration<URL> getResources(String name) throws IOException {
    @SuppressWarnings("unchecked")
    Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[];
    if (parent != null) {
        //如果存在父加載器,則向上疊代擷取資源
        tmp[] = parent.getResources(name);
    } else {
        //Bootstrap classLoader主要加載JVM自身工作需要的類,位于$JAVA_HOME/jre/lib/下的jar包
        tmp[] = getBootstrapResources(name);
    }
    tmp[] = findResources(name);

    return new CompoundEnumeration<>(tmp);
}
           

目前類加載器,如果存在父加載器,則向上疊代擷取資源, 是以能加到jar包裡面的資源檔案。

4.2、處理不以classpath*開頭且不包含通配符

就是一行代碼

new Resource[] {getResourceLoader().getResource(locationPattern)};
           

getResourceLoader()

方法擷取到就是預設的資源加載器

public ResourceLoader getResourceLoader() {
    return this.resourceLoader;
}
public PathMatchingResourcePatternResolver(ClassLoader classLoader) {
    this.resourceLoader = new DefaultResourceLoader(classLoader);
}
           

是以,我們進入

DefaultResourceLoader

類的

getResource

方法:

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)) {
        //如果以classpath開頭,則建立為一個ClassPathResource
        return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
    }
    else {
        try {
            // Try to parse the location as a URL...
            //以URL的方式加載資源,建立一個UrlResource.
            URL url = new URL(location);
            return new UrlResource(url);
        }
        catch (MalformedURLException ex) {
            // No URL -> resolve as resource path.
            return getResourceByPath(location);
        }
    }
}
           

由此可看出,不以“classpath*”的開頭路徑getResource()僅傳回路徑(包括jar包)中的一個且僅一個資源;對于多個比對的也隻傳回一個。

4.3、路徑包含通配符的

protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
    //擷取确定的根路徑(沒有通配符的最長路徑),如cn/javass/config-*.xml,則根路徑是cn/javass/;
    //如cn/**/config.xml,跟路徑是cn/
    String rootDirPath = determineRootDir(locationPattern);
    //含通配符的,如cn/javass/config-*.xml,則subPattern是config-*.xml
    String subPattern = locationPattern.substring(rootDirPath.length());
    //擷取跟路徑下所有資源
    Resource[] rootDirResources = getResources(rootDirPath);
    Set<Resource> result = new LinkedHashSet<Resource>();
    for (Resource rootDirResource : rootDirResources) {//查找滿足通配符的資源
        rootDirResource = resolveRootDirResource(rootDirResource);
        URL rootDirURL = rootDirResource.getURL();
        if (equinoxResolveMethod != null) {
            if (rootDirURL.getProtocol().startsWith("bundle")) {
                rootDirURL = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirURL);
                rootDirResource = new UrlResource(rootDirURL);
            }
        }
        if (rootDirURL.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
            result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirURL, subPattern, getPathMatcher()));
        }
        else if (ResourceUtils.isJarURL(rootDirURL) || isJarResource(rootDirResource)) {
            result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirURL, subPattern));
        }
        else {
            result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
        }
    }
    if (logger.isDebugEnabled()) {
        logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
    }
    return result.toArray(new Resource[result.size()]);
}
           

以“classpath*”的開頭路徑,getResources()加載類路徑(包括jar包)中的所有比對的資源。

至此, Spring IOC 容器在初始化時将配置的 Bean 定義資源檔案定位為 Spring 封裝的 Resource。

總結

來一張圖過一遍代碼執行簡要過程

Spring源碼閱讀-- IOC容器資源定位

參考

http://blog.csdn.net/ray_seu/article/details/50096889

http://blog.csdn.net/zl3450341/article/details/9306983

繼續閱讀