0


重看Spring聚焦ApplicationContext分析

干货分享,感谢您的阅读!

在现代软件开发中,依赖注入(Dependency Injection, DI)和控制反转(Inversion of Control, IoC)已成为构建灵活且可维护应用程序的核心原则。作为 Spring 框架中实现这些原则的关键组件之一,ApplicationContext 扮演着不可或缺的角色。它不仅负责管理应用程序的生命周期,还充当了应用中的核心配置和资源管理中心。

本篇文章将深入探讨 ApplicationContext 的设计理念、主要功能以及实现细节,帮助读者全面理解其在 Spring 应用中的重要性。通过分析相关接口与实现类,我们将揭示 ApplicationContext 如何高效地管理 bean 的创建、依赖注入和事件发布等关键功能。此外,我们还将结合实际应用场景和示例代码,展示其灵活性与可扩展性,使开发者能够在项目中更好地应用这些设计理念。

无论您是 Spring 框架的初学者,还是希望进一步深化对其内部机制理解的开发者,这篇文章都将为您提供宝贵的见解与实用的知识,助力您在构建现代化企业级应用中取得更大的成功。

一、理解下ApplicationContext的设计

(一)功能性的理解

ApplicationContext 提供了一个轻量级、灵活、可扩展的容器,能帮助我们更加轻松地构建和管理复杂的应用程序,其通过依赖注入和控制反转等技术,降低了组件之间的耦合度,提高了代码的可维护性和可测试性,同时还提供了丰富的功能和扩展点可以更加灵活地满足不同项目的需求。

从基本的官方网站 Spring | Home中来重新体会的话,基本的功能性可以初步总结以下几点:

  1. Bean 实例化和管理:创建、配置和管理应用程序中的 bean 实例,可以根据配置文件或注解中的信息来实例化 bean,并且负责处理 bean 之间的依赖关系。
  2. 依赖注入(DI):支持依赖注入,即自动将 bean 之间的依赖关系注入到相应的属性中。可以通过构造函数、Setter 方法或字段注入的方式来实现依赖注入,降低组件之间的耦合度。
  3. 控制反转(IoC):实现了控制反转,即由容器负责管理组件的生命周期和依赖关系,而不是由组件自己来管理。
  4. AOP 支持:提供了对面向切面编程(AOP)的支持,可以在不修改原有代码的情况下,通过切面来实现诸如日志记录、事务管理、安全性等与业务逻辑无关的功能。
  5. 事件传播:提供了事件传播机制,允许 bean 之间进行通信,并且可以基于事件来触发特定的逻辑。使组件之间解耦更彻底,同时也提高了应用程序的可扩展性和灵活性。
  6. 国际化支持:支持国际化,允许开发人员在应用程序中使用多种语言和地区的资源。提供了统一接口来访问国际化资源,并且可根据用户的语言和地区设置动态加载相应的资源。
  7. 资源管理:不仅管理 bean,还管理应用程序中的其他资源,如消息、国际化资源、文件等。它提供了统一的接口来访问这些资源,并提供了便利的方法来加载和管理它们。
  8. 生命周期管理:负责管理 bean 的生命周期,包括实例化、初始化、销毁等过程。可以通过实现特定的接口或注解来定义 bean 的生命周期回调方法,从而在 bean 的生命周期中执行特定的逻辑。

更多理解性的内容还是通过官方文档自行体会,以上只是个人视角。

(二)ApplicationContext 结构类图

Spring Framework 中的 ApplicationContext 接口是 Spring 容器的核心接口之一,它负责管理应用程序中的 bean 并提供了各种功能。除了 ApplicationContext 接口之外,Spring 还提供了一些与其相关的子接口,这些子接口通常根据具体的使用场景和需求来扩展 ApplicationContext 的功能。

在这个类图中:

  • ApplicationContext 继承了 BeanFactory,因此拥有 BeanFactory 的功能,并增加了一些额外的特性。
  • ApplicationContext 还实现了 ApplicationEventPublisher、ResourceLoader、MessageSource 和 EnvironmentCapable 接口,提供了事件发布、资源加载、国际化消息处理和环境相关功能。
  • ApplicationContext 中的属性包括父级 ApplicationContext、BeanFactory、事件发布器、资源加载器、消息源和环境对象。
  • ApplicationContext 提供了访问这些属性的公共方法,例如获取父级 ApplicationContext、获取 BeanFactory、发布事件、获取资源加载器、获取消息源和获取环境对象等。

本文后面会针对其重要的接口和子接口进行重新的学习和总结分享。

二、ApplicationContext根接口

(一)源码展示

