Spring Boot - 容器资源感知与获取
# Spring Boot - 容器资源感知与获取
# 1. Spring Aware接口概
在Spring框架的设计理念中,一个重要原则是让应用组件(Bean)不感知容器的存在,从而降低组件与框架之间的耦合度。这种"非侵入式"设计允许开发者专注于业务逻辑,而无需关心组件是如何被管理的。
然而,在某些特定场景下,我们确实需要在Bean中感知到Spring容器的存在,获取容器提供的资源或服务。例如:
- 获取当前应用的环境配置信息
- 获取其他Bean的引用
- 获取容器中的特定资源
- 发布应用事件等
为了满足这些需求,Spring框架提供了一系列的"Aware"接口。通过实现这些接口,Bean可以在其生命周期的特定阶段获得对Spring容器特定资源的访问权限。
Aware接口的核心思想:允许Bean在不破坏容器封装性的前提下,有选择地获取容器提供的服务和资源。
# 2. 常用Aware接口
Spring框架提供了多种Aware接口,每种接口都允许Bean获取容器中的不同资源。以下是最常用的Aware接口及其功能详解:
接口名称 | 回调方法 | 提供的资源 | 使用场景 |
---|---|---|---|
BeanNameAware | setBeanName(String name) | Bean在容器中的名称 | 需要在代码中获取Bean自身的名称 |
BeanFactoryAware | setBeanFactory(BeanFactory factory) | BeanFactory实例,可用于获取其他Bean | 需要编程式获取其他Bean实例 |
ApplicationContextAware | setApplicationContext(ApplicationContext ctx) | 应用上下文,功能比BeanFactory更丰富 | 获取应用上下文中的各种资源,最常用的Aware接口 |
EnvironmentAware | setEnvironment(Environment env) | 应用的环境配置信息 | 获取配置文件中的属性、系统环境变量等 |
EmbeddedValueResolverAware | setEmbeddedValueResolver(StringValueResolver resolver) | 字符串解析器,可解析配置文件中的占位符 | 解析properties文件或yml文件中的属性值 |
ResourceLoaderAware | setResourceLoader(ResourceLoader loader) | 资源加载器,用于加载各种资源 | 加载类路径或文件系统中的资源文件 |
ApplicationEventPublisherAware | setApplicationEventPublisher(ApplicationEventPublisher publisher) | 事件发布器 | 发布自定义应用事件 |
MessageSourceAware | setMessageSource(MessageSource ms) | 消息源,用于国际化 | 获取国际化消息,支持多语言应用 |
BeanClassLoaderAware | setBeanClassLoader(ClassLoader classLoader) | 类加载器 | 获取加载Bean的类加载器,通常用于高级场景 |
# 3. Aware接口实现原理
Spring容器是如何感知并处理实现了Aware接口的Bean的?其内部实现原理如下:
# 3.1 Aware接口处理流程
Bean实例化:Spring容器首先实例化Bean对象。
Aware接口检测:在Bean的初始化流程中,Spring容器会检查Bean是否实现了各种Aware接口。
回调方法调用:如果Bean实现了某个Aware接口,容器会调用对应的回调方法,传入相应的资源。
注入时机:Aware接口的回调方法通常在属性填充之后、初始化方法(如
@PostConstruct
或afterPropertiesSet()
)调用之前执行。
# 3.2 执行机制
Spring在AbstractAutowireCapableBeanFactory
类的initializeBean
方法中处理Aware接口:
// Spring内部处理Aware接口的代码片段(简化版)
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
// 处理Aware接口
if (bean instanceof Aware) {
if (bean instanceof BeanNameAware) {
((BeanNameAware) bean).setBeanName(beanName);
}
if (bean instanceof BeanClassLoaderAware) {
((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader());
}
if (bean instanceof BeanFactoryAware) {
((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
}
// 处理其他Aware接口...
}
// 继续Bean的初始化流程...
return bean;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
不同的Aware接口在Bean生命周期的不同阶段被处理。例如,ApplicationContextAware
接口是通过ApplicationContextAwareProcessor
这个BeanPostProcessor来处理的,它在Bean实例化后的后处理阶段被调用。
# 4. 常用Aware接口使用
下面通过具体示例展示如何使用各种Aware接口:
# 4.1 BeanFactoryAware
BeanFactoryAware
接口允许我们获取到Spring的BeanFactory
实例,从而可以编程式地获取其他Bean。
/**
* BeanFactoryAware示例
* 通过实现BeanFactoryAware接口获取Spring的BeanFactory
*/
@Component
public class MyBeanFactoryAware implements BeanFactoryAware {
private BeanFactory beanFactory;
/**
* 当Bean被创建时,Spring容器会调用此方法并传入BeanFactory实例
* @param beanFactory Spring容器的BeanFactory实例
* @throws BeansException 如果获取BeanFactory时出错
*/
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
// 保存BeanFactory的引用,以便后续使用
this.beanFactory = beanFactory;
// 演示:从BeanFactory中获取AccountService实例
AccountService accountService = beanFactory.getBean(AccountService.class);
System.out.println("BeanFactoryAware:成功获取AccountService实例 - " + accountService);
}
/**
* 公共方法示例:使用保存的BeanFactory获取任意Bean
* @param beanClass 需要获取的Bean类型
* @return 对应类型的Bean实例
*/
public <T> T getBean(Class<T> beanClass) {
return this.beanFactory.getBean(beanClass);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 4.2 BeanNameAware
BeanNameAware
接口允许Bean获取自己在Spring容器中的名称。
/**
* BeanNameAware示例
* 通过实现BeanNameAware接口获取Bean在容器中的名称
*/
@Component
public class MyBeanNameAware implements BeanNameAware {
private String beanName;
/**
* 当Bean被创建时,Spring容器会调用此方法并传入Bean的名称
* @param name Bean在容器中的名称
*/
@Override
public void setBeanName(String name) {
// 保存Bean名称,以便后续使用
this.beanName = name;
System.out.println("BeanNameAware:当前Bean的名称是 - " + name);
}
/**
* 获取当前Bean的名称
* @return Bean名称
*/
public String getBeanName() {
return this.beanName;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 4.3 EnvironmentAware
EnvironmentAware
接口允许我们获取Spring应用的环境信息,包括配置文件中的属性、系统环境变量等。
/**
* EnvironmentAware示例
* 通过实现EnvironmentAware接口获取应用环境配置
*/
@Component
public class MyEnvironmentAware implements EnvironmentAware {
private Environment environment;
/**
* 当Bean被创建时,Spring容器会调用此方法并传入Environment实例
* @param environment Spring应用的环境配置实例
*/
@Override
public void setEnvironment(Environment environment) {
// 保存Environment引用,以便后续使用
this.environment = environment;
// 演示:获取配置文件中的服务端口配置
String serverPort = environment.getProperty("server.port");
System.out.println("EnvironmentAware:应用服务端口配置为 - " + serverPort);
// 获取多个配置属性示例
String appName = environment.getProperty("spring.application.name", "默认应用名");
boolean isDevProfile = environment.acceptsProfiles(Profiles.of("dev"));
System.out.println("应用名称: " + appName);
System.out.println("是否是开发环境: " + isDevProfile);
}
/**
* 获取任意配置属性的便捷方法
* @param key 配置键
* @return 配置值,如果不存在则返回null
*/
public String getProperty(String key) {
return this.environment.getProperty(key);
}
/**
* 获取带默认值的配置属性
* @param key 配置键
* @param defaultValue 默认值
* @return 配置值,如果不存在则返回默认值
*/
public String getProperty(String key, String defaultValue) {
return this.environment.getProperty(key, defaultValue);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 4.4 ApplicationContextAware
ApplicationContextAware
是最常用的Aware接口,它提供了对Spring应用上下文的完整访问,功能比BeanFactory
更加丰富。
/**
* ApplicationContextAware示例
* 通过实现ApplicationContextAware接口获取Spring应用上下文
*/
@Component
public class MyApplicationContextAware implements ApplicationContextAware {
private ApplicationContext applicationContext;
/**
* 当Bean被创建时,Spring容器会调用此方法并传入ApplicationContext实例
* @param applicationContext Spring应用上下文实例
* @throws BeansException 如果获取ApplicationContext时出错
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// 保存ApplicationContext引用,以便后续使用
this.applicationContext = applicationContext;
// 演示:从ApplicationContext中获取Bean
CouponService couponService = applicationContext.getBean(CouponService.class);
System.out.println("ApplicationContextAware:成功获取CouponService实例 - " + couponService);
// 演示:通过ApplicationContext获取环境配置
String serverPort = applicationContext.getEnvironment().getProperty("server.port");
System.out.println("ApplicationContextAware:应用服务端口配置为 - " + serverPort);
// 演示:获取应用名称
String appName = applicationContext.getApplicationName();
System.out.println("应用名称: " + appName);
// 演示:检查Bean是否存在
boolean userServiceExists = applicationContext.containsBean("userService");
System.out.println("userService是否存在: " + userServiceExists);
}
/**
* 获取指定类型的Bean
* @param clazz Bean类型
* @return Bean实例
*/
public <T> T getBean(Class<T> clazz) {
return this.applicationContext.getBean(clazz);
}
/**
* 根据名称获取Bean
* @param beanName Bean名称
* @return Bean实例
*/
public Object getBean(String beanName) {
return this.applicationContext.getBean(beanName);
}
/**
* 获取ApplicationContext,可用于外部访问
* @return ApplicationContext实例
*/
public ApplicationContext getApplicationContext() {
return this.applicationContext;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 4.5 ResourceLoaderAware
ResourceLoaderAware
接口允许我们获取Spring的资源加载器,用于加载各种资源文件。
/**
* ResourceLoaderAware示例
* 通过实现ResourceLoaderAware接口获取Spring资源加载器
*/
@Component
public class MyResourceLoaderAware implements ResourceLoaderAware {
private ResourceLoader resourceLoader;
/**
* 当Bean被创建时,Spring容器会调用此方法并传入ResourceLoader实例
* @param resourceLoader Spring资源加载器实例
*/
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
// 保存ResourceLoader引用,以便后续使用
this.resourceLoader = resourceLoader;
// 演示:加载类路径下的配置文件
Resource applicationYml = resourceLoader.getResource("classpath:application.yml");
System.out.println("ResourceLoaderAware:成功加载资源 - " + applicationYml.getDescription());
// 演示:加载多种资源
try {
// 加载类路径资源
Resource classpathResource = resourceLoader.getResource("classpath:logback.xml");
System.out.println("类路径资源是否存在: " + classpathResource.exists());
// 加载文件系统资源
Resource fileResource = resourceLoader.getResource("file:///D:/config/app-config.properties");
System.out.println("文件系统资源是否存在: " + fileResource.exists());
// 加载URL资源
Resource urlResource = resourceLoader.getResource("https://example.com/config.json");
System.out.println("URL资源描述: " + urlResource.getDescription());
} catch (Exception e) {
System.out.println("加载资源时发生错误: " + e.getMessage());
}
}
/**
* 加载指定路径的资源
* @param location 资源路径,如:classpath:config.xml, file:/config/app.properties
* @return 资源对象
*/
public Resource getResource(String location) {
return this.resourceLoader.getResource(location);
}
/**
* 读取资源内容为字符串
* @param location 资源路径
* @return 资源内容字符串
* @throws IOException 如果读取失败
*/
public String getResourceAsString(String location) throws IOException {
Resource resource = getResource(location);
try (InputStream is = resource.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
return reader.lines().collect(Collectors.joining("\n"));
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# 4.6 ApplicationEventPublisherAware
ApplicationEventPublisherAware
接口允许我们获取Spring的事件发布器,用于发布应用事件。
/**
* ApplicationEventPublisherAware示例
* 通过实现ApplicationEventPublisherAware接口获取Spring事件发布器
*/
@Component
public class MyEventPublisherAware implements ApplicationEventPublisherAware {
private ApplicationEventPublisher eventPublisher;
/**
* 当Bean被创建时,Spring容器会调用此方法并传入ApplicationEventPublisher实例
* @param eventPublisher Spring事件发布器实例
*/
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
// 保存事件发布器引用,以便后续使用
this.eventPublisher = eventPublisher;
System.out.println("ApplicationEventPublisherAware:成功获取事件发布器");
}
/**
* 发布自定义事件的示例方法
* @param message 事件携带的消息
*/
public void publishCustomEvent(String message) {
// 创建自定义事件对象
CustomEvent event = new CustomEvent(this, message);
// 通过事件发布器发布事件
eventPublisher.publishEvent(event);
System.out.println("已发布自定义事件: " + message);
}
/**
* 发布任意事件对象
* @param event 要发布的事件对象
*/
public void publishEvent(Object event) {
this.eventPublisher.publishEvent(event);
}
/**
* 自定义事件类示例
*/
public static class CustomEvent extends ApplicationEvent {
private final String message;
public CustomEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 4.7 MessageSourceAware
MessageSourceAware
接口允许我们获取Spring的消息源,用于国际化和多语言支持。
/**
* MessageSourceAware示例
* 通过实现MessageSourceAware接口获取Spring消息源
*/
@Component
public class MyMessageSourceAware implements MessageSourceAware {
private MessageSource messageSource;
/**
* 当Bean被创建时,Spring容器会调用此方法并传入MessageSource实例
* @param messageSource Spring消息源实例
*/
@Override
public void setMessageSource(MessageSource messageSource) {
// 保存消息源引用,以便后续使用
this.messageSource = messageSource;
// 演示:获取国际化消息
String welcome = messageSource.getMessage("welcome", null, Locale.getDefault());
System.out.println("MessageSourceAware:默认语言的欢迎消息 - " + welcome);
// 获取带参数的消息
String greeting = messageSource.getMessage("greeting", new Object[]{"John"}, Locale.US);
System.out.println("英文问候: " + greeting);
// 获取中文消息
String chineseGreeting = messageSource.getMessage("greeting", new Object[]{"张三"}, Locale.CHINESE);
System.out.println("中文问候: " + chineseGreeting);
}
/**
* 获取指定语言的国际化消息
* @param code 消息代码
* @param args 消息参数
* @param locale 语言环境
* @return 格式化后的国际化消息
*/
public String getMessage(String code, Object[] args, Locale locale) {
return this.messageSource.getMessage(code, args, locale);
}
/**
* 获取当前语言环境的国际化消息
* @param code 消息代码
* @param args 消息参数
* @return 格式化后的国际化消息
*/
public String getMessage(String code, Object[] args) {
return this.messageSource.getMessage(code, args, Locale.getDefault());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 5. 实际应用场景
# 5.1 Aware接口的应用场景
- 通用工具类:创建全局可访问的工具Bean,提供对Spring容器资源的访问。
/**
* Spring上下文工具类
* 通过ApplicationContextAware接口实现全局访问Spring容器的能力
*/
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}
/**
* 获取ApplicationContext
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通过名称获取Bean
*/
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
/**
* 通过类型获取Bean
*/
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
/**
* 获取配置属性
*/
public static String getProperty(String key) {
return applicationContext.getEnvironment().getProperty(key);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
- 定制化配置处理:在应用启动时读取特定配置并进行处理。
/**
* 数据库配置处理器
* 在应用启动时初始化数据库连接池配置
*/
@Component
public class DatabaseConfigProcessor implements EnvironmentAware {
@Override
public void setEnvironment(Environment environment) {
// 获取数据库配置
String dbUrl = environment.getProperty("spring.datasource.url");
String dbUsername = environment.getProperty("spring.datasource.username");
// 处理敏感信息
String dbPassword = environment.getProperty("spring.datasource.password");
if (dbPassword != null) {
// 解密密码(示例)
dbPassword = decryptPassword(dbPassword);
// 重新设置解密后的密码(通过系统属性)
System.setProperty("DB_DECRYPTED_PASSWORD", dbPassword);
}
System.out.println("数据库配置已处理: " + dbUrl);
}
private String decryptPassword(String encryptedPassword) {
// 实现密码解密逻辑
return "decrypted:" + encryptedPassword;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- 自定义事件发布:实现业务事件的发布与监听。
/**
* 订单服务
* 使用事件发布器实现订单状态变更通知
*/
@Service
public class OrderService implements ApplicationEventPublisherAware {
private ApplicationEventPublisher eventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
/**
* 创建新订单
*/
public Order createOrder(OrderRequest request) {
// 创建订单逻辑
Order newOrder = new Order();
newOrder.setId(generateOrderId());
newOrder.setCustomerId(request.getCustomerId());
newOrder.setAmount(request.getAmount());
newOrder.setStatus("CREATED");
// 保存订单
// orderRepository.save(newOrder);
// 发布订单创建事件
eventPublisher.publishEvent(new OrderCreatedEvent(newOrder));
return newOrder;
}
/**
* 更新订单状态
*/
public void updateOrderStatus(Long orderId, String newStatus) {
// 获取订单
Order order = getOrderById(orderId);
String oldStatus = order.getStatus();
// 更新状态
order.setStatus(newStatus);
// orderRepository.update(order);
// 发布订单状态变更事件
eventPublisher.publishEvent(
new OrderStatusChangedEvent(order, oldStatus, newStatus));
}
// 模拟方法 - 实际项目中应连接数据库
private Order getOrderById(Long orderId) {
Order order = new Order();
order.setId(orderId);
order.setStatus("PENDING");
return order;
}
private Long generateOrderId() {
return System.currentTimeMillis();
}
// 内部类 - 订单请求
@Data
public static class OrderRequest {
private Long customerId;
private Double amount;
}
// 内部类 - 订单实体
@Data
public static class Order {
private Long id;
private Long customerId;
private Double amount;
private String status;
}
// 内部类 - 订单创建事件
public static class OrderCreatedEvent extends ApplicationEvent {
public OrderCreatedEvent(Order order) {
super(order);
}
public Order getOrder() {
return (Order) getSource();
}
}
// 内部类 - 订单状态变更事件
public static class OrderStatusChangedEvent extends ApplicationEvent {
private final String oldStatus;
private final String newStatus;
public OrderStatusChangedEvent(Order order, String oldStatus, String newStatus) {
super(order);
this.oldStatus = oldStatus;
this.newStatus = newStatus;
}
public Order getOrder() {
return (Order) getSource();
}
public String getOldStatus() {
return oldStatus;
}
public String getNewStatus() {
return newStatus;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# 5.2 最佳实践
谨慎使用:Aware接口引入了对Spring容器的依赖,破坏了组件的独立性。只在必要时使用。
选择最小权限的接口:如果只需要特定功能,选择对应的专用Aware接口,而不是总是使用ApplicationContextAware。
考虑替代方案:在许多情况下,可以使用依赖注入替代Aware接口。例如,可以直接注入Environment而不是实现EnvironmentAware。
注意线程安全:在多线程环境中使用Aware接口获取的资源时,需要考虑线程安全问题。
避免在构造函数中使用:Aware接口的回调方法是在Bean实例化后调用的,不要在构造函数中尝试使用这些资源。