Spring-ApplicationContext初认识
本文最后更新于:5 个月前
Spring的ApplicationContext
提供了一个功能丰富的环境。本文将对ApplicationContext
做一个初步的介绍,主要是对其实现的接口做一个详细说明。
类图
SpringBoot项目的启动方法会返回一个ConfigurableApplicationContext
类型的对象,其类图如下:
由图可知ApplicationContext
主要实现了以下接口,分别为:
BeanFactory
MessageSource
ResourcePatternResolver
EnvironmentCapable
ApplicationEventPublisher
下面将依次对这些接口进行说明。
BeanFactory
Spring框架中最基础、最核心的组件,用于管理Java对象(beans)。
API
BeanFactory
中提供了许多有关于Bean的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
的类图,如下
由图可知,DefaultListableBeanFactory
类继承了DefaultSingletonBeanRegistry
类,这个类中包含一个成员变量singletonObjects
1 |
|
这个成员变量就存放着我们所熟知的单例bean
我们可以先自定义两个组件
1 |
|
在项目启动以后,通过ApplicationContext
拿到BeanFactory
的实例对象,再通过反射的方法读取到所有的单例bean
1 |
|
运行SpringBoot项目,控制台输出如下:
1 |
|
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
在
resources
目录下新建配置文件message.properties
1
hello=接收 {0} ,返回 你好
message_en_US.properties
1
hello=get {0} and return hello
这两个配置文件中通过占位符,支持了动态传参
项目配置文件
application.properties
中配置消息的基名以及编码格式,如果此步不配置,则SpringBoot的自动装配会默认将basename
取值为“message”
,默认编码格式为“UTF-8”
1
2spring.messages.basename=message
spring.messages.encoding=UTF-8新建接口,获取
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);
}测试
1
2
3
4
5
6
7
8curl -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-INFO
下spring.factories
)
1 |
|
控制台输出如下
1 |
|
EnvironmentCapable
EnvironmentCapable
的主要用途是提供对Environment
对象的访问。Environment
是Spring中用于封装所有与环境相关的属性,例如配置文件、系统属性、环境变量等的抽象。
主要功能
获取当前应用上下文或组件相关联的Environment
对象,该对象具有如下功能:
- 属性解析:
Environment
提供方法来解析属性,支持从多种源读取,如JVM
系统属性、环境变量、配置文件等。 - 配置文件管理:在基于SpringBoot的应用中,
Environment
接口特别有用,因为它可以管理和激活不同的配置文件(如application-dev.properties
,application-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.properties
或application.yml
) - 应用内部默认属性(如:通过
@PropertySource
注解或在配置类中以编程方式设置的属性)
- 条件化配置:Spring的条件化配置(
@Conditional
注解)可以使用Environment
对象来检查某些属性是否存在或满足特定值,从而决定是否激活某个配置或组件。
Demo
读取系统变量
1
2
3
4
5
6
7ConfigurableApplicationContext 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
2JAVAE_HOME: /usr/lib/jvm/java-8-openjdk-amd64
server.port: 8080创建配置文件application-prod.properties
1
server.port=8081
SpringBoot应用添加启动的参数
1
--spring.profiles.active=prod
添加以上参数以后,即可更换应用的配置项,运行项目后控制台输出如下
1
2JAVA_HOME: C:\Program Files\Java\jdk1.8.0_281
server.port: 8081启动参数中指定
JAVA_HOME
属性1
--JAVA_HOME="/usr/local/java"
运行项目后控制台输出如下
1
JAVA_HOME: /usr/local/java
创建条件类
ProdCondition
1
2
3
4
5
6public 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
18ConfigurableApplicationContext 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
定义一个应用运行事件
AppRunEvent
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class AppRunEvent extends ApplicationEvent {
private final Long timestamp;
public AppRunEvent(Object source, Long timestamp) {
super(source);
this.timestamp = timestamp;
}
public Long getTimeStamp() {
return timestamp;
}
}创建这个监听这个事件的监听器
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
}
}在项目启动时发布事件
1
2ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
context.publishEvent(new AppRunEvent(context, System.currentTimeMillis()));控制台输出如下
1
AppRunListener: 1726590020427
总结
本文深入探讨了Spring框架的核心组件ApplicationContext
及其扩展接口,通过对这些接口的了解,开发者可以更好地利用Spring框架提供的强大功能,以构建灵活、可维护的应用程序。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!