本文内容

  1. Environment抽象的2个重要概念
  2. @Profile 的使用
  3. @PropertySource 的使用

Environment抽象的2个重要概念

Environment 接口表示当前应用程序运行环境的接口。对应用程序环境的两个关键方面进行建模:配置文件( profiles )和属性(properties)。与属性访问相关的方法通过 PropertyResolver 超接口公开。环境对象的配置必须通过 ConfigurableEnvironment 接口完成,该接口从所有 AbstractApplicationContext 子类 getEnvironment() 方法返回

环境与配置文件

配置文件是一个命名的、逻辑的 bean 定义组,仅当给定的配置文件处于活动状态时才向容器注册。可以将 Bean 分配给配置文件,无论是在 XML 中定义还是通过注释 @Profile 定义;与配置文件相关的环境对象的作用是确定哪些配置文件(如果有)当前处于活动状态,以及哪些配置文件(如果有)默认应该是活动的

环境与属性

属性在几乎所有应用程序中都发挥着重要作用,并且可能源自多种来源:属性文件、JVM 系统属性、系统环境变量、JNDI、servlet 上下文参数、属性对象、map等。与属性相关的环境对象的作用是为用户提供一个方便的服务接口,用于配置属性源并从中解析属性

在 ApplicationContext 中管理的 Bean 可以注册为 EnvironmentAware 或 @Inject Environment,以便直接查询配置文件状态或解析属性。然而,在大多数情况下,应用程序级别的 bean 不需要直接与 Environment 交互,而是可能必须将 ${...} 属性值替换为属性占位符配置器,例如 PropertySourcesPlaceholderConfigurer,它本身是 EnvironmentAware 并且从 Spring 3.1 开始使用 context:property-placeholder 时默认注册 ,或是通过java bean的方式注册到容器中。

PropertySourcesPlaceholderConfigurer 分析可以阅读上一篇: Spring系列14:IoC容器的扩展点

接口源码粗览

接口继承关系

接口源码如下提供配置文件相关的接口方法,其继承的 PropertyResolver 提供属性相关的接口。

public interface Environment extends PropertyResolver {
// 当前激活的配置文件列表
// 设置系统属性值 spring.profiles.active=xxx 可激活
// 或是调用 ConfigurableEnvironment#setActiveProfiles(String...)激活
String[] getActiveProfiles(); // 当没有明确设置活动配置文件时,默认配置文件集返回为活动状态。
String[] getDefaultProfiles(); // 返回活动配置文件是否与给定的 Profiles 匹配
boolean acceptsProfiles(Profiles profiles); }

PropertyResolver 是针对任何底层源解析属性的接口,主要接口方法如下。有一个非常重要的实现类是 PropertySourcesPlaceholderConfigurer 。

public interface PropertyResolver {

	// 是否包含属性
boolean containsProperty(String key);
// 获取属性值
String getProperty(String key);
// 获取属性值带默认值
String getProperty(String key, String defaultValue);
// 获取属性值
<T> T getProperty(String key, Class<T> targetType);
// 获取属性值带默认值
<T> T getProperty(String key, Class<T> targetType, T defaultValue); // 获取属性值
String getRequiredProperty(String key) throws IllegalStateException;
// 获取属性值
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException; // 解析给定文本中的 ${...} 占位符
String resolvePlaceholders(String text); // 解析给定文本中的 ${...} 占位符
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException; }

ConfigurablePropertyResolver 是大多数 PropertyResolver 类型都将实现的配置接口。提供用于访问和自定义将属性值从一种类型转换为另一种类型时使用的 ConversionService 的工具。

public interface ConfigurablePropertyResolver extends PropertyResolver {

   ConfigurableConversionService getConversionService();

   void setConversionService(ConfigurableConversionService conversionService);

   // 设置占位符前缀 默认的 "${"怎么来的
void setPlaceholderPrefix(String placeholderPrefix); // 设置占位符后缀 默认的 "}"怎么来的
void setPlaceholderSuffix(String placeholderSuffix); // 设置占位符值分分隔符 默认的 ":"怎么来的
void setValueSeparator(@Nullable String valueSeparator); void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders); void setRequiredProperties(String... requiredProperties); void validateRequiredProperties() throws MissingRequiredPropertiesException; }

