博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
IoC容器13——环境抽象
阅读量:6481 次
发布时间:2019-06-23

本文共 8360 字,大约阅读时间需要 27 分钟。

hot3.png

Environment是集成在容器中的抽象,它为应用程序的两个关键方面:profiles和properties提供模型。

只有在给定的profiles处于活动状态时,profiles才是要向容器注册的一个命名逻辑组的bean定义。bean可以被分配到XML或注解形式的profiles中。与profiles相关的Environment对象的角色是确定哪些profiles(如果有的话)当前处于活动状态,哪些profiles如果有的话——默认情况下应处于活动状态。

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

1 Bean定义profiles

Bean定义profiles时核心容器中的一个机制,它允许在不同环境中的注册不同bean。环境这个词对于不同的用户意味着不同的东西并且这个特性可以在许多用例中起作用,包括:

  • 处理开发中在内存中的数据源 vs 在QA或生产环境中从JNDI查找相同的数据源;
  • 仅在将应用程序部署到性能环境时注册进空基础架构;
  • 注册用户A vs 用户B部署的bean的自定义实现。

考虑第一种用例,在一个实际应用中需要一个DataSource。在测试环境中,配置像下面这样:

@Beanpublic 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的使用者设计了一系列的方法来实现,通常依赖与组合系统环境变量和包含${placeholder}标记的XML<import/>声明,这个标记用于介些依赖于环境变量值的正确配置文件路径。Bean定义profiles是提供这个问题的一个解决方法的核心容器特性。

如果将环境特定的bean定义概括为上面的例子,我们最终需要在某些上下文中注册某些bean,而不在其它情况下。可以说要在情景A中注册一个特定的bean定义profile,并且在情景B中注册一个不同的profile。首先看看如何更新配置以满足这种需求。

@Profile注解指示一个组件在一个或多个特定的profile处于活动状态时有注册的资格。使用上面的例子,可以从西安数据源的配置如下:

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

使用@Bean方法,一般要选择编程式的JNDI查找:使用Spring的JndiTemplate/JndiLocatorDelegate助手或者直接使用JNDI的InitailContext,如上例,但是不能使用JndiObjectFactoryBean的变体,因为它强制要求将返回类型声明为FactroyBean类型。

@Profile可以被用于创建自定义组合注解的元注解。下面的例子声明一个自定义的@Production注解,用于以插入式的方式替代@Profile("production"):

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

如果一个@Configuration类被标记为@Profile,所有的@Bean方法和@Import注解关联的类将会被忽略,除非一个或多个指定的profiles处于活动状态。如果一个@Component或@Configuration类被@Profile({"p1","p2"})标记,这个类不会被注册/处理除非profile p1 和/或 p2 被激活。如果一个给定的profile有非操作符!为前缀,那注解的元素在profile没有激活时被注册。例如,给定的@Profile({"p1", "!p2"})的注册将会发生在profile pi 被激活或者profile p2 不被激活。

@Profile也可以在方法级别声明,仅包含配置类的一个特定bean,例如对于特定bean替代的另一种方法:

