Spring-ApplicationContext初认识

本文最后更新于:5 个月前

Spring的ApplicationContext提供了一个功能丰富的环境。本文将对ApplicationContext做一个初步的介绍,主要是对其实现的接口做一个详细说明。

类图

SpringBoot项目的启动方法会返回一个ConfigurableApplicationContext类型的对象,其类图如下:

ConfigurableApplicationContext类图

由图可知ApplicationContext主要实现了以下接口,分别为:

  1. BeanFactory
  2. MessageSource
  3. ResourcePatternResolver
  4. EnvironmentCapable
  5. ApplicationEventPublisher

下面将依次对这些接口进行说明。

BeanFactory

Spring框架中最基础、最核心的组件,用于管理Java对象(beans)。

API

BeanFactory中提供了许多有关于Bean的API

bean-factory-api

核心功能

  • 依赖注入BeanFactory通过控制反转(IoC)的方式管理bean,即bean的创建和依赖关系不是由bean自己控制,而是由BeanFactory进行管理。这有助于降低组件间的耦合度。
  • 生命周期管理BeanFactory管理bean的整个生命周期,从创建、初始化、使用到销毁,包括调用初始化和销毁回调。
  • bean的后处理:通过BeanPostProcessor接口,BeanFactory允许对bean实例进行额外的处理,比如检查bean属性的完整性或者对bean进行代理。

实现类和用法

  • **XmlBeanFactory**(现已废弃):这是早期版本的Spring中使用的一个实现,它从XML文件中读取bean定义。

  • **DefaultListableBeanFactory**:这是一个全功能的bean容器,支持从XML文件、Java注解等多种配置源加载bean。它还支持按类型和按名称自动装配依赖。

  • **GenericApplicationContext**:配合DefaultListableBeanFactory使用,提供了一个灵活的应用上下文实现。

Demo

查看DefaultListableBeanFactory的类图,如下

default-listable-bean-factory

由图可知,DefaultListableBeanFactory类继承了DefaultSingletonBeanRegistry类,这个类中包含一个成员变量singletonObjects

1
2
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

这个成员变量就存放着我们所熟知的单例bean

我们可以先自定义两个组件

1
2
3
4
5
6
7
@Component("component1")
public class Component1 {
}

@Component("component2")
public class Component2 {
}

在项目启动以后,通过ApplicationContext拿到BeanFactory的实例对象,再通过反射的方法读取到所有的单例bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@SpringBootApplication
public class Application {

public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);

try {
// 使用反射获取DefaultSingletonBeanRegistry中的singletonObjects成员变量
Class<DefaultSingletonBeanRegistry> clazz = DefaultSingletonBeanRegistry.class;
Field singletonObjects = clazz.getDeclaredField("singletonObjects");
singletonObjects.setAccessible(true);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
Map<String, Object> singletonObjectsValue = (Map<String, Object>)singletonObjects.get(beanFactory);
singletonObjectsValue.forEach((k, v) -> {
if (k.startsWith("component")) {
System.out.println(k + " : " + v);
}
});
} catch (Exception e) {
e.printStackTrace();
}

}

}

运行SpringBoot项目,控制台输出如下:

1
2
component1 : space.yangtao.spring.component.Component1@55dfcc6
component2 : space.yangtao.spring.component.Component2@222eb8aa

MessageSource

MessageSource 是一个用于解决国际化(i18n)和本地化(l10n)消息的接口。它提供了一种从多种源加载消息的方式,并支持消息的国际化,即根据不同的地区显示不同的消息。

主要功能

  • 国际化和本地化MessageSource允许应用程序根据用户的地区(Locale)加载特定的消息。这使得创建多语言应用变得简单。
  • 消息格式化:支持带有参数的消息,可以在运行时传递变量,实现动态消息文本。
  • 继承结构MessageSource可以配置为层级结构,如果在一个MessageSource中找不到消息,它会查询其父MessageSource

核心方法

  • String getMessage(String code, Object[] args, String defaultMessage, Locale locale):尝试根据消息代码、参数、默认消息和地区解析消息。如果找不到对应代码的消息,则返回默认消息。
  • String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException:根据消息代码、参数和地区解析消息。如果找不到消息,则抛出异常。
  • String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException:这是一个更高级的方法,允许通过MessageSourceResolvable对象,对象中包含多个消息代码和参数,对消息代码进行遍历解析,找到对应的消息后立即返回。如果找不到消息,则抛出异常。