展示ApplicationContext 接口的部分源码,:

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
        MessageSource, ApplicationEventPublisher, ResourceLoader {
    
    // 获取父级 ApplicationContext
    @Nullable
    ApplicationContext getParent();
    
    // 获取唯一 ID
    String getId();
    
    // 获取应用程序名称
    String getApplicationName();
    
    // 获取应用程序上下文显示名称
    String getDisplayName();
    
    // 获取启动时间
    long getStartupDate();
    
    // 获取所有 Bean 的名称
    String[] getBeanDefinitionNames();
    
    // 获取所有 Bean 的数量
    int getBeanDefinitionCount();
    
    // 检查是否包含指定名称的 Bean
    boolean containsBeanDefinition(String beanName);
    
    // 获取指定名称的 Bean 的类型
    @Nullable
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;
    
    // 获取指定名称的 Bean 的别名
    String[] getAliases(String name);
    
    // 判断指定名称的 Bean 是否是单例
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
    
    // 判断指定名称的 Bean 是否是原型
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
    
    // 判断指定名称的 Bean 是否是当前上下文的 Bean
    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
    
    // 判断指定名称的 Bean 是否是指定类型的 Bean
    boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
    
    // 获取指定名称的 Bean
    @Nullable
    <T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;
    
    // 获取指定名称的 Bean
    @Nullable
    Object getBean(String name) throws BeansException;
    
    // 获取指定类型的所有 Bean
    <T> Map<String, T> getBeansOfType(@Nullable Class<T> type) throws BeansException;
    
    // 获取指定类型的所有 Bean 的名称
    String[] getBeanNamesForType(@Nullable Class<?> type);
    
    // 获取指定类型的所有 Bean 的名称,包括非单例 Bean
    String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit);
    
    // 获取所有监听指定事件类型的监听器
    <T> Map<String, T> getBeansOfType(@Nullable Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
            throws BeansException;
    
    // 获取指定类型的 Bean,如果有多个符合条件的 Bean,则抛出异常
    <T> T getRequiredBeanOfType(@Nullable Class<T> type) throws BeansException;
    
    // 获取指定类型的 Bean 的名称,如果有多个符合条件的 Bean,则抛出异常
    String[] getBeanNamesForType(@Nullable ResolvableType type);
    
    // 获取指定类型的 Bean,如果有多个符合条件的 Bean,则抛出异常
    <T> Map<String, T> getBeansOfType(@Nullable ResolvableType type) throws BeansException;
    
    // 获取指定类型的 Bean,如果有多个符合条件的 Bean,则抛出异常
    <T> T getBean(Class<T> requiredType) throws BeansException;
    
    // 获取指定类型的 Bean 的名称,如果有多个符合条件的 Bean,则抛出异常
    String[] getBeanNamesForType(ResolvableType type);
    
    // 获取指定类型的 Bean,如果有多个符合条件的 Bean,则抛出异常
    <T> Map<String, T> getBeansOfType(ResolvableType type) throws BeansException;
    
    // 获取指定类型的 Bean,如果有多个符合条件的 Bean,则抛出异常
    <T> T getBeanProvider(Class<T> requiredType) throws BeansException;
    
    // 是否是活跃的
    boolean isActive();
}

可以从展示的部分直观的看到其定义了许多方法用于获取和操作 ApplicationContext 中的 bean。它继承了 EnvironmentCapable、ListableBeanFactory、HierarchicalBeanFactory、MessageSource、ApplicationEventPublisher 和 ResourceLoader 等接口,提供了丰富的功能来管理应用程序中的 bean,并且提供了许多便捷的方法来获取 bean 的信息。

(二)分析说明

ApplicationContext 是 Spring Framework 中的核心接口之一,其提供了一种高级的容器机制,用于管理应用程序中的组件。从上面的基本源码上,我们可以提炼出以下的基本功能分析(这里不做全面的深入,因为还是太多了):

  1. Bean工厂方法: ApplicationContext 提供了丰富的方法来访问应用程序中的组件,这些方法是从 ListableBeanFactory 接口继承而来的。通过这些方法,可以方便地获取、检查和操作应用程序中的各种组件,从而实现对应用程序的灵活管理和配置。
  2. 加载文件资源: ApplicationContext 通过实现 ResourceLoader 接口,提供了一种通用的方式来加载文件资源。这些资源可以是位于类路径、文件系统、URL 等位置的文件,通过统一的接口可以方便地进行加载和访问,从而实现对应用程序资源的统一管理。
  3. 发布事件: 通过实现 ApplicationEventPublisher 接口具备了发布事件的能力,可以向注册的监听器发布事件消息,从而实现组件之间的通信和解耦。
  4. 解析消息和国际化支持: ApplicationContext 实现了 MessageSource 接口,支持消息的解析和国际化。通过 MessageSource,可以方便地在应用程序中实现多语言支持,根据不同的地区和语言加载相应的消息资源,从而提高了应用程序的可用性和用户体验。
  5. 父子上下文继承: ApplicationContext 支持从父上下文继承,子上下文中的定义优先级更高。意味着可以在整个应用程序中共享父上下文中的 bean,同时每个子上下文可以拥有自己的 bean 定义,实现更加灵活的应用程序结构。这种层级关系的管理机制使得应用程序更加模块化和可维护。

除了以上提到的功能外,ApplicationContext 还具备对应用程序中的 bean 进行生命周期管理的能力,同时可以检测和调用实现了 ApplicationContextAware、ResourceLoaderAware、ApplicationEventPublisherAware 和 MessageSourceAware 接口的 bean,使得这些 bean 可以获取对 ApplicationContext、ResourceLoader、ApplicationEventPublisher 和 MessageSource 的引用,并在运行时进行相应的操作和处理。

三、子接口ConfigurableApplicationContext

(一)源码展示

ConfigurableApplicationContext 是 ApplicationContext 接口重要的子接口,它继承了 ApplicationContext 接口,并添加了一些额外的配置功能。以下是 ConfigurableApplicationContext 接口的部分源码展示:

public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {

    // 设置应用程序上下文的 ID
    void setId(String id);

    // 设置应用程序名称
    void setApplicationName(String applicationName);

    // 设置父级应用程序上下文
    void setParent(@Nullable ApplicationContext parent);

    // 添加 BeanFactoryPostProcessor,用于在 bean 工厂初始化之前对 bean 定义进行后置处理
    void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor);

    // 注册一个关闭钩子,用于在 JVM 关闭时关闭应用程序上下文
    void registerShutdownHook();

    // 刷新应用程序上下文,启动容器并且在必要时触发 bean 工厂后置处理器和 bean 后置处理器的注册
    void refresh() throws BeansException, IllegalStateException;

    // 关闭应用程序上下文,释放所有资源并触发 destroy 方法
    void close();

    // 销毁应用程序上下文,释放所有资源
    @Override
    void destroy();

    // 获取环境信息
    @Override
    ConfigurableEnvironment getEnvironment();
}

在这个接口中,可以看到一些额外的配置功能,例如设置应用程序上下文的 ID、应用程序名称、父级上下文等。另外,还提供了添加 BeanFactoryPostProcessor、注册关闭钩子、刷新和关闭应用程序上下文等方法,用于配置和管理应用程序上下文的生命周期。

