深入Spring IOC源码之ResourceLoader

本文深入探讨Spring框架中的ResourceLoader接口及其实现类,包括DefaultResourceLoader、FileSystemResourceLoader和ServletContextResourceLoader等,同时解析了ResourcePatternResolver接口及PathMatchingResourcePatternResolver的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这里写图片描述

深入Spring IOC源码之ResourceLoader
在《深入Spring IOC源码之Resource》中已经详细介绍了Spring中Resource的抽象,Resource接口有很多实现类,我们当然可以使用各自的构造函数创建符合需求的Resource实例,然而Spring提供了ResourceLoader接口用于实现不同的Resource加载策略,即将不同Resource实例的创建交给ResourceLoader来计算。

public interface ResourceLoader {

    //classpath

    String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;

    Resource getResource(String location);

    ClassLoader getClassLoader();

}

在ResourceLoader接口中,主要定义了一个方法:getResource(),它通过提供的资源location参数获取Resource实例,该实例可以是ClasPathResource、FileSystemResource、UrlResource等,但是该方法返回的Resource实例并不保证该Resource一定是存在的,需要调用exists方法判断。该方法需要支持一下模式的资源加载:

1.       URL位置资源,如”file:C:/test.dat”

2.       ClassPath位置资源,如”classpath:test.dat”

3.       相对路径资源,如”WEB-INF/test.dat”,此时返回的Resource实例根据实现不同而不同。

ResourceLoader接口还提供了getClassLoader()方法,在加载classpath下的资源时作为参数传入ClassPathResource。将ClassLoader暴露出来,对于想要获取ResourceLoader使用的ClassLoader用户来说,可以直接调用getClassLoader()方法获得,而不是依赖于Thread Context ClassLoader,因为有些时候ResourceLoader内部使用自定义的ClassLoader。

在实际开发中经常会遇到需要通过某种匹配方式查找资源,而且可能有多个资源匹配这种模式,在Spring中提供了ResourcePatternResolver接口用于实现这种需求,该接口继承自ResourceLoader接口,定义了自己的模式匹配接口:

public interface ResourcePatternResolver extends ResourceLoader {

    String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

    Resource[] getResources(String locationPattern) throws IOException;

}

ResourcePatternResolver定义了getResources()方法用于根据传入的locationPattern查找和其匹配的Resource实例,并以数组的形式返回,在返回的数组中不可以存在相同的Resource实例。ResourcePatternResolver中还定义了”classpath*:”模式,用于表示查找classpath下所有的匹配Resource。

在Spring中,对ResourceLoader提供了DefaultResourceLoader、FileSystemResourceLoader和ServletContextResourceLoader等单独实现,对ResourcePatternResolver接口则提供了PathMatchingResourcePatternResolver实现。并且ApplicationContext接口继承了ResourcePatternResolver,在实现中,ApplicationContext的实现类会将逻辑代理给相关的单独实现类,如PathMatchingResourceLoader等。在ApplicationContext中ResourceLoaderAware接口,可以将ResourceLoader(自身)注入到实现该接口的Bean中,在Bean中可以将其强制转换成ResourcePatternResolver接口使用(为了安全,强转前需要判断)。在Spring中对ResourceLoader相关类的类图如下:



DefaultResourceLoader类

DefaultResourceLoader是ResourceLoader的默认实现,AbstractApplicationContext继承该类(关于这个继承,简单吐槽一下,Spring内部感觉有很多这种个人感觉使用组合更合适的继承,比如还有AbstractBeanFactory继承自FactoryBeanRegisterySupport,这个让我看起来有点不习惯,而且也增加了类的继承关系)。它接收ClassLoader作为构造函数的参数,或使用不带参数的构造函数,此时ClassLoader使用默认的ClassLoader(一般为Thread Context ClassLoader),ClassLoader也可以通过set方法后继设置。

其最主要的逻辑实现在getResource方法中,该方法首先判断传入的location是否以”classpath:”开头,如果是,则创建ClassPathResource(移除”classpath:”前缀),否则尝试创建UrlResource,如果当前location没有定义URL的协议(即以”file:”、”zip:”等开头,比如使用相对路径”resources/META-INF/MENIFEST.MF),则创建UrlResource会抛出MalformedURLException,此时调用getResourceByPath()方法获取Resource实例。getResourceByPath()方法默认返回ClassPathContextResource实例,在FileSystemResourceLoader中有不同实现。

