Spring5中文文档【10】IOC容器之Profile和PropertySource

1. 前言

本系列基于最新5.3.10版本,大部分内容copy于官方文档…
官方文档地址

Environment环境接口是集成在容器中的抽象,它对应用程序环境的两个关键方面建模:Profile和属性。

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

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

2. Bean 定义Profile

Bean 定义Profile在核心容器中提供了一种机制,允许在不同环境中注册不同的 Bean。“环境”这个词对不同的用户可能有不同的含义,此功能可以帮助解决许多用例,包括:

  • 在开发中使用内存数据源,而不是在 QA测试 或生产中从 JNDI 查找相同的数据源。

  • 仅在将应用程序部署到性能环境中时才注册监控基础设施。

  • 为客户 A 和客户 B 部署注册 bean 的自定义实现。

比如我们开发环境下,注入的DataSource数据源如下:

@Bean
public DataSource dataSource() {
    
    
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("my-schema.sql")
        .addScript("my-test-data.sql")
        .build();
}

那么在测试或者生产环境中部署程序时,就需要考虑切换数据源配置,如何根据当前环境进行不同的数据源Bean切换呢?Bean 定义配置文件是核心容器的一个特性,它为这个问题提供了解决方案。

2.1 使用@Profile

@Profile注解由spring-context模块提供。只有当@Profile指定的环境配置被激活时,才会将@Profile中所对应的Bean注册到Spring容器中。