注意的是,ConfigurableApplicationContext 接口继承了 Lifecycle 和 Closeable 接口,这意味着它具备了管理生命周期和关闭资源的能力。因此,任何实现了 ConfigurableApplicationContext 接口的类都应该实现这些接口中定义的方法,以确保应用程序上下文的正常运行和关闭。

(二)分析理解

ConfigurableApplicationContext 接口的设计旨在提供一种可配置的、可定制化的应用程序上下文,以满足不同场景下的需求。具体理解可以从这几个方面展开:

  1. 灵活的配置功能: ConfigurableApplicationContext 接口提供了一系列设置方法,如 setId、setApplicationName、setParent 等,用于对应用程序上下文进行配置。通过这些方法,可以灵活地配置应用程序上下文的各种属性,以满足不同应用场景下的需求。例如,可以为应用程序上下文设置唯一的 ID、指定应用程序的名称、设置父级上下文等。
  2. 后置处理器的支持: ConfigurableApplicationContext 接口提供了添加 BeanFactoryPostProcessor 的方法 addBeanFactoryPostProcessor。这意味着在应用程序上下文刷新之前,可以注册一个或多个 BeanFactoryPostProcessor,用于在 bean 工厂初始化之前对 bean 定义进行后置处理。通过这种机制,可以在容器启动之前对 bean 进行定制化的处理,例如修改 bean 的定义、添加新的 bean 定义等。
  3. 生命周期管理: ConfigurableApplicationContext 继承了 Lifecycle 接口,具备了管理生命周期的能力。它提供了刷新应用程序上下文的方法 refresh,用于启动容器并且在必要时触发 bean 工厂后置处理器和 bean 后置处理器的注册。另外,它还提供了关闭应用程序上下文的方法 close,用于释放所有资源并触发 destroy 方法。通过这些方法,可以对应用程序上下文的生命周期进行管理,确保容器的正常运行和关闭。
  4. 环境配置: ConfigurableApplicationContext 提供了获取环境信息的方法 getEnvironment,用于获取应用程序上下文的环境配置。通过环境信息,可以方便地获取应用程序的配置属性,例如配置文件中的属性、系统属性等,为应用程序的配置和定制提供了更多的灵活性和可定制性。

四、深入理解几个父接口

ConfigurableApplicationContext 是 ApplicationContext 接口重要的子接口,ApplicationContext本身还实现了几个父接口,这些父接口共同组成了ApplicationContext扩展的几个核心特性。

(一)EnvironmentCapable

EnvironmentCapable 接口的设计和实现旨在提供一种统一的方式来获取应用程序的环境配置信息,以实现组件之间的解耦合、环境配置的一致性,并且易于扩展和定制。

1.源码展示分析

EnvironmentCapable 接口定义了获取 Environment 对象的方法,是 Spring Framework 中用于获取应用程序环境配置信息的核心接口之一。以下是 EnvironmentCapable 接口的部分源码展示:

public interface EnvironmentCapable {

    // 获取应用程序环境对象
    Environment getEnvironment();
}

在这个接口中,只定义了一个方法 getEnvironment,用于获取应用程序的环境对象。Environment 对象提供了访问应用程序配置属性、系统属性、环境变量等环境信息的方法,使得应用程序可以方便地获取和管理这些配置信息。通过实现 EnvironmentCapable 接口并提供相应的实现,可以使得应用程序具备了获取环境配置信息的能力,从而可以根据不同的环境配置来调整应用程序的行为和功能。

2.理解其与ApplicationContext的关系

EnvironmentCapable 接口与 ApplicationContext 的关系在于 ApplicationContext 接口继承了 EnvironmentCapable 接口。这意味着任何实现了 ApplicationContext 接口的类都必须提供对环境配置信息的获取能力,即实现了 getEnvironment 方法

ApplicationContext 接口继承了 EnvironmentCapable 接口的原因在于,Spring 应用程序上下文需要能够获取应用程序的环境配置信息,以便于在运行时根据配置信息进行相应的操作和调整。环境配置信息可能包括应用程序的属性配置、系统属性、环境变量等。通过实现 EnvironmentCapable 接口,ApplicationContext 接口承担了获取环境配置信息的责任,使得应用程序上下文具备了对环境配置的统一访问能力。

EnvironmentCapable 接口与 ApplicationContext 的关系是一种接口继承关系,它体现了 ApplicationContext 接口对环境配置信息的依赖和需求。通过这种关系,ApplicationContext 可以通过 getEnvironment 方法获取环境配置信息,从而实现了对环境配置的统一管理和访问。

3.简单的应用举例说明

我现在有个应用程序,可能会根据不同的环境配置采取不同的行动。例如,在开发环境中,可能希望连接到本地数据库,而在生产环境中,可能希望连接到远程数据库。针对这样的需求我们就可以通过使用 EnvironmentCapable 接口和 ApplicationContext,可以轻松地实现这种行为差异。

下面是一个简单的示例,演示了如何在 Spring 应用程序中使用 EnvironmentCapable 接口和 ApplicationContext 来根据不同的环境配置加载不同的数据库连接信息:

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.Environment;

public class DatabaseConfigExample {

    public static void main(String[] args) {
        // 根据不同的环境加载不同的配置类
        ApplicationContext context = new AnnotationConfigApplicationContext(DatabaseConfig.class);

        // 获取 Environment 对象
        Environment environment = context.getEnvironment();

        // 从环境中获取数据库连接信息
        String databaseUrl = environment.getProperty("database.url");
        String databaseUsername = environment.getProperty("database.username");
        String databasePassword = environment.getProperty("database.password");

        // 连接到数据库
        System.out.println("Connecting to database:");
        System.out.println("URL: " + databaseUrl);
        System.out.println("Username: " + databaseUsername);
        System.out.println("Password: " + databasePassword);
    }
}

代码中定义了一个 DatabaseConfig 类,它根据不同的环境加载不同的数据库连接配置。然后,我们通过 ApplicationContext 获取到 Environment 对象,从中获取数据库连接信息。通过这种方式,可以根据不同的环境配置加载不同的数据库连接信息,从而实现了环境配置的灵活管理。