public Resource getResource(String location) {

    Assert.notNull(location, "Location must not be null");

    if (location.startsWith(CLASSPATH_URL_PREFIX)) {

        return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());

    }

    else {

        try {

            // Try to parse the location as a URL...

            URL url = new URL(location);

            return new UrlResource(url);

        }

        catch (MalformedURLException ex) {

            // No URL -> resolve as resource path.

            return getResourceByPath(location);

        }

    }

}

protected Resource getResourceByPath(String path) {

    return new ClassPathContextResource(path, getClassLoader());

}

FileSystemResourceLoader类

FileSystemResourceLoader继承自DefaultResourceLoader,它的getResource方法的实现逻辑和DefaultResourceLoader相同,不同的是它实现了自己的getResourceByPath方法,即当UrlResource创建失败时,它会使用FileSystemContextResource实例而不是ClassPathContextResource:

protected Resource getResourceByPath(String path) {

    if (path != null && path.startsWith("/")) {

        path = path.substring(1);

    }

    return new FileSystemContextResource(path);

}

使用该类时要特别注意的一点:即使location以”/”开头,资源的查找还是相对于VM启动时的相对路径而不是绝对路径(从以上代码片段也可以看出,它会先截去开头的”/”),这个和Servlet Container保持一致。如果需要使用绝对路径,需要添加”file:”前缀。

ServletContextResourceLoader类

ServletContextResourceLoader类继承自DefaultResourceLoader,和FileSystemResourceLoader一样,它的getResource方法的实现逻辑和DefaultResourceLoader相同,不同的是它实现了自己的getResourceByPath方法,即当UrlResource创建失败时,它会使用ServletContextResource实例:

protected Resource getResourceByPath(String path) {

    return new ServletContextResource(this.servletContext, path);

}

这里的path即使以”/”开头,也是相对ServletContext的路径,而不是绝对路径,要使用绝对路径,需要添加”file:”前缀。

PathMatchingResourcePatternResolver类

PathMatchingResourcePatternResolver类实现了ResourcePatternResolver接口,它包含了对ResourceLoader接口的引用,在对继承自ResourceLoader接口的方法的实现会代理给该引用,同时在getResources()方法实现中,当找到一个匹配的资源location时,可以使用该引用解析成Resource实例。默认使用DefaultResourceLoader类,用户可以使用构造函数传入自定义的ResourceLoader。

PathMatchingResourcePatternResolver还包含了一个对PathMatcher接口的引用,该接口基于路径字符串实现匹配处理,如判断一个路径字符串是否包含通配符(’*’、’?’),判断给定的path是否匹配给定的pattern等。Spring提供了AntPathMatcher对PathMatcher的默认实现,表达该PathMatcher是采用Ant风格的实现。其中PathMatcher的接口定义如下:

public interface PathMatcher {

    boolean isPattern(String path);

    boolean match(String pattern, String path);

    boolean matchStart(String pattern, String path);

    String extractPathWithinPattern(String pattern, String path);

}

isPattern(String path):

判断path是否是一个pattern,即判断path是否包含通配符:

public boolean isPattern(String path) {

    return (path.indexOf('*') != -1 || path.indexOf('?') != -1);

}

match(String pattern, String path):

判断给定path是否可以匹配给定pattern:

matchStart(String pattern, String path):

判断给定path是否可以匹配给定pattern,该方法不同于match,它只是做部分匹配,即当发现给定path匹配给定path的可能性比较大时,即返回true。在PathMatchingResourcePatternResolver中,可以先使用它确定需要全面搜索的范围,然后在这个比较小的范围内再找出所有的资源文件全路径做匹配运算。

在AntPathMatcher中,都使用doMatch方法实现,match方法的fullMatch为true,而matchStart的fullMatch为falseprotected boolean doMatch(String pattern, String path, boolean fullMatch)

doMatch的基本算法如下:

1.       检查pattern和path是否都以”/”开头或者都不是以”/”开头,否则,返回false。

2.       将pattern和path都以”/”为分隔符,分割成两个字符串数组pattArray和pathArray。

3.       从头遍历两个字符串数组,如果遇到两给字符串不匹配(两个字符串的匹配算法再下面介绍),返回false,否则,直到遇到pattArray中的”**”字符串,或pattArray和pathArray中有一个遍历完。