@Target({
    
    ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({
    
    ProfileCondition.class})
public @interface Profile {
    
    
    String[] value();
}

比如上述的dataSource数据源配置,可以使用@Profile定义在development环境下才加载当前Bean对象。

@Configuration
@Profile("development")
public class StandaloneDataConfig {
    
    

    @Bean
    public DataSource dataSource() {
    
    
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}

在生产环境下,指定@Profile的值为production。

@Configuration
@Profile("production")
public class JndiDataConfig {
    
    

    @Bean(destroyMethod="")
    public DataSource dataSource() throws Exception {
    
    
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

当然也可以指定多个值:

@Profile({
    
    "p1", "!p2"})

也可以结合@Bean注解直接使用

@Configuration
public class AppConfig {
    
    
	// 该standaloneDataSource方法仅在development配置环境中可用。
    @Bean("dataSource")
    @Profile("development") 
    public DataSource standaloneDataSource() {
    
    
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
	// 该jndiDataSource方法仅在production配置环境中可用。
    @Bean("dataSource")
    @Profile("production") 
    public DataSource jndiDataSource() throws Exception {
    
    
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

可以用@Profile作为元注解来创建自定义组合注解。以下示例定义了一个自定义 @Production注解,您可以将其用作 @Profile(“production”)的替代品 :

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

2.2 XML Bean 定义Profile

XML 中对应的是<beans>元素的profile属性,我们前面的示例配置可以改写为两个 XML 文件,如下所示:

<beans profile="development"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xsi:schemaLocation="...">

    <jdbc:embedded-database id="dataSource">
        <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
        <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
    </jdbc:embedded-database>
</beans>
<beans profile="production"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>

2.3 激活Profile

在之前我们配置了各个环境的Profile,然后我们需要指定当前应用程序的环境,Spring提供了多种方式激活。

但最直接的是针对Environment API 以编程方式进行,该API 可通过 ApplicationContext,以下示例显示了如何执行此操作:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();

当然也可以一次性激活多个:

ctx.getEnvironment().setActiveProfiles("profile1", "profile2");

还可以通过spring.profiles.active属性声明性地激活配置文件,这些属性可以通过系统环境变量、JVM 系统属性、servlet 上下文参数、web.xml指定,甚至可以作为JNDI 中的条目(请参阅PropertySource抽象)。在集成测试中,可以使用模块中的@ActiveProfiles注解来声明活动配置文件spring-test(请参阅环境配置文件的上下文配置)。

2.4 默认Profile

用过spring boot的应该知道,在项目启动时,如果没有指定Profile,则会使用default作为默认的Profile。

对于以下示例,如果没有Profile处于活动状态,dataSource则会创建 。可以将其视为为一个或多个 bean 提供默认定义的一种方式。如果启用了任何Profile,则默认Profile不适用。

@Configuration
@Profile("default")
public class DefaultDataConfig {
    
    

    @Bean
    public DataSource dataSource() {
    
    
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .build();
    }
}

还可以更改默认Profile的名称,方法是使用Environment中setDefaultProfiles()方法,或者使用spring.profiles.default属性配置。

3. PropertySource

Spring 的Environment 提供了对属性源PropertySource的配置和搜索操作。

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);

上述代码中,从Spring 的Environment 中查询当前环境是否定义了 my-property 属性,Environment对一组对象执行搜索PropertySource,PropertySource是对任何键值对属性的简单抽象,可以理解PropertySource为应用程序环境配置的属性对象。

比如Environment 的实现类StandardEnvironment ,配置了两个 PropertySource 对象,

  • systemProperties: JVM 系统属性
  • systemEnvironment:系统环境变量
public class StandardEnvironment extends AbstractEnvironment {
    
    
    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

    public StandardEnvironment() {
    
    
    }

    protected StandardEnvironment(MutablePropertySources propertySources) {
    
    
        super(propertySources);
    }

    protected void customizePropertySources(MutablePropertySources propertySources) {
    
    
        propertySources.addLast(new PropertiesPropertySource("systemProperties", this.getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", this.getSystemEnvironment()));
    }
}

而StandardServletEnvironment 填充了额外的默认属性源,包括 servlet 配置和 servlet 上下文参数。

public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
    
    

	/** Servlet context init parameters property source name: {@value}. */
	public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";

	/** Servlet config init parameters property source name: {@value}. */
	public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";

	/** JNDI property source name: {@value}. */
	public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";
	
	@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
    
    
		propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
		propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
		if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
    
    
			propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
		}
		super.customizePropertySources(propertySources);
	}

	@Override
	public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
    
    
		WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
	}

}

在查询属性时是分层的。默认情况下,系统属性优先于环境变量。如果my-property在调用期间碰巧在两个地方都设置了该属性,则系统属性值会优先查询到并返回。请注意,属性值不会合并,而是被前面的条目完全覆盖。

对于StandardServletEnvironment,完整的层次结构如下,最高优先级的条目位于顶部:

  • ServletConfig 参数(如果适用例如,在DispatcherServlet上下文的情况下)

  • ServletContext 参数(web.xml 上下文参数条目)

  • JNDI 环境变量(java:comp/env/条目)

  • JVM 系统属性(-D 命令行执行参数)

  • JVM系统环境(操作系统环境变量)

整个机制是可配置的。请实现并实例化自定义的PropertySource,并将其添加到当前Environment. 以下示例显示了如何执行此操作:

ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());

MyPropertySource已最高优先级添加。如果它包含一个my-property属性,则会优先查询并返回该属性。

4. 使用@PropertySource注解

@PropertySource 注解提供了一种方便且声明性的机制来添加PropertySource 到 Spring 的Environment中。

比如我们首先创建一个app.properties配置文件,配置一个属性(testbean.name=myTestBean),然后在@Configuration类中使用@PropertySource引入这个配置文件,然后就可以在环境中获取这个属性。

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
    
    

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
    
    
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

还可以使用${…​}占位符,在@PropertySource都根据针对环境注册的一组属性源进行解析,如以下示例所示:

@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
    
    

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
    
    
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

假设my.placeholder存在于已注册的属性源之一(例如,系统属性或环境变量)中,占位符将解析为相应的值。如果没有,则将default/path其用作默认值。如果未指定默认值且无法解析属性,则抛出 IllegalArgumentException异常。

@PropertySource注解是可重复配置多个的。但是,所有@PropertySource都需要在同一级别声明,可以直接在配置类上声明,也可以作为同一自定义注解中的元注解。不推荐混合直接注解和元注解,因为直接注解有效地覆盖了元注解。

5. 语句中的占位符解析

在之前,xml标签中的占位符的值只能根据 JVM 系统属性或环境变量进行解析。因为Environment是集成在整个容器中的,所以很容易通过它来路由占位符的解析。

这意味着可以按照自己喜欢的任何方式配置解析过程。您可以更改搜索系统属性和环境变量的优先级或完全删除它们。您还可以根据需要将自己的属性源添加到组合中。

具体来说,无论属性在哪里定义,以下语句都有效,只要它在可用Environment的中:

<beans>
    <import resource="com/bank/service/${customer}-config.xml"/>
</beans>

猜你喜欢

转载自blog.csdn.net/qq_43437874/article/details/120713080
今日推荐