在实际的应用程序中,你可以将开发环境、测试环境和生产环境的配置信息分别存放在不同的配置文件中,然后通过 Spring 的配置功能来加载相应的配置文件,并根据需要获取相应的环境配置信息。这样就能够实现在不同环境下对应用程序的配置和行为进行灵活管理。

(二)MessageSource

ApplicationContext 中通常会包含一个 MessageSource 对象,用于解析和获取消息文本。通过 ApplicationContext 提供的方法,可以方便地获取 MessageSource 对象,并使用它来获取应用程序的国际化消息。这样就实现了 ApplicationContext 对国际化功能的支持和扩展。

1.源码展示分析

MessageSource 是 Spring Framework 提供的国际化支持接口,用于解析和获取消息,支持应用程序的多语言和国际化功能。以下是 MessageSource 接口的部分源码展示:

public interface MessageSource {

    // 根据消息的代码和参数获取对应的消息文本
    String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale) throws NoSuchMessageException;

    // 根据消息的代码和参数获取对应的消息文本,如果找不到对应的消息,则返回 null
    @Nullable
    String getMessage(String code, @Nullable Object[] args, Locale locale);

    // 根据消息的代码获取对应的消息文本,如果找不到对应的消息,则返回 null
    @Nullable
    String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
}

MessageSource 接口的设计和实现目的在于提供一种统一的方式来解析和获取消息文本,以支持应用程序的多语言和国际化功能。通过调用 getMessage 方法并传入相应的参数,可以根据消息的代码、参数和区域设置获取对应的消息文本,从而实现应用程序的国际化支持。

2.理解应用举例说明

实现一个简单的控制器,用于处理用户登录请求,并返回相应的消息,要求使用 MessageSource 接口来实现对登录成功和失败消息的国际化支持。

首先,我们需要在 Spring 配置文件中配置 MessageSource bean,并提供相应的消息资源文件,如 messages.properties 和 messages_zh.properties。这些文件包含了登录成功和失败消息的文本,分别对应不同的语言版本。

示例 messages.properties 文件内容如下:

login.success=Login successful.
login.error=Login failed. Please check your username and password.

示例 messages_zh.properties 文件内容如下:

login.success=登录成功。
login.error=登录失败。请检查用户名和密码。

事实上,在 Spring 应用程序中,ApplicationContext 是一个全局的容器,它在应用程序启动时被加载,并负责管理各个组件的生命周期和依赖关系。我们的控制器通常会被 Spring 容器管理,而 ApplicationContext 会自动地注入到控制器中。按当前的需求简单实现代码如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Locale;

@Controller
public class LoginController {

    @Autowired
    private ApplicationContext applicationContext;

    @PostMapping("/login")
    @ResponseBody
    public String login(@RequestParam("username") String username, @RequestParam("password") String password) {
        // 假设这里进行了用户名和密码的验证
        boolean isAuthenticated = authenticate(username, password);

        if (isAuthenticated) {
            // 获取当前请求的语言环境
            Locale locale = LocaleContextHolder.getLocale();
            // 从 ApplicationContext 中获取 MessageSource 对象
            MessageSource messageSource = applicationContext.getBean(MessageSource.class);
            // 获取登录成功消息
            String successMessage = messageSource.getMessage("login.success", null, locale);
            return successMessage;
        } else {
            // 获取当前请求的语言环境
            Locale locale = LocaleContextHolder.getLocale();
            // 从 ApplicationContext 中获取 MessageSource 对象
            MessageSource messageSource = applicationContext.getBean(MessageSource.class);
            // 获取登录失败消息
            String errorMessage = messageSource.getMessage("login.error", null, locale);
            return errorMessage;
        }
    }

    private boolean authenticate(String username, String password) {
        // 这里省略了用户名密码验证的逻辑,简单返回 true
        return true;
    }
}

(三)ApplicationEventPublisher

ApplicationContext 接口继承了 ApplicationEventPublisher 接口,这意味着 ApplicationContext 具有发布事件的能力。在 Spring 应用程序中,通常会使用 ApplicationContext 来发布事件,以实现组件之间的解耦合和通信。

事件发布是 ApplicationContext 提供的一个重要功能,它允许应用程序的各个组件在适当的时候发布自定义事件,而不需要直接依赖于其他组件。通过事件驱动的方式,可以实现应用程序的松耦合,提高了代码的灵活性和可维护性。

除了作为事件发布器之外,ApplicationContext 还可以作为事件监听器(Event Listener)的容器。在 Spring 应用程序中,我们可以将事件监听器注册到 ApplicationContext 中,然后 ApplicationContext 会自动将事件分发给注册的监听器,从而实现对事件的监听和处理。

1.源码展示分析

ApplicationEventPublisher 接口是 Spring Framework 中用于发布事件的核心接口之一。以下是 ApplicationEventPublisher 接口的部分源码展示:

public interface ApplicationEventPublisher {

    // 发布事件
    void publishEvent(ApplicationEvent event);

    // 发布事件给指定的监听器
    void publishEvent(Object event);
}

在这个接口中,包含了两个发布事件的方法:

  1. **publishEvent(ApplicationEvent event)**: 这个方法用于发布指定的应用程序事件。它接收一个 ApplicationEvent 对象作为参数,表示要发布的事件。一般情况下,我们会创建自定义的事件类,继承自 ApplicationEvent,然后通过这个方法来发布自定义事件。
  2. **publishEvent(Object event)**: 这个方法也用于发布事件,但它接收一个任意类型的对象作为参数。Spring 会根据对象的类型来确定要发布的事件。如果对象是一个 ApplicationEvent 的子类,那么 Spring 会将其视为一个应用程序事件,并按照正常的方式进行处理。否则,Spring 会将其包装成 PayloadApplicationEvent,然后发布给监听器。