4.       如果pattArray遍历完:

a)         pathArray也遍历完,并且pattern和path都以”/”结尾或都不以”/”,返回true,否则返回false。

b)         pattArray没有遍历完,但fullMatch为false,返回true。

c)         pattArray只剩最后一个”*”,同时path以”/”结尾,返回true。

d)         pattArray剩下的字符串都是”**”,返回true,否则返回false。

5.       如果pathArray没有遍历完,而pattArray遍历完了,返回false。

6.       如果pathArray和pattArray都没有遍历完,fullMatch为false,而且pattArray下一个字符串为”**”时,返回true。

7.       从后开始遍历pathArray和pattArray,如果遇到两个字符串不匹配,返回false,否则,直到遇到pattArray中的”**”字符串,或pathArray和pattArray中有一个和之前的遍历索引相遇。

8.       如果是因为pathArray与之前的遍历索引相遇,此时,如果没有遍历完的pattArray所有字符串都是”**”,则返回true,否则,返回false。

9.       如果pathArray和pattArray中间都没有遍历完:

a)         去除pattArray中相邻的”**”字符串,并找到其下一个”**”字符串,其索引号为pattIdxTmp,他们的距离即为s

b)         从剩下的pathArray中的第i个元素向后查找s个元素,如果找到所有s个元素都匹配,则这次查找成功,记i为temp,如果没有找到这样的s个元素,返回false。

c)         将pattArray的起始索引设置为pattIdxTmp,将pathArray的索引号设置为temp+s,继续查找,直到pattArray或pathArray遍历完。

10.   如果pattArray没有遍历完,但剩下的元素都是”**”,返回true,否则返回false。

对路径字符串数组中的字符串匹配算法如下:

1.       记pattern为模式字符串,str为要匹配的字符串,将两个字符串转换成两个字符数组pattArray和strArray。

2.       遍历pattArray直到遇到’*’字符。

3.       如果pattArray中不存在’*’字符,则只有在pattArray和strArray的长度相同两个字符数组中所有元素都相同,其中pattArray中的’?’字符可以匹配strArray中的任何一个字符,否则,返回false。

4.       如果pattArray只包含一个’*’字符,返回true

5.       遍历pattArray和strArray直到pattArray遇到’*’字符或strArray遍历完,如果存在不匹配的字符,返回false。

6.       如果因为strArray遍历完成,而pattArray剩下的字符都是’*’,返回true,否则返回false

7.       从末尾开始遍历pattArray和strArray,直到pattArray遇到’*’字符,或strArray遇到之前的遍历索引,中间如果遇到不匹配字符,返回false

8.       如果strArray遍历完,而剩下的pattArray字符都是’*’字符,返回true,否则返回false

9.       如果pattArray和strArray都没有遍历完(类似之前的算法):

a)         去除pattArray相邻的’*’字符,查找下一个’*’字符,记其索引号为pattIdxTmp,两个’*’字符的相隔距离为s

b)         从剩下的strArray中的第i个元素向后查找s个元素,如果有找到所有s个元素都匹配,则这次查找成功,记i为temp,如果没有到这样的s个元素,返回false。

c)         将pattArray的起始索引设置为pattIdxTmp,strArray的起始索引设置为temp+s,继续查找,直到pattArray或strArray遍历完。

10.   如果pattArray没有遍历完,但剩下的元素都是’*’,返回true,否则返回false

String extractPathWithinPattern(String pattern, String path):

去除path中和pattern相同的字符串,只保留匹配的字符串。比如如果pattern为”/doc/csv/*.htm”,而path为”/doc/csv/commit.htm”,则该方法的返回值为commit.htm。该方法默认pattern和path已经匹配成功,因而算法比较简单:

以’/’分割pattern和path为两个字符串数组pattArray和pathArray,遍历pattArray,如果该字符串包含’*’或’?’字符,则并且pathArray的长度大于当前索引号,则将该字符串添加到结果中。

遍历完pattArray后,如果pathArray长度大于pattArray,则将剩下的pathArray都添加到结果字符串中。

最后返回该字符串。

不过也正是因为该算法实现比较简单,因而它的结果貌似不那么准确,比如pattern的值为:/com/**/levin/**/commit.html,而path的值为:/com/citi/cva/levin/html/commit.html,其返回结果为:citi/levin/commit.html