Demo

  1. resources目录下新建配置文件

    message.properties

    1
    hello=接收 {0} ,返回 你好

    message_en_US.properties

    1
    hello=get {0} and return hello

    这两个配置文件中通过占位符,支持了动态传参

  2. 项目配置文件application.properties中配置消息的基名以及编码格式,如果此步不配置,则SpringBoot的自动装配会默认将basename取值为“message”,默认编码格式为“UTF-8”

    1
    2
    spring.messages.basename=message
    spring.messages.encoding=UTF-8
  3. 新建接口,获取message参数,通过getMessage方法获取其国际化的值

    1
    2
    3
    4
    5
    6
    7
    @Resource
    private MessageSource messageSource;

    @GetMapping("/{message}")
    public String hello(@PathVariable("message") String message, Locale locale) {
    return messageSource.getMessage(message, new Object[]{message}, "No Such Message", locale);
    }
  4. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    curl -XGET http://localhost:8080/message/hello
    接收 hello ,返回 你好

    curl -XGET -H "Accept-Language":"en-US" http://localhost:8080/message/hello
    get hello and return hello

    curl -XGET -H "Accept-Language":"en-US" http://localhost:8080/message/hello2
    No Such Message

ResourcePatternResolver

ResourcePatternResolver是一个用于加载资源(如配置文件、图像文件等)的接口,它扩展了ResourceLoader接口的功能,提供了一种在classpath或其他位置查找资源的更灵活方式。ResourcePatternResolver可以解析特定模式的资源路径,可以一次性查找符合特定模式的所有资源。

主要功能

  • 模式匹配ResourcePatternResolver支持使用通配符和其他模式来匹配资源路径,如classpath*:file:ant风格的路径模式(如**/*.xml)。
  • 资源查找:它能够查找并返回符合指定模式的所有Resource实例,这些Resource实例可以代表文件系统中的文件、classpath中的元素或其他任何形式的资源。

核心方法

  • Resource[] getResources(String locationPattern)方法用于根据提供的路径模式返回Resource数组。这个方法是用来解析资源模式并加载所有匹配的资源。

常用的资源模式

  • 单一路径:使用classpath:(类路径)、classpath*:(含jar包类路径)、file:(磁盘路径)。
  • 所有匹配资源:使用classpath*: 等前缀配合**(表示多层路径)和*(表示任意字符)的组合,查找所有匹配的资源。例如,classpath*:/**/*.xml可以查找所有classpath路径及其子路径下的XML文件。

Demo

项目启动时获取SpringBoot自动配置的相关文件(META-INFOspring.factories

1
2
3
4
5
6
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);

Resource[] resources = context.getResources("classpath*:META-INF/spring.factories0");
for (Resource resource : resources) {
System.out.println(resource);
}

控制台输出如下

1
2
3
URL [jar:file:/C:/Users/yangtao/.m2/repository/org/springframework/boot/spring-boot/2.7.18/spring-boot-2.7.18.jar!/META-INF/spring.factories]
URL [jar:file:/C:/Users/yangtao/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.7.18/spring-boot-autoconfigure-2.7.18.jar!/META-INF/spring.factories]
URL [jar:file:/C:/Users/yangtao/.m2/repository/org/springframework/spring-beans/5.3.31/spring-beans-5.3.31.jar!/META-INF/spring.factories]

EnvironmentCapable

EnvironmentCapable的主要用途是提供对Environment对象的访问。Environment是Spring中用于封装所有与环境相关的属性,例如配置文件、系统属性、环境变量等的抽象。

主要功能

获取当前应用上下文或组件相关联的Environment对象,该对象具有如下功能:

  • 属性解析Environment提供方法来解析属性,支持从多种源读取,如JVM系统属性、环境变量、配置文件等。
  • 配置文件管理:在基于SpringBoot的应用中,Environment接口特别有用,因为它可以管理和激活不同的配置文件(如application-dev.propertiesapplication-prod.properties等)。
  • 属性覆盖Environment允许对同一属性的不同值进行覆盖,按照特定的优先级排序(如系统属性可以覆盖配置文件中的值)。SpringBoot属性加载顺序优先级由高到低依次为:
    • Devtools全局设置(仅在使用Spring Boot Devtools时适用)
    • 命令行参数(如:java -jar app.jar --app.name="My App"
    • 系统环境变量
    • JVM系统属性(如:java -Dapp.name="My App" -jar app.jar
    • 应用属性文件(如:application.propertiesapplication.yml
    • 应用内部默认属性(如:通过@PropertySource注解或在配置类中以编程方式设置的属性)
  • 条件化配置:Spring的条件化配置(@Conditional注解)可以使用Environment对象来检查某些属性是否存在或满足特定值,从而决定是否激活某个配置或组件。

Demo

  1. 读取系统变量

    1
    2
    3
    4
    5
    6
    7
    ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);

    ConfigurableEnvironment environment = context.getEnvironment();
    String javaHome = environment.getProperty("JAVA_HOME");
    String port = environment.getProperty("server.port");
    System.out.println("JAVA_HOME: " + javaHome);
    System.out.println("server.port: " + port);

    控制台输出如下

    1
    2
    JAVAE_HOME: /usr/lib/jvm/java-8-openjdk-amd64
    server.port: 8080
  2. 创建配置文件application-prod.properties

    1
    server.port=8081

    SpringBoot应用添加启动的参数

    1
    --spring.profiles.active=prod

    添加以上参数以后,即可更换应用的配置项,运行项目后控制台输出如下

    1
    2
    JAVA_HOME: C:\Program Files\Java\jdk1.8.0_281
    server.port: 8081
  3. 启动参数中指定JAVA_HOME属性

    1
    --JAVA_HOME="/usr/local/java"

    运行项目后控制台输出如下

    1
    JAVA_HOME: /usr/local/java
  4. 创建条件类ProdCondition

    1
    2
    3
    4
    5
    6
    public class ProdCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    return "prod".equals(context.getEnvironment().getProperty("env"));
    }
    }

    创建组件ProdComponent

    1
    2
    3
    4
    @Component
    @Conditional(ProdCondition.class)
    public class ProdComponent {
    }

    项目启动时指定参数 env值为prod

    1
    --env="prod"

    应用启动时检测应用中是否含有组件ProdComponent

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);

    try {
    AtomicBoolean hasProdComponent = new AtomicBoolean(false);
    Class<DefaultSingletonBeanRegistry> clazz = DefaultSingletonBeanRegistry.class;
    Field singletonObjects = clazz.getDeclaredField("singletonObjects");
    singletonObjects.setAccessible(true);
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    Map<String, Object> singletonObjectsValue = (Map<String, Object>) singletonObjects.get(beanFactory);
    singletonObjectsValue.forEach((k, v) -> {
    if (v instanceof ProdComponent) {
    hasProdComponent.set(true);
    }
    });
    System.out.println("hasProdComponent: " + hasProdComponent.get());
    } catch (Exception e) {
    e.printStackTrace();
    }

    启动应用,控制台输出如下

    1
    hasProdComponent: true

    去除项目启动时的参数 env 或改变其值,则控制台输出如下

    1
    hasProdComponent: false

