Bootstrap

Spring 5 中文解析核心篇-IoC容器之Environment抽象

1.13 Environment抽象

接口是在容器中抽象集成,应用环境包括两个重要的方面: 和 。配置文件是一个命名的bean定义逻辑组,只有在给定的配置文件处于激活状态时才在容器中注册。可以将bean分配给配置文件,不管它是用XML定义的还是用注解定义的。与文件相关的环境对象的角色是确定哪些文件(如果有的话)当前是激活的,以及哪些文件(如果有的话)在默认情况下应该是激活的。

几乎在所有应用中扮演一个重要的角色并且可能来自于多个源:属性文件、JVM系统属性、系统环境变量、JNDI、servlet上下文参数、Properties对象、Map对象等等。对象的角色与关联去提供给用户一个便利的服务接口去配置属性源和解析属性。

1.13.1 Bean定义Profiles

bean定义属性文件在核心容器中提供一个机制,它允许在不同的环境中注册不同bean。环境这个词对于不同的用户可能意味着不同的东西,这个特性可以帮助许多使用场景,包括:

  • 在开发中针对内存中的数据源进行工作,而不是在进行QA或生产时从JNDI查找相同的数据源。

  • 当部署应用到执行环境使用注册健康基础设施

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

在实践应用中考虑第一个使用场景,它需要获取一个。在测试环境中,配置假设如下:

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

现在考虑这个应用怎样部署到QA或生产环境,假设应用程序数据源注册在生成应用服务JNDI目录。我们的bean看起来类似下面清单:

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

问题是如何根据当前环境在使用这两种变体之间进行切换。随着时间的流逝,Spring用户已经设计出多种方法来完成此任务,通常依赖系统环境变量和包含占位符的XML 语句的组合,这些语句根据值解析为正确的配置文件路径环境变量。Bean定义配置文件是一项核心容器功能,可解决此问题。

如果我们概括前面环境特定的bean定义示例中所示的用例,我们最终需要在特定上下文中注册特定的bean定义,而不是在其他上下文中注册。可以这样说,你希望在情形A中注册bean定义的某个配置文件,而在情形B中注册另一个配置文件。我们开始更新配置以反映这种需求。

使用

注解允许你去指明哪些组件适合去注册,当一个或多个指定处于激活状态时。使用我们前面的例子,我们可以重写配置如下:

@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();
    }
}

@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");
    }
}

如前所述,对于方法,你典型的选择使用编程式的JNDI查找,通过使用Spring的帮助类或直接使用前面展示的JNDI ,而不是使用变体,这将迫使你将返回类型声明为类型。

配置文件字符串可能包含一个简单的配置名称(例如,)或一个配置表达式。一个配置表达式允许更复杂的配置逻辑去表达(例如,),下面的操作符在表达式中是被支持的:

  • !:逻辑非

  • &:逻辑与

  • |:逻辑或

你不能混合和操作符而不使用括号。例如,是无效表达式。它必须被表达类似。

你可以使用作为一个元数据注解去创建你自定义的注解。以下示例定义了一个自定义注解,你可以将其用作的替代。

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

如果类被标注,所有的方法和注解关联的类都将被绕过,除非一个或多个指定的配置文件被激活。如果或类被标记,这个类不会被注册或处理除非配置文件 或 被激活。如果给的配置前缀是操作符(),注解元素仅仅在配置文件没有被激活时被注册。例如,,如果配置 被激活或者配置没有被激活时注册才会发生。

也可以被声明在方法级别去包含一个特定的配置bean类(例如,用于特定bean的替代),类似下面例子展示:

@Configuration
public class AppConfig {

    @Bean("dataSource")
    @Profile("development") //1
    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();
    }

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

对于方法上的,可以应用一个特殊的场景:在重载相同Java方法名的方法(类似于构造函数重载)的情况下,需要一致地在所有重载方法上声明条件。如果条件不一致,则只有重载方法中第一个声明的条件重要。因此,不能被使用去选择具有特定参数签名重载方法。在创建时,同一bean的所有工厂方法之间的解析都遵循Spring的构造函数解析方法。

如果你想去定义不同配置条件的bean,使用不同的Java方法名称,通过使用 name属性指向相同名称的bean,类似前面展示例子。如果参数前面都相同(例如,所有的变体有无参构造函数),这是在一个有效的Java类中表示这种安排的唯一方法(因为只能有一个具有特定名称和参数签名的方法)。

XML bean定义配置文件

XML对应项是元素的属性。我们前面的相同配置能被重写在两个XML文件中,类似下面:



    
        
        
    



    