现在言归正传,看一下PathMatchingResourcePatternResolver中的getResources方法的实现:

public Resource[] getResources(String locationPattern) throws IOException {

    Assert.notNull(locationPattern, "Location pattern must not be null");

    if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {

        // 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 {

        // 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(":") + 1;

        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下的资源,相同名字的资源可能存在多个,如果使用”classpath*:”作为前缀,表明需要找到classpath下所有该名字资源,因而需要调用findClassPathResources方法查找classpath下所有该名称的Resource,对非classpath下的资源,对于不存在模式字符的location,一般认为一个location对应一个资源,因而直接调用ResourceLoader.getResource()方法即可(对classpath下没有以”classpath*:”开头的location也适用)。

findClassPathResources方法实现相对比较简单:

适用ClassLoader.getResources()方法,遍历结果URL集合,将每个结果适用UrlResource封装,最后组成一个Resource数组返回即可。

对包含模式匹配字符的location来说,需要调用findPathMatchingResources方法:

protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {

    String rootDirPath = determineRootDir(locationPattern);

    String subPattern = locationPattern.substring(rootDirPath.length());

    Resource[] rootDirResources = getResources(rootDirPath);

    Set result = new LinkedHashSet(16);

    for (int i = 0; i < rootDirResources.length; i++) {

        Resource rootDirResource = resolveRootDirResource(rootDirResources[i]);

        if (isJarResource(rootDirResource)) {

            result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));

        }

        else {

            result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));

        }

    }

    if (logger.isDebugEnabled()) {

        logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);

    }

    return (Resource[]) result.toArray(new Resource[result.size()]);

}

1.       determinRootDir()方法返回locationPattern中最长的没有出现模式匹配字符的路径

2.       subPattern则表示rootDirPath之后的包含模式匹配字符的路径信pattern

3.       使用getResources()获取rootDirPath下的所有资源数组。

4.       遍历这个数组。

a)         对jar中的资源,使用doFindPathMatchingJarResources()方法来查找和匹配。

b)         对非jar中资源,使用doFindPathMatchingFileResources()方法来查找和匹配。

doFindPathMatchingJarResources()实现:

1.       计算当前Resource在Jar文件中的根路径rootEntryPath。

2.       遍历Jar文件中所有entry,如果当前entry名以rootEntryPath开头,并且之后的路径信息和之前从patternLocation中截取出的subPattern使用PathMatcher匹配,若匹配成功,则调用rootDirResource.createRelative方法创建一个Resource,将新创建的Resource添加入结果集中。

doFindPathMatchingFileResources()实现:

1.       获取要查找资源的根路径(根路径全名)

2.       递归获得根路径下的所有资源,使用PathMatcher匹配,如果匹配成功,则创建FileSystemResource,并将其加入到结果集中。在递归进入一个目录前首先调用PathMatcher.matchStart()方法,以先简单的判断是否需要递归进去,以提升性能。

protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set result) throws IOException {

    if (logger.isDebugEnabled()) {

        logger.debug("Searching directory [" + dir.getAbsolutePath() +

                "] for files matching pattern [" + fullPattern + "]");

    }

    File[] dirContents = dir.listFiles();

    if (dirContents == null) {

        throw new IOException("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");

    }

    for (int i = 0; i < dirContents.length; i++) {

        File content = dirContents[i];

        String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");

        if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {

            doRetrieveMatchingFiles(fullPattern, content, result);

        }

        if (getPathMatcher().match(fullPattern, currPath)) {

            result.add(content);

        }

    }

}

最后,需要注意的是,由于ClassLoader.getResources()方法存在的限制,当传入一个空字符串时,它只能从classpath的文件目录下查找,而不会从Jar文件的根目录下查找,因而对”classpath*:”前缀的资源来说,找不到Jar根路径下的资源。即如果我们有以下定义:”classpath*:*.xml”,如果只有在Jar文件的根目录下存在*.xml文件,那么这个pattern将返回空的Resource数组。解决方法是不要再Jar文件根目录中放文件,可以将这些文件放到Jar文件中的resources、config等目录下去。并且也不要在”classpath*:”之后加一些通配符,如”classpath*:**/*Enum.class”,至少在”classpath*:”后加入一个不存在通配符的路径名。

ServletContextResourcePatternResolver类

ServletContextResourcePatternResolver类继承自PathMatchingResourcePatternResolver类,它重写了父类的文件查找逻辑,即对ServletContextResource资源使用ServletContext.getResourcePaths()方法来查找参数目录下的文件,而不是File.listFiles()方法:

protected Set doFindPathMatchingFileResources(Resource rootDirResource, String subPattern) throws IOException {

    if (rootDirResource instanceof ServletContextResource) {

        ServletContextResource scResource = (ServletContextResource) rootDirResource;

        ServletContext sc = scResource.getServletContext();

        String fullPattern = scResource.getPath() + subPattern;

        Set result = new LinkedHashSet(8);

        doRetrieveMatchingServletContextResources(sc, fullPattern, scResource.getPath(), result);

        return result;

    }

    else {

        return super.doFindPathMatchingFileResources(rootDirResource, subPattern);

    }

}

AbstractApplicationContext对ResourcePatternResolver接口的实现

在AbstractApplicationContext中,对ResourcePatternResolver的实现只是简单的将getResources()方法的实现代理给resourcePatternResolver字段,而该字段默认在AbstractApplicationContext创建时新建一个PathMatchingResourcePatternResolver实例:

public AbstractApplicationContext(ApplicationContext parent) {

    this.parent = parent;

    this.resourcePatternResolver = getResourcePatternResolver();

}

protected ResourcePatternResolver getResourcePatternResolver() {

    return new PathMatchingResourcePatternResolver(this);

}

public Resource[] getResources(String locationPattern) throws IOException {

    return this.resourcePatternResolver.getResources(locationPattern);

}
<think>好的,我现在需要帮助用户理解Spring IOC容器的源码实现和工作原理。用户之前已经提供了一些相关的引用内容,我需要结合这些内容来组织一个清晰且详细的回答。 首先,我应该先回顾用户提供的引用,找出关键点。引用1提到BeanFactory和ApplicationContext是两个核心接口,需要分析它们的源码差异。引用2提到了IoC容器的基本概念、关键接口、bean的生命周期和循环依赖的解决方法。引用3提到了事件发布器和监听器的初始化,这部分涉及观察者模式。引用4是关于prepareRefresh方法的预处理步骤,包括初始化属性和早期事件处理。引用5提到了Bean工厂后置处理器和Bean实例化的完成。 接下来,我需要确定回答的结构。通常,源码分析可以从容器启动流程、Bean生命周期管理、核心接口差异、设计模式应用等方面展开。用户的问题比较广泛,所以需要覆盖主要部分,同时保持结构清晰。 第一步,容器启动流程。根据引用4中的prepareRefresh方法,这是刷新前的预处理步骤,包括设置启动时间、状态,初始化属性,验证环境变量,保存早期事件。然后,引用5中的invokeBeanFactoryPostProcessors和finishBeanFactoryInitialization是后续步骤,处理Bean工厂后置处理器和实例化Bean。需要详细说明这些步骤的顺序和作用。 第二步,Bean的生命周期管理。这里需要结合BeanFactory和ApplicationContext的区别,可能涉及Bean的创建、初始化、依赖注入、销毁等阶段。引用2中提到bean的生命周期是一个重点,可能需要详细描述每个阶段涉及的接口和方法,比如BeanPostProcessor。 第三步,关键接口分析。BeanFactory作为基础接口,而ApplicationContext扩展了更多功能,如事件发布、国际化等。引用1和引用2都提到了这两个接口,需要比较它们的源码和功能差异,比如ApplicationContext如何继承BeanFactory并添加额外特性。 第四部分,设计模式的应用。例如,观察者模式在事件处理中的应用,如引用3中的事件发布器和监听器的初始化。可能还有工厂模式在Bean创建中的使用,单例模式在Bean作用域中的应用等。 第五部分,解决循环依赖。引用2提到循环依赖是高级特性,需要解释Spring如何通过三级缓存解决这个问题,可能涉及源码中的DefaultSingletonBeanRegistry类中的getSingleton方法及相关处理。 在组织内容时,要确保每个部分都有对应的源码引用和解释,例如在容器启动流程中,可以引用prepareRefresh的代码片段,说明其作用。同时,结合用户提供的引用,正确标注引用标识,如[^4]、[^5]等。 另外,用户可能需要示例代码或流程图来辅助理解,但根据问题描述,用户要求的是文本回答,所以需要用清晰的步骤和子标题来划分内容。比如使用“1. 容器启动流程”、“2. Bean生命周期管理”等。 最后,生成相关问题,可能需要围绕源码分析、设计模式、具体实现细节等方面提问,例如询问BeanFactory和ApplicationContext的区别,或者如何扩展BeanPostProcessor接口。 需要确保回答符合用户的要求:结构清晰,分步骤解释,引用正确,并且使用中文。同时,避免使用Markdown格式,但根据用户提供的示例,可能需要使用代码块来展示源码片段,如引用4中的prepareRefresh方法代码。 现在需要整合这些思路,形成一个连贯的回答,涵盖用户提到的所有关键点,并正确引用提供的参考文献。</think>### Spring IOC 源码分析及详细解读 #### 一、容器启动流程 Spring IOC 容器的启动流程围绕 `ApplicationContext` 的 `refresh()` 方法展开,核心步骤如下: 1. **预处理阶段 (`prepareRefresh()`)** - 记录启动时间,设置容器状态为激活。 - 初始化环境变量和配置参数(如 `initPropertySources()`)。 - 验证必要属性是否合法(如 `validateRequiredProperties()`)。 - 准备早期事件存储队列 `earlyApplicationEvents` [^4]。 2. **加载Bean定义 (`obtainFreshBeanFactory()`)** - 创建 `BeanFactory`(默认实现为 `DefaultListableBeanFactory`)。 - 解析 XML 或注解配置,将 Bean 定义注册到 `BeanDefinitionMap` 中。 3. **处理后置处理器 (`invokeBeanFactoryPostProcessors()`)** - 调用 `BeanFactoryPostProcessor` 实现类(如 `ConfigurationClassPostProcessor`),完成配置类解析、`@Bean` 方法解析等[^5]。 4. **初始化事件和监听器** - 通过 `initApplicationEventMulticaster()` 初始化事件广播器。 - 通过 `registerListeners()` 注册监听器,支持观察者模式[^3]。 5. **实例化单例Bean (`finishBeanFactoryInitialization()`)** - 遍历 `BeanDefinitionMap`,对非懒加载的单例 Bean 进行实例化、依赖注入和初始化[^5]。 --- #### 二、Bean生命周期管理 Bean 的生命周期由以下接口控制: ```java // 示例代码:Bean生命周期关键接口 public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object bean, String beanName); Object postProcessAfterInitialization(Object bean, String beanName); } ``` 1. **实例化**:通过反射调用构造函数。 2. **属性注入**:通过 `populateBean()` 方法注入依赖(如 `@Autowired`)。 3. **初始化**: - 调用 `InitializingBean.afterPropertiesSet()`。 - 执行自定义初始化方法(如 `@PostConstruct`)。 4. **销毁**:容器关闭时调用 `DisposableBean.destroy()` 或自定义销毁方法。 --- #### 三、核心接口对比:BeanFactory vs ApplicationContext | 特性 | BeanFactory | ApplicationContext | |---------------------|---------------------------|----------------------------------------| | Bean实例化 | 延迟加载 | 支持预加载(通过 `refresh()` 触发) | | 国际化支持 | 无 | 有(`MessageSource`) | | 事件发布 | 无 | 有(`ApplicationEventPublisher`) | | AOP集成 | 需手动配置 | 自动集成 | | 资源访问 | 基础支持 | 扩展支持(如 `ResourceLoader`) | ApplicationContext 通过组合模式扩展了 BeanFactory 的功能[^1]。 --- #### 四、设计模式应用 1. **工厂模式**:`BeanFactory` 是工厂接口,`DefaultListableBeanFactory` 是具体实现。 2. **观察者模式**:事件广播器 (`ApplicationEventMulticaster`) 和监听器 (`ApplicationListener`) 实现解耦[^3]。 3. **模板方法模式**:`AbstractApplicationContext.refresh()` 定义了容器刷新的步骤骨架。 --- #### 五、循环依赖解决 Spring 通过**三级缓存**解决循环依赖: 1. **一级缓存** (`singletonObjects`):存放完全初始化的 Bean。 2. **二级缓存** (`earlySingletonObjects`):存放未完成属性注入的早期 Bean 引用。 3. **三级缓存** (`singletonFactories`):存放 Bean 工厂对象,用于生成早期代理对象。 源码关键方法: ```java // DefaultSingletonBeanRegistry 类 protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值