ConfigurableEnvironment是大多数环境类型都将实现的配置接口。提供用于设置活动和默认配置文件以及操作基础属性源的工具。允许客户端通过 ConfigurablePropertyResolver 超级接口设置和验证所需属性、自定义转换服务等。

public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {

	void setActiveProfiles(String... profiles);

	void addActiveProfile(String profile);

	void setDefaultProfiles(String... profiles);

	MutablePropertySources getPropertySources();

	// 关键的系统属性 System#getProperties()
Map<String, Object> getSystemProperties(); // 关键的系统环境 System#getenv()
Map<String, Object> getSystemEnvironment(); void merge(ConfigurableEnvironment parent);
}

@Profile 的使用

@Profile 表示当一个或多个profiles处于活动状态时,组件有资格注册。可以通过以下的方式设置活跃的一个或是多个配置文件:

  • 编程方式:ConfigurableEnvironment#setActiveProfiles(String...)
  • 启动参数: -Dspring.profiles.active="profile1,profile2"
  • xml配置方式:

使用案例

来看一个实际场景:不同环境要求在容器中注入不同类型的的数据源,dev环境使用H2,生产环境prod使用Mysql,default环境使用 HSQL。

定义不同环境的数据源,并标识 @Profile

@Configuration
@ComponentScan
public class AppConfig { // 测试环境数据源H2
@Profile("dev")
@Bean
public DataSource devDataSource() {
DataSource dataSource = new DataSource();
dataSource.setType("H2");
dataSource.setUrl("jdbc:h2:xxxxxx");
return dataSource;
}
// 生产环境数据源mysql
@Profile("prod")
@Bean
public DataSource prodDataSource() {
DataSource dataSource = new DataSource();
dataSource.setType("mysql");
dataSource.setUrl("jdbc:mysql:xxxxxx");
return dataSource;
} // default 环境的 HSQL
@Profile("default")
@Bean
public DataSource defaultDataSource() {
DataSource dataSource = new DataSource();
dataSource.setType("HSQL");
dataSource.setUrl("jdbc:HSQL:xxxxxx");
return dataSource;
} }

测试程序,首先不指定 profile

@org.junit.Test
public void test_profile() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext();
// context.getEnvironment().setActiveProfiles("prod");
context.register(AppConfig.class);
context.refresh();
DataSource dataSource = context.getBean(DataSource.class);
System.out.println(dataSource.getType());
context.close();
}
// 输出结果
HSQL

从结果可知,注册到容器中的 default 环境对应的 HSQL

指定 profile 为 prod ,观察输出

context.getEnvironment().setActiveProfiles("prod")
// 结果
mysql

从结果可知,注册到容器中的 prod 环境对应的 mysql 。

支持逻辑操作符

支持与或非操作组合

  • &
  • |

组合&和|必须使用小括号

反例:production & us-east | eu-central

正例:production & (us-east | eu-central)

使用 @Profile 自定义组合注解

定义组合注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}

使用

@Configuration
@Production
public class MyConfiguration {
}

如果@Configuration 类用@Profile 标记,则与该类关联的所有@Bean 方法和@Import 注释都将被绕过,除非一个或多个指定的配置文件处于活动状态。

使用xml指定 profile

标签中的 profile元素的可以指定配置文件。

<?xml version="1.0" encoding="UTF-8"?>
<beans profile="prod"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="com.crab.spring.ioc.demo13.DataSource" id="dataSource">
<property name="type" value="mysql"/>
<property name="url" value="jdbc:mysql/xxxxx"/>
</bean> </beans>

PropertySource 抽象

Spring 的 Environment 抽象在可配置的属性源层次结构上提供搜索操作。来看下案例如何从Spring 容器获取属性。

@org.junit.Test
public void test_property_source() {
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
}

PropertySource 是对任何键值对源的简单抽象。Spring 的 StandardEnvironment 配置了两个 PropertySource 对象:

  • 一个表示一组 JVM 系统属性 (System.getProperties())

  • 一个表示一组系统环境变量 (System.getenv())

public class StandardEnvironment extends AbstractEnvironment {

	/** System environment property source name: {@value}. */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment"; /** JVM system properties property source name: {@value}. */
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties"; // 自定义适合任何标准的属性源自定义一组属性源
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(
new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
} }

在属性源中查找属性是否存在的优先级顺序如下,从高到低:

  1. ServletConfig parameters (web上下文)
  2. ServletContext parameters (web.xml context-param entries)
  3. JNDI environment variables (java:comp/env/ entries)
  4. JVM system properties (-D command-line arguments)
  5. JVM system environment (operating system environment variables)

自定义 PropertySource

自定义 MyPropertySource 实现 Property 提供基于 Map 属性键值对的属性源

/**
* 自定义 PropertySource
* @author zfd
* @version v1.0
* @date 2022/1/22 22:13
* @关于我 请关注公众号 螃蟹的Java笔记 获取更多技术系列
*/
public class MyPropertySource extends PropertySource<Map<String, Object>> { public MyPropertySource(String name, Map<String, Object> source) {
super(name, source);
} public MyPropertySource(String name) {
super(name);
} @Override
public Object getProperty(String name) {
return this.source.get(name);
}
}

添加到Spring 容器环境中,优先级最高

@org.junit.Test
public void test_custom_property_source() { ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
Map<String, Object> map = new HashMap<>();
map.put("my-property", "xxx");
sources.addFirst(new MyPropertySource("myPropertySource",map));
// true
boolean containsMyProperty = ctx.getEnvironment().containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
}

@PropertySource 使用

相比上面的编程式添加 PropertySource,@PropertySource 注解为将 PropertySource 添加到 Spring 的环境中提供了一种方便且声明性的机制。直接看案例。

app.properties配置

testBean.name=xxx

配置类

@Configuration
// 注入配置文件
@PropertySource("classpath:demo13/app.properties")
public class AppConfig3 { @Autowired
private Environment env; @Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testBean.name"));
return testBean;
}
}

测试结果观察

    @org.junit.Test
public void test_property_source_annotation() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig3.class);
TestBean testBean = context.getBean(TestBean.class);
System.out.println(testBean.getName());
}
// 结果
xxx

@PropertySource 中指定配置文件也是可以使用占位符${...}的。如果环境中属性值my.config.path已经存在则进行解析,否则使用默认值demo13

@Configuration
// 注入配置文件
@PropertySource("classpath:${my.config.path:demo13}/app.properties")
public class AppConfig3 {}

总结

本文介绍了Spring中的Environment抽象的2个重要概念:Bean定义配置文件和属性源。同时介绍了@Profile使用和@PropertySource 的使用。

本篇源码地址: https://github.com/kongxubihai/pdf-spring-series/tree/main/spring-series-ioc/src/main/java/com/crab/spring/ioc/demo13

知识分享,转载请注明出处。学无先后,达者为先!

Spring系列15:Environment抽象的更多相关文章

  1. Spring Environment抽象

    1:概述 Spring中Environment是Spring3.1版本引入的,是Spring核心框架定义的一个接口,用来表示整个应用运行时环境.该环境模型只接受两种应用环境profiles(配置文件) ...

  2. Spring系列之JDBC对不同数据库异常如何抽象的?

    前言 使用Spring-Jdbc的情况下,在有些场景中,我们需要根据数据库报的异常类型的不同,来编写我们的业务代码.比如说,我们有这样一段逻辑,如果我们新插入的记录,存在唯一约束冲突,就会返回给客户端 ...

  3. Spring系列.Environment接口

    Environment 接口介绍 在 Spring 中,Environment 接口主要管理应用程序两个方面的内容:profile 和 properties. profile 可以简单的等同于环境,比 ...

  4. Spring系列(零) Spring Framework 文档中文翻译

    Spring 框架文档(核心篇1和2) Version 5.1.3.RELEASE 最新的, 更新的笔记, 支持的版本和其他主题,独立的发布版本等, 是在Github Wiki 项目维护的. 总览 历 ...

  5. 朱晔和你聊Spring系列S1E2:SpringBoot并不神秘

    朱晔和你聊Spring系列S1E2:SpringBoot并不神秘 [编辑器丢失了所有代码的高亮,建议查看PDF格式文档] 文本我们会一步一步做一个例子来看看SpringBoot的自动配置是如何实现的, ...

  6. Spring 系列: Spring 框架简介 -7个部分

    Spring 系列: Spring 框架简介 Spring AOP 和 IOC 容器入门 在这由三部分组成的介绍 Spring 框架的系列文章的第一期中,将开始学习如何用 Spring 技术构建轻量级 ...

  7. Java 集合系列 15 Map总结

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  8. Spring 系列: Spring 框架简介(转载)

    Spring 系列: Spring 框架简介 http://www.ibm.com/developerworks/cn/java/wa-spring1/ Spring AOP 和 IOC 容器入门 在 ...

  9. Spring系列

    Spring系列之访问数据库   阅读目录 一.概述 二.JDBC API的最佳实践 三.Spring对ORM的集成 回到顶部 一.概述 Spring的数据访问层是以统一的数据访问异常层体系为核心,结 ...

随机推荐

  1. Python入门(上)

    Python入门(上) Python入门(上) 简介 Python 基础语法 行与缩进 注释 运算符 标准数据类型 变量 编程流程 顺序(略) 分支 if 循环 for while break 和 c ...

  2. 阿里云服务器ECS Ubuntu16.04 + Seafile 搭建私人网盘 (Seafile Pro)

    原文链接:? 传送门 本文主要讲述 使用 Ubuntu 16.04 云服务器 通过脚本实现对 Seafile Pro 的安装,完成私人网盘的搭建 首先给出 Seafile 专业版的下载地址(Linux ...

  3. boot项目启动成功 接口全部404

    今天开发的时候遇到一个404的错误,路径启动类位置都对,就是404很气人.记录下解决的过程,以供遇到同等困惑的小伙伴参考 404原因排查步骤 首先按照下面步骤检查一遍 首先检查路径是否正确,把路径重新 ...

  4. echarts-gl初体验:使用echarts-gl实现3D地球

    首先我们要下载引入echarts.js和echarts-gl.js 有需要的自己拿资源哈 链接:https://pan.baidu.com/s/1J7U79ey-2ZN4pjb7RTarjg 提取码: ...

  5. leetcode 1021. 删除最外层的括号

    问题描述 有效括号字符串为空 ("")."(" + A + ")" 或 A + B,其中 A 和 B 都是有效的括号字符串,+ 代表字符串的 ...

  6. Java基础(十)——枚举与注解

    一.枚举 1.介绍 枚举类:类的对象只有有限个,确定的.当需要定义一组常量时,强烈建议使用枚举类.如果枚举类中只有一个对象,则可以作为单例模式的实现. 使用 enum 定义的枚举类默认继承了 java ...

  7. CentOS 7安装Odoo 15社区版的详细操作指南

    我之前的文章介绍过在Windows环境下安装Odoo 15,如果您需要在Windows部署,具体可参考我文末的微信号<10分钟教你本机电脑安装Odoo 15,并启用一个内置的项目APP应用> ...

  8. conda : 无法将“conda”项识别为 cmdlet、函数、脚本文件或可运行程序的名称

    conda : 无法将"conda"项识别为 cmdlet.函数.脚本文件或可运行程序的名称.请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次. 解决办法: 没有添加系 ...

  9. 【记录一个问题】linux + opencv + gpu视频解码,好不容易编译通过,运行又coredump了

    1.首先编译了opencv + cuda   编译选项中使用了以下关于cuvid库的内容: //"nvcuvid" libraryCUDA_nvcuvid_LIBRARY:FILE ...

  10. 聊一聊如何用C#轻松完成一个SAGA分布式事务

    背景 银行跨行转账业务是一个典型分布式事务场景,假设 A 需要跨行转账给 B,那么就涉及两个银行的数据,无法通过一个数据库的本地事务保证转账的 ACID ,只能够通过分布式事务来解决. 市面上使用比较 ...