@Configurationpublic class AppConfig {    @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();    }    @Bean("dataSource")    @Profile("production")    public DataSource jndiDataSource() throws Exception {        Context ctx = new InitialContext();        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");    }}

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

如果想使用不同的profile条件定义bean的选择,使用@Bean name属性指向同一个bean名称,而Java方法名称不同,如上面的例子所展示的。如果参数签名都一样(例如所有的工厂方法都没有参数),这是在有效的Java类中首先表示这种安排的唯一方法(因为只能有一个方法拥有指定的方法名和参数签名)。

XML bean定义profile

XML对应的是<beans/>元素的profile属性。上面的例子可以用XML重写为如下形式:

也可以避免拆分,将<beans/>嵌套在一个文件中:

spring-bean.xsd被限制为只允许这样的元素在文件的最后。这有助于提供灵活情,又不会在XML文件中产生混乱。

激活一个profile

既然已经更新了配置,仍然需要指示Spring哪个profile要被激活。如果现在开始示例应用,会抛出NoSuchBeanDefinitionException,因为容器不能找到名为dataSource的bean。

激活一个profile可以有几种方式,但是最直接的方式是以编程的方式使用Environment API,Environment可以通过ApplicationContext获取:

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

此外,profile可以以声明的方式通过spring.profiles.active属性被激活,这个属性可以由系统环境变量、JVN系统属性、web.xml中的servlet上下文参数甚至是JNDI中的一个键值对定义。在集成测试中,激活profile可以通过spring-test模块中的@ActiveProfiles注解实现。

注意profile不是一个“任意-或”的命题,可以一次激活多个profile。编程的方式,提供多个profile名字给setActiveProfiles()函数就可以了,这个函数接受可变数量的String类型:

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

声明的方式,spring.profiles.active可以接受都好分割的profile名字列表:

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

默认profile

默认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被激活,上面的dataSource会被创建;这被视作为一个或多个bean提供默认定义的一个方法。如果有任何一个profile被激活,默认的profile将不会被应用。

默认profile的名字可以使用Environment的setDefaultProfiles()方法或直接使用spring.profiles.default属性改变。

2 PropertySource 抽象

Spring的Environment抽象提供了对可配置属性元结构的搜索操作。为了完整的解释,请考虑下面的例子:

ApplicationContext ctx = new GenericApplicationContext();Environment env = ctx.getEnvironment();boolean containsFoo = env.containsProperty("foo");System.out.println("Does my environment contain the 'foo' property? " + containsFoo);

在上面的代码片段中,可以看到一个高级的方法来询问Spring foo属性是否在当前环境中定义。为了回答这个问题,Environment对象对一组PropertySource对象进行搜索。PropertySource是对任何来源的键值对的简单抽象,同时Spring的StandardEnvironment被两个PropertySource对象配置,一个代表JVM系统属性的集合(System.getProperties()),另一个代表系统环境变量的集合(System.getenv())。

这些默认的属性源存在于StandardEnvironment,用于独立应用程序。StandardServletEnvironment还包含其它默认的属性源,包括servlet配置和servlet上下文参数。StandardPortletEnvironment同样包含portlet配置和portlet上下文参数作为配置源。这两者都可以选择启用JndiPropertySource。细节可参考javadoc。

具体的,当使用StandardEnvironment,调用env.containsProperty("foo")会返回true如果一个foo系统属性或foo环境变量在运行时存在。

搜索的行为是有层次的。默认的,系统属性优先于环境变量,所以如果在调用env.getProperty("foo")时恰好在两个地方设置了foo属性,系统属性值将会“胜出”并且优先于环境变量被返回。请注意属性值只会被优先的覆盖而不会被合并。

对于普通的StandardServletEnvironment,完整的层次结构如下,最上面的有最高的优先权:

  • ServletConfig 参数(如果适用,例如在DispatcherServlet上下文的情况下);
  • ServletContext 参数(web.xml的context-param键值对);
  • JNDI环境变量("java:comp/env/"键值对);
  • JVM 系统属性("-D" 命令行参数);
  • JVM 系统环境(操作系统环境变量)。

最重要的是,整个机制是可配置的。也许有一个自定义的配置源需要集成到搜索机制中。没问题——只要实现并实例化自定义的PropertySource并且将其添加到当前Environment的PropertySources集合中:

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

在上面的代码中,MyPropertySource被添加为最高优先级。如果它包含一个foo属性,它会被发现并先于其它PropertySource中foo属性被返回。MutablePropertySources API暴露了一系列方法,允许精确操作属性源的集合。

3 @PropertySource

@PropertySource注解为添加一个PropertySource到Spring的Environment提供了方便的声明机制。

给定一个app.properties文件包含了键值对testbean.name=myTestBean,下面的@()将会返回"myTestBean"。

@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"存在于已经注册的配置源集合中的一个,例如系统配置或环境变量,占位符将会被解析为相应的值。如果没有,那么“默认/路径”将会被使用。如果没有声明默认值并且属性不能被解析,将抛出IllegalArgumentException。

4 声明中的占位符解析

从前XML元素中的占位符的值只能被JVM系统属性或环境变量解析。现在已不是这种情况。因为环境抽象被集成到容器中,通过它可以轻松的路由占位符解析。这意味着可以任意的配置解析处理:改变系统属性和环境变量的搜索优先级,或者直接移除它们;添加自定义的属性源。

具体的,下面的声明不关注customer属性在哪里定义,只要在Environment中即可:

转载于:https://my.oschina.net/u/2453016/blog/1377446

你可能感兴趣的文章
软件设计之UML—UML的构成[上]
查看>>
[SPLEB]CodeSmith原理剖析(1)
查看>>
如何使用AdMob中介界面?
查看>>
分享一个shell脚本:通过Jumper机器来创建Jumper和target机器账号
查看>>
UITableViewCell分割线不是左对齐的问题
查看>>
CentOS7 编译安装PHP7
查看>>
MySQL常见错误代码及代码说明
查看>>
Cglib动态代理基础使用
查看>>
技术人员,为什么会苦逼
查看>>
使用126邮箱发送邮件的python脚本
查看>>
Maven
查看>>
课后习题和问题 Chapter 2 Problems 10-18
查看>>
缓存系统在游戏业务中的特异性
查看>>
Ngrok搭建自己的内网穿透
查看>>
redis的基本数据类型
查看>>
.NET 同步与异步之锁(Lock、Monitor)(七)
查看>>
前端大牛们都学过哪些?
查看>>
利用simulink分析系统各种传递函数的BODE图、阶跃响应、单位脉冲响应
查看>>
在iOS当中发送电子邮件和短信
查看>>
python的单例模式
查看>>