ApplicationEventPublisher 接口的设计和实现目的在于提供一种统一的方式来发布事件,以实现应用程序中各个组件之间的解耦合。通过调用 publishEvent 方法,可以将自定义事件发布给应用程序中的所有监听器,从而实现事件驱动的编程模型。

2.Spring Framework 事件驱动核心组件

Spring Framework 的事件驱动核心可以划分为以下几个主要部分:

  • 事件(Event): 事件是 Spring Framework 中的核心概念之一,代表着应用程序中发生的某个特定的动作或状态变化。事件通常被封装成一个 Java 类,实现了 ApplicationEvent 接口或其子类。Spring 提供了 ApplicationEvent 接口以及一些预定义的事件类,同时也支持开发者自定义事件类。
  • 事件发布器(Event Publisher): 事件发布器是 Spring Framework 中用于发布事件的组件。在 Spring 中,事件发布器由 ApplicationEventPublisher 接口来定义它通常由 ApplicationContext 实现。通过事件发布器,应用程序可以将事件发布到应用程序上下文中,以便监听器能够捕获并处理这些事件。
  • 事件监听器(Event Listener): 事件监听器是 Spring Framework 中用于监听和处理事件的组件。在 Spring 中,事件监听器由 ApplicationListener 接口来定义,开发者可以实现这个接口来定义自己的事件监听器。事件监听器监听特定类型的事件,当事件发生时,监听器会被调用,并执行相应的处理逻辑。
  • 事件多播器(Event Multicaster): 事件多播器是 Spring Framework 中用于将事件分发给多个监听器的组件。在 Spring 中,事件多播器由 ApplicationEventMulticaster 接口来定义,通常由 AbstractApplicationContext 实现。它负责将事件传递给所有注册的监听器,并确保它们按照特定的顺序进行调用。
  • 事件源(Event Source): 事件源是指触发事件的对象或组件。在 Spring 中,事件源通常是 ApplicationContext 或其他的 Spring Bean。当事件发生时,事件源会将事件传递给事件多播器,然后由多播器将事件分发给相应的监听器。

3.应用实现

下面是一个简单的示例使用

ApplicationEventPublisher

发布自定义事件,并编写测试验证事件是否被正确触发。

定义一个自定义事件

CustomEvent

package org.zyf.javabasic.spring.applicationeventpublisher;

import org.springframework.context.ApplicationEvent;

/**
 * @program: zyfboot-javabasic
 * @description: 自定义事件
 * @author: zhangyanfeng
 * @create: 2024-04-12 18:38
 **/
public class CustomEvent extends ApplicationEvent {
    private String message;

    public CustomEvent(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

然后编写一个服务类

EventService

,该服务类包含一个方法用于发布

CustomEvent

事件:

package org.zyf.javabasic.spring.applicationeventpublisher;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

/**
 * @program: zyfboot-javabasic
 * @description: 服务类 EventService
 * @author: zhangyanfeng
 * @create: 2024-04-12 18:39
 **/
@Service
public class EventService {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public void publishCustomEvent(String message) {
        CustomEvent customEvent = new CustomEvent(this, message);
        eventPublisher.publishEvent(customEvent);
    }
}

接下来编写一个事件监听器

CustomEventListener

,用于监听并处理

CustomEvent

事件:

package org.zyf.javabasic.spring.applicationeventpublisher;

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @program: zyfboot-javabasic
 * @description: 事件监听器 CustomEventListener
 * @author: zhangyanfeng
 * @create: 2024-04-12 18:40
 **/
@Component
public class CustomEventListener implements ApplicationListener<CustomEvent> {

    @Override
    public void onApplicationEvent(CustomEvent event) {
        String message = event.getMessage();
        System.out.println("Received custom event with message: " + message);
    }
}

最后,我们编写一个测试类来验证事件是否被正确触发:

package org.zyf.javabasic.spring.applicationeventpublisher;

import lombok.extern.log4j.Log4j2;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.zyf.javabasic.ZYFApplication;

/**
 * @program: zyfboot-javabasic
 * @description: 测试类来验证事件是否被正确触发
 * @author: zhangyanfeng
 * @create: 2024-04-12 18:40
 **/
@Log4j2
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ZYFApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class EventServiceTest {

    @Autowired
    private EventService eventService;

    @Test
    public void testPublishCustomEvent() {
        eventService.publishCustomEvent("Hello, Custom Event!");
    }
}

运行测试类

EventServiceTest

,您将会在控制台看到类似以下的输出:

这表明事件已经被正确发布并被监听器处理。

(四)ResourcePatternResolver

ResourcePatternResolver

ApplicationContext

之间的关系是相辅相成的。

ResourcePatternResolver

提供了一种统一的资源加载机制,而

ApplicationContext

则在 Spring 应用程序中提供了资源加载的实际实现和管理能力,二者共同促进了 Spring 应用程序中资源的加载和管理。

1.源码展示分析

ResourcePatternResolver

是 Spring 框架中用于解析资源路径模式的接口,它的默认实现是

PathMatchingResourcePatternResolver

。下面是

ResourcePatternResolver

接口的定义:

public interface ResourcePatternResolver extends ResourceLoader {
    Resource[] getResources(String locationPattern) throws IOException;
}
ResourcePatternResolver

接口继承自

ResourceLoader

接口,因此它具有加载资源的能力。主要方法是

getResources(String locationPattern)

,该方法根据给定的资源路径模式返回匹配的资源数组。接下来看一下

PathMatchingResourcePatternResolver

的简化实现:

public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
    private final ResourceLoader resourceLoader;

    public PathMatchingResourcePatternResolver() {
        this.resourceLoader = new DefaultResourceLoader();
    }

    public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
        this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader());
    }

    @Override
    public Resource[] getResources(String locationPattern) throws IOException {
        // 使用 AntPathMatcher 进行路径模式匹配,获取匹配的资源路径
        String rootDirPath = determineRootDir(locationPattern);
        String subPattern = locationPattern.substring(rootDirPath.length());
        Resource rootDirResource = this.resourceLoader.getResource(rootDirPath);
        File rootDir = rootDirResource.getFile();
        if (!rootDir.exists()) {
            return new Resource[0];
        }
        if (rootDir.isDirectory()) {
            return findPathMatchingResources(rootDir, subPattern);
        } else {
            return new Resource[0];
        }
    }

    private String determineRootDir(String locationPattern) {
        // 解析出根目录路径,例如将 "classpath:config/*.properties" 解析为 "classpath:config/"
        // 实现省略,可以使用 AntPathMatcher 等工具类进行路径解析
        return locationPattern.substring(0, locationPattern.indexOf("*"));
    }

    private Resource[] findPathMatchingResources(File rootDir, String subPattern) throws IOException {
        // 实现省略,使用递归方式查找匹配的资源文件
        // ...
        return new Resource[0];
    }

    @Override
    public ClassLoader getClassLoader() {
        return this.resourceLoader.getClassLoader();
    }

    @Override
    public ResourceLoader getResourceLoader() {
        return this.resourceLoader;
    }
}

PathMatchingResourcePatternResolver

中,我们使用了

ResourceLoader

来加载资源,并使用

AntPathMatcher

进行路径模式的匹配。主要步骤包括:

  1. 解析资源路径模式,确定根目录路径和子模式路径。
  2. 使用 ResourceLoader 获取根目录对应的 Resource 对象。
  3. 根据根目录的文件或者目录状态,使用递归方式查找匹配的资源文件。
  4. 返回匹配的资源数组。

这只是

PathMatchingResourcePatternResolver

的简化实现,实际代码可能会更加复杂。这个类主要负责根据资源路径模式查找匹配的资源,并返回一个资源数组。通过这个实现,我们可以方便地根据特定的资源路径模式加载资源,以满足应用程序的需求。

2.理解其设计和目的

ResourcePatternResolver

的设计旨在提供一种统一的机制,用于根据特定的资源路径模式解析和获取匹配的资源。其主要目的包括:

  • 统一资源加载接口:ResourcePatternResolver 继承自 ResourceLoader 接口,因此提供了一种统一的方式来加载资源,无论是简单的资源路径还是包含通配符的资源路径模式。
  • 支持通配符路径模式: 资源路径模式允许使用通配符(例如 *?)来匹配多个资源。所以在加载资源时可以更加灵活,例如可以一次性加载所有以 .xml 结尾的配置文件。
  • 解决特定需求: 在开发中,经常需要根据一定的规则加载特定类型的资源,例如加载所有的配置文件、静态资源、国际化文件等。ResourcePatternResolver 提供了一种简单而灵活的方式来满足这些需求,而不需要开发人员自己编写复杂的逻辑。
  • 适用于不同环境:ResourcePatternResolver 可以在不同的环境中使用,包括传统的 Java SE 环境、Web 应用程序中以及 Spring Boot 等环境中,能够灵活地适应各种场景下的资源加载需求。

3.实际应用简单举例

假设我们有一个工程项目,项目结构如下:

目标是使用

ResourcePatternResolver

加载所有位于

src/main/resources/db

目录下的

.sql

文件。编写测试代码 :

package org.zyf.javabasic.spring.resourcePatternResolver;

import lombok.extern.log4j.Log4j2;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.test.context.junit4.SpringRunner;
import org.zyf.javabasic.ZYFApplication;

import java.io.IOException;

/**
 * @program: zyfboot-javabasic
 * @description: 简单的案例来验证 ResourcePatternResolver 的功能
 * @author: zhangyanfeng
 * @create: 2024-04-12 19:18
 **/
@Log4j2
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ZYFApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ResourcePatternResolverTest {
    @Autowired
    private ResourcePatternResolver resourcePatternResolver;

    @Test
    public void testResourcePatternResolver() throws IOException {
        String locationPattern = "classpath:db/*.sql";
        Resource[] resources = resourcePatternResolver.getResources(locationPattern);
        for (Resource resource : resources) {
            System.out.println("Found resource: " + resource.getFilename());
        }
    }
}

在测试类中注入了

ResourcePatternResolver

对象,并在方法中使用它加载指定路径下的所有资源,并打印出它们的文件名。运行测试类输出结果:

五、ApplicationContext实现类分析

在通过以上的分析后,我们开始分析其几个重要的实现类,总结图如下:

基本的实现类这里不会全部都进行讲解,本博客只快速讲解部分,具体如下。

(一)AbstractApplicationContext

AbstractApplicationContext

是 Spring 框架中定义了 ApplicationContext 接口的抽象实现类之一,它提供了 ApplicationContext 接口的一些通用功能的默认实现。下面是

AbstractApplicationContext

的简化版本代码:

public abstract class AbstractApplicationContext implements ConfigurableApplicationContext {
    // 保存当前应用程序上下文的 BeanFactory
    private DefaultListableBeanFactory beanFactory;

    // 保存当前应用程序上下文的资源加载器
    private ResourcePatternResolver resourcePatternResolver;

    // 保存当前应用程序上下文的消息源
    private MessageSource messageSource;

    // 保存当前应用程序上下文的事件发布器
    private ApplicationEventPublisher eventPublisher;

    // 其他属性和方法省略...

    public AbstractApplicationContext() {
        // 初始化默认的 BeanFactory
        this.beanFactory = new DefaultListableBeanFactory();
        // 初始化资源加载器
        this.resourcePatternResolver = getResourcePatternResolver();
        // 初始化消息源
        this.messageSource = getMessageSource();
        // 初始化事件发布器
        this.eventPublisher = new SimpleApplicationEventPublisher(this);
    }

    // 获取资源加载器,默认返回 PathMatchingResourcePatternResolver
    protected ResourcePatternResolver getResourcePatternResolver() {
        return new PathMatchingResourcePatternResolver(this);
    }

    // 获取消息源,默认返回 DelegatingMessageSource
    protected MessageSource getMessageSource() {
        return new DelegatingMessageSource();
    }

    // 其他初始化方法和功能方法省略...
}

AbstractApplicationContext

中,基本属性主要包括:

  • beanFactory:保存当前应用程序上下文的 BeanFactory,用于管理应用程序中的 bean 实例。
  • resourcePatternResolver:保存当前应用程序上下文的资源加载器,用于加载资源文件。
  • messageSource:保存当前应用程序上下文的消息源,用于解析和获取消息文本。
  • eventPublisher:保存当前应用程序上下文的事件发布器,用于发布事件消息。

在构造方法中进行了这些属性的初始化工作,包括创建默认的

BeanFactory

、资源加载器、消息源和事件发布器。默认的资源加载器采用了

PathMatchingResourcePatternResolver

,默认的消息源采用了

DelegatingMessageSource

。这些默认实现可以通过子类重写相应的方法来进行自定义。

AbstractApplicationContext

提供了 ApplicationContext 接口的一些通用功能的默认实现,包括对 BeanFactory、资源加载器、消息源和事件发布器的初始化工作。通过继承

AbstractApplicationContext

,可以方便地创建自定义的应用程序上下文实现类,并进行相应的定制化配置。

(二)GenericApplicationContext

GenericApplicationContext

是 Spring 框架中的一个通用应用程序上下文实现类,它是

AbstractApplicationContext

的子类。下面是

GenericApplicationContext

的简化版本代码:

public class GenericApplicationContext extends AbstractApplicationContext {
    // 保存当前应用程序上下文的 BeanDefinitionRegistry
    private final DefaultListableBeanFactory beanFactory;

    public GenericApplicationContext() {
        this.beanFactory = new DefaultListableBeanFactory();
    }

    @Override
    protected void refreshBeanFactory() throws BeansException {
        // 将 BeanDefinition 注册到 BeanFactory 中
        getBeanFactory().registerBeanDefinitions(loadBeanDefinitions());
    }

    @Override
    public DefaultListableBeanFactory getBeanFactory() {
        return this.beanFactory;
    }

    // 加载 BeanDefinition 的方法,具体实现省略...
    protected abstract BeanDefinition[] loadBeanDefinitions() throws BeansException;
}

GenericApplicationContext

中,主要关注以下几点:

  1. beanFactory:保存当前应用程序上下文的 BeanFactory,采用了 DefaultListableBeanFactory 实现。
  2. refreshBeanFactory() 方法:重写了 AbstractApplicationContext 中的该方法,用于刷新 BeanFactory。在这里,它调用了 loadBeanDefinitions() 方法加载 BeanDefinition,并将其注册到 BeanFactory 中。
  3. getBeanFactory() 方法:重写了 AbstractApplicationContext 中的该方法,返回当前应用程序上下文的 BeanFactory
GenericApplicationContext

主要是作为一个通用的应用程序上下文实现类,它提供了对

BeanFactory

的默认实现,并提供了加载 BeanDefinition 的接口供子类实现。通过继承

GenericApplicationContext

,我们可以创建自定义的应用程序上下文,并根据需要进行配置和定制化。同时,由于其继承了

AbstractApplicationContext

,因此也具备了

AbstractApplicationContext

中定义的一些通用功能。

(三)AnnotationConfigApplicationContext

AnnotationConfigApplicationContext

是 Spring 框架中的一个特殊应用程序上下文实现类,用于从基于 Java 注解的配置类中加载 Spring Bean。它是

GenericApplicationContext

的子类,并且提供了特定于注解配置的功能。简化版本代码如下:

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
    private final AnnotatedBeanDefinitionReader reader;
    private final ClassPathBeanDefinitionScanner scanner;

    public AnnotationConfigApplicationContext() {
        this.reader = new AnnotatedBeanDefinitionReader(this);
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

    public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
        this();
        register(annotatedClasses);
        refresh();
    }

    public AnnotationConfigApplicationContext(String... basePackages) {
        this();
        scan(basePackages);
        refresh();
    }

    @Override
    public void register(Class<?>... annotatedClasses) {
        this.reader.register(annotatedClasses);
    }

    @Override
    public void scan(String... basePackages) {
        this.scanner.scan(basePackages);
    }
}

AnnotationConfigApplicationContext

中,主要关注以下几点:

  1. AnnotatedBeanDefinitionReaderClassPathBeanDefinitionScanner 这两个类分别用于从注解配置类和包中扫描并注册 BeanDefinition。AnnotatedBeanDefinitionReader 用于读取注解配置类,并将其转换为相应的 BeanDefinition,而 ClassPathBeanDefinitionScanner 则用于从指定的包路径中扫描并注册 BeanDefinition。
  2. 构造方法:AnnotationConfigApplicationContext 提供了几个不同的构造方法,可以接受注解配置类或扫描包路径作为参数,并在构造方法中进行相应的注册和刷新操作。这些构造方法使得创建 AnnotationConfigApplicationContext 实例变得简单和方便。
  3. register()scan() 方法: 这两个方法分别用于注册注解配置类和扫描包路径中的 BeanDefinition。register() 方法将指定的注解配置类注册到应用程序上下文中,而 scan() 方法则扫描指定的包路径并注册其中的 BeanDefinition。
  4. 自动刷新: 在构造方法中,当注册或扫描操作完成后,会立即调用 refresh() 方法对应用程序上下文进行刷新,以确保所有的 BeanDefinition 被加载到 BeanFactory 中并准备就绪。

通过

AnnotationConfigApplicationContext

,可以方便地从基于 Java 注解的配置类中加载 Spring Bean,并且可以通过注册注解配置类或扫描包路径的方式进行配置。

(四)AbstractRefreshableApplicationContext

AbstractRefreshableApplicationContext

是 Spring 框架中用于可刷新的应用程序上下文的抽象实现类,是

AbstractApplicationContext

的子类,并且提供了一些与刷新操作相关的通用功能。简化版本代码如下:

public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext implements ConfigurableApplicationContext {
    // 保存配置文件的位置
    private String[] configLocations;

    public void setConfigLocations(String... locations) {
        this.configLocations = locations;
    }

    @Override
    protected final void refreshBeanFactory() throws BeansException {
        if (this.configLocations != null) {
            // 根据配置文件位置加载 BeanDefinition,并注册到 BeanFactory 中
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            loadBeanDefinitions(beanFactory);
            refreshBeanFactory(beanFactory);
        } else {
            // 如果配置文件位置为空,则抛出异常
            throw new IllegalStateException("No config locations specified");
        }
    }

    protected DefaultListableBeanFactory createBeanFactory() {
        return new DefaultListableBeanFactory();
    }

    protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException;

    protected void refreshBeanFactory(DefaultListableBeanFactory beanFactory) {
        // 设置 BeanFactory 到当前上下文中
        this.beanFactory = beanFactory;
    }
}

AbstractRefreshableApplicationContext

中,主要关注以下几点:

  1. configLocations 属性: 该属性用于保存配置文件的位置,即 Spring 配置文件的路径。通过 setConfigLocations() 方法设置。
  2. refreshBeanFactory() 方法: 该方法在刷新应用程序上下文时被调用,用于加载 BeanDefinition 并注册到 BeanFactory 中。它首先检查配置文件位置是否已设置,然后根据配置文件位置创建 BeanFactory,并调用 loadBeanDefinitions() 方法加载 BeanDefinition。加载完成后,调用 refreshBeanFactory() 方法刷新 BeanFactory。
  3. createBeanFactory() 方法: 该方法用于创建默认的 BeanFactory 实例。子类可以重写此方法以提供自定义的 BeanFactory 实现。
  4. loadBeanDefinitions() 方法: 这是一个抽象方法,子类必须实现该方法以加载 BeanDefinition。在 refreshBeanFactory() 方法中调用。
  5. refreshBeanFactory() 方法: 该方法用于刷新 BeanFactory,并将其设置到当前应用程序上下文中。默认实现直接将创建的 BeanFactory 设置到 beanFactory 属性中。

通过

AbstractRefreshableApplicationContext

,可以创建一个可刷新的应用程序上下文,用于加载 Spring 配置文件中的 BeanDefinition,并在需要时进行刷新。子类只需实现

loadBeanDefinitions()

方法来加载配置文件中的 BeanDefinition,即可实现具体的应用程序上下文实现。

(五)AbstractXmlApplicationContext

AbstractXmlApplicationContext

是 Spring 框架中用于从 XML 配置文件中加载 BeanDefinition 的抽象应用程序上下文实现类,是

AbstractRefreshableApplicationContext

的子类,提供了针对 XML 配置的特定功能。简化版本代码如下:

public abstract class AbstractXmlApplicationContext extends AbstractRefreshableApplicationContext {
    // 保存配置文件的路径
    private Resource[] configResources;

    public void setConfigLocations(String... locations) {
        this.configLocations = locations;
    }

    public void setConfigResources(Resource... resources) {
        this.configResources = resources;
    }

    @Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException {
        if (this.configResources != null) {
            // 加载配置资源中的 BeanDefinition
            XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
            reader.loadBeanDefinitions(this.configResources);
        } else {
            // 如果配置资源为空,则抛出异常
            throw new IllegalStateException("No config resources specified");
        }
    }
}

AbstractXmlApplicationContext

中,主要关注以下几点:

  1. configResources 属性: 该属性用于保存配置文件的资源,即 XML 配置文件的路径。通过 setConfigResources() 方法设置。
  2. loadBeanDefinitions() 方法: 这是 AbstractRefreshableApplicationContext 中的一个抽象方法,被重写以加载 XML 配置文件中的 BeanDefinition。在这里,它首先检查配置资源是否已设置,然后创建 XmlBeanDefinitionReader 对象,并使用它来加载配置资源中的 BeanDefinition。
  3. setConfigLocations()setConfigResources() 方法: 这两个方法用于设置配置文件的路径或资源。开发者可以选择使用其中之一,根据自己的需要来设置配置文件的位置。

通过

AbstractXmlApplicationContext

,可以方便地从 XML 配置文件中加载 BeanDefinition,并将其注册到 BeanFactory 中,从而创建一个基于 XML 配置的应用程序上下文。子类只需实现

loadBeanDefinitions()

方法来加载 XML 配置文件中的 BeanDefinition 即可。

六、总结

在本篇文章中,我们深入探讨了 ApplicationContext 在 Spring 框架中的核心作用,揭示了其如何通过依赖注入和控制反转等设计理念,促进应用程序的灵活性与可维护性。我们分析了 ApplicationContext 的不同实现类型,包括 ClassPathXmlApplicationContextFileSystemXmlApplicationContextAnnotationConfigApplicationContext,并探讨了它们各自的适用场景和优缺点。

此外,通过示例代码和实际应用场景的讨论,我们强调了 ApplicationContext 在管理 bean 生命周期、依赖注入、事件发布等方面的关键功能。这些功能不仅提升了应用程序的模块化程度,也使得开发者在处理复杂业务逻辑时,能够更加专注于核心功能的实现,而无需担心底层的配置细节。

总而言之,理解 ApplicationContext 的运作机制是每个 Spring 开发者的必修课。无论是在构建小型项目还是企业级应用,充分利用 ApplicationContext 提供的强大功能,必将提升开发效率和代码质量,助力团队快速响应业务需求的变化。我们希望这篇文章能为您在 Spring 开发之旅中提供有价值的见解和指导。

源码或官方文章

https://docs.spring.io/spring-framework/reference/core/beans/context-introduction.html

Container Overview :: Spring Framework

Spring Framework Documentation :: Spring Framework

Java-based Container Configuration :: Spring Framework

标签: spring java 后端

本文转载自: https://blog.csdn.net/xiaofeng10330111/article/details/137670813
版权归原作者 张彦峰ZYF 所有, 如有侵权,请联系我们删除。

“重看Spring聚焦ApplicationContext分析”的评论:

还没有评论