ApplicationEventPublisher

ApplicationEventPublisher提供了发布事件到应用程序中所有注册的监听器的功能。这是Spring事件驱动模型的核心部分,允许组件之间进行松耦合的通信。通过使用事件,可以在应用程序的不同部分传递状态信息或改变通知,而不需要直接调用其他部分的代码。

核心功能

  • 事件发布ApplicationEventPublisher允许应用程序组件发布广泛的事件,这些事件可以是任何扩展自 ApplicationEvent的对象。
  • 松耦合的架构:事件发布者与事件监听者之间不需要直接的引用或知识,他们通过事件对象进行交互,从而降低了系统各部分之间的耦合度。

关键方法

  • void publishEvent(ApplicationEvent event):发布一个继承自ApplicationEvent的事件。
  • void publishEvent(Object event):从Spring Framework 4.2开始,可以发布任何对象作为事件,不再限制于ApplicationEvent的子类。

Demo

  1. 定义一个应用运行事件AppRunEvent

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class AppRunEvent extends ApplicationEvent {

    private final Long timestamp;

    public AppRunEvent(Object source, Long timestamp) {
    super(source);
    this.timestamp = timestamp;
    }

    public Long getTimeStamp() {
    return timestamp;
    }

    }
  2. 创建这个监听这个事件的监听器AppRunListener

    1
    2
    3
    4
    5
    6
    7
    8
    @Component
    public class AppRunListener implements ApplicationListener<AppRunEvent> {
    @Override
    public void onApplicationEvent(AppRunEvent event) {
    System.out.println("AppRunListener: " + event.getTimeStamp());
    // do something
    }
    }
  3. 在项目启动时发布事件

    1
    2
    ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
    context.publishEvent(new AppRunEvent(context, System.currentTimeMillis()));
  4. 控制台输出如下

    1
    AppRunListener: 1726590020427

总结

本文深入探讨了Spring框架的核心组件ApplicationContext及其扩展接口,通过对这些接口的了解,开发者可以更好地利用Spring框架提供的强大功能,以构建灵活、可维护的应用程序。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!