也可以避免在同一文件中拆分和嵌套元素,如以下示例所示:



    

    
        
            
            
        
    

    
        
    

已被限制为仅允许这些元素作为文件中的最后一个。这应该有助于提供灵活性,而不会在XML文件中引起混乱。

XML对应项不支持前面描述的配置文件表达式:然而,它可能通过操作符否定一个配置文件。它也可能通过嵌入配置文件应用逻辑,类型下面例子显示:



 

     
         
     
 

在前面的例子中,如果和配置都被激活,则 bean被暴露。

激活profile

现在我们已经更新了配置文件,我们仍然需要指示Spring哪一个配置文件激活。如果我们已经启动了我们的应用程序,我们将看到一个抛出,因为容器不能找到名称为的bean。

可以通过多种方式来激活配置文件,但最直接的方法是可通过获得的 API以编程方式进行配置。下面例子展示怎样去做:

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

此外,你也可以声明式地激活配置文件通过属性,也可通过系统环境变量、JVM属性、在web.xml中servlet上下文参数,甚至作为JNDI中的条目(查看抽象)。在集成测试中,激活配置文件可以通过使用声明在模块(查看)。

请注意,配置文件不是的命题。你可以一次性激活多个配置文件。编程式地,你可以提供多个配置文件名称给方法,它可以接受 可变参数。下面例子激活多个配置文件:

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

声明式地,可以接收配置名称逗号分隔列表,类似下面例子展示:

-Dspring.profiles.active="profile1,profile2"

默认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();
    }
}

如果没有被激活,实例被创建。你可以看到这种方式提供一个默认的定义为一个或多个bean。如果任何被激活,这个默认profile不被使用。

你可以通过在上使用改变默认名称或者,声明式地,通过使用属性。

1.13.2 抽象

Spring的抽象提供了可配置属性源层次结构上的搜索操作。考虑下面清单:

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的方式,是否属性在当前环境中被定义:去回答这个问题,对象执行搜索 集合对象。是一个简单对源的抽象,并且Spring的被配置两个 对象,一个描述JVM系统()属性集合另一个描述系统环境变量集合()。

这些默认的属性源适用,为在独立的应用中使用。 是通过附加默认属性源填充,包括servlet配置和servlet上下文参数。它可以选择启用。查看javadock详情。

具体地说,当你使用时,如果系统属性或环境变量在运行时被描述,调用方法将返回true。

执行的搜索是分层的,默认情况,系统属性优先级高于环境变量。因此,如果属性在两个地方被设置,在调用时,系统属性值将被返回。请注意,属性值不会合并,而是会被前面的值完全覆盖。

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

参数(如果使用的-例如,如果是上下文)

参数(web.xml 上下文参数)

3.JNDI 环境变量()

4.JVM系统参数(-D 命令行参数)

5.JVM系统变量(操作系统环境变量)

最重要地,整个机制是可配置的。也许你有一个自定义的属性源,你想整合到此搜索中。这样做,实现和实例化你的并且添加到当前的 集合中。下面例子展示怎样去做:

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

在前面的代码中,被添加到最高索引优先级中。如果它包含属性,则会检测到并返回该属性,从而支持任何其他中的属性。API暴露了一些方法,这些方法允许去精确操作属性源集合。

1.13.3 使用

注解提供一个便捷的和陈述式的机制去添加到Spring的中。

给定一个名叫文件,它包含健值对,下面的类使用,以这种方式调用并返回:

@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;
    }
}

假设在一个已经被注册的属性源中被描述(例如,系统属性或环境变量),则占位符被解析为对应值。如果没有,则使用一个默认值。如果没有指定默认值并且属性不能被解析,一个被抛出。

根据Java8约定,注解是可以重复的。然而,所有的注解需要在相同等级被声明,要么直接地在配置类上,要么作为元数据注解在相同自定义注解中。不建议将直接注释和元注释混合使用,因为直接注释会有效地覆盖元注释。

参考代码:

1.13.4 语句中的占位符解析

在以前,元素中占位符的值只能根据JVM系统属性或环境变量来解析。现在情况已经不同了。因为抽象已经集成到容器,很容易通过它来路由占位符的解析。这意味着你可以按照自己喜欢的任何方式配置解析过程。你可以更改搜索系统属性和环境变量的优先级,也可以完全删除它们。你还可以适当地将你自己的属性源添加到组合中。

具体地说,不论在何处定义customer属性,只要在环境中可用,以下语句就可以工作:


    

作者

个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。

博客地址: 

CSDN: 

微信公众号: