zoukankan      html  css  js  c++  java
  • Spring3.1新属性管理API:PropertySource、Environment、Profile

    新的属性管理API

    PropertySource:属性源,key-value属性对抽象,比如用于配置数据

    PropertyResolver:属性解析器,用于解析相应key的value

    Environment:环境,本身是一个PropertyResolver,但是提供了Profile特性,即可以根据环境得到相应数据(即激活不同的Profile,可以得到不同的属性数据,比如用于多环境场景的配置(正式机、测试机、开发机DataSource配置))

    Profile:剖面,只有激活的剖面的组件/配置才会注册到Spring容器,类似于maven中profile

    也就是说,新的API主要从配置属性、解析属性、不同环境解析不同的属性、激活哪些组件/配置进行注册这几个方面进行了重新设计,使得API的目的更加清晰,而且功能更加强大。

    PropertySource

    key-value对,API如下所示:

    1 public String getName()  //属性源的名字
    2 public T getSource()        //属性源(比如来自Map,那就是一个Map对象)
    3 public boolean containsProperty(String name)  //是否包含某个属性
    4 public abstract Object getProperty(String name)   //得到属性名对应的属性值

    非常类似于Map;用例如下:

     1     @Test
     2     public void test() throws IOException {
     3         Map<String, Object> map = new HashMap<>();
     4         map.put("encoding", "gbk");
     5         PropertySource propertySource1 = new MapPropertySource("map", map);
     6         System.out.println(propertySource1.getProperty("encoding"));
     7 
     8         ResourcePropertySource propertySource2 = new ResourcePropertySource("resource", "classpath:resources.properties"); //name, location
     9         System.out.println(propertySource2.getProperty("encoding"));
    10     }

    MapPropertySource的属性来自于一个Map,而ResourcePropertySource的属性来自于一个properties文件,另外还有如PropertiesPropertySource,其属性来自Properties,ServletContextPropertySource的属性来自ServletContext上下文初始化参数等等,大家可以查找PropertySource的继承层次查找相应实现。

    1     @Test
    2     public void test2() throws IOException {
    3         //省略propertySource1/propertySource2
    4         CompositePropertySource compositePropertySource = new CompositePropertySource("composite");
    5         compositePropertySource.addPropertySource(propertySource1);
    6         compositePropertySource.addPropertySource(propertySource2);
    7         System.out.println(compositePropertySource.getProperty("encoding"));
    8     }

    CompositePropertySource提供了组合PropertySource的功能,查找顺序就是注册顺序。 

    另外还有一个PropertySources,从名字可以看出其包含多个PropertySource:

    1 public interface PropertySources extends Iterable<PropertySource<?>> {
    2     boolean contains(String name); //是否包含某个name的PropertySource
    3     PropertySource<?> get(String name); //根据name找到PropertySource
    4 }

    示例如下:

     1     @Test
     2     public void test3() throws IOException {
     3         //省略propertySource1/propertySource2
     4         MutablePropertySources propertySources = new MutablePropertySources();
     5         propertySources.addFirst(propertySource1);
     6         propertySources.addLast(propertySource2);
     7         System.out.println(propertySources.get("resource").getProperty("encoding"));
     8 
     9         for(PropertySource propertySource : propertySources) {
    10             System.out.println(propertySource.getProperty("encoding"));
    11         }
    12     }

    默认提供了一个MutablePropertySources实现,我们可以调用addFirst添加到列表的开头,addLast添加到末尾,另外可以通过addBefore(propertySourceName, propertySource)或addAfter(propertySourceName, propertySource)添加到某个propertySource前面/后面;最后大家可以通过iterator迭代它,然后按照顺序获取属性。

    到目前我们已经有属性了,接下来需要更好的API来解析属性了。

    PropertyResolver

    属性解析器,用来根据名字解析其值等。API如下所示:

     1 public interface PropertyResolver {
     2 
     3     //是否包含某个属性
     4     boolean containsProperty(String key);
     5  
     6         //获取属性值 如果找不到返回null 
     7     String getProperty(String key);
     8      
     9         //获取属性值,如果找不到返回默认值       
    10     String getProperty(String key, String defaultValue);
    11   
    12         //获取指定类型的属性值,找不到返回null
    13     <T> T getProperty(String key, Class<T> targetType);
    14 
    15         //获取指定类型的属性值,找不到返回默认值
    16     <T> T getProperty(String key, Class<T> targetType, T defaultValue);
    17 
    18          //获取属性值为某个Class类型,找不到返回null,如果类型不兼容将抛出ConversionException
    19     <T> Class<T> getPropertyAsClass(String key, Class<T> targetType);
    20 
    21         //获取属性值,找不到抛出异常IllegalStateException
    22     String getRequiredProperty(String key) throws IllegalStateException;
    23 
    24         //获取指定类型的属性值,找不到抛出异常IllegalStateException       
    25     <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
    26 
    27         //替换文本中的占位符(${key})到属性值,找不到不解析
    28     String resolvePlaceholders(String text);
    29 
    30         //替换文本中的占位符(${key})到属性值,找不到抛出异常IllegalArgumentException
    31     String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
    32 
    33 }

    从API上我们已经看出解析器的作用了,具体功能就不要罗嗦了。示例如下:

     1     @Test
     2     public void test() throws Exception {
     3         //省略propertySources
     4 
     5         PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);
     6 
     7         System.out.println(propertyResolver.getProperty("encoding"));
     8         System.out.println(propertyResolver.getProperty("no", "default"));
     9         System.out.println(propertyResolver.resolvePlaceholders("must be encoding ${encoding}"));  //输出must be encoding gbk
    10     }

    从如上示例可以看出其非常简单。另外Environment也继承了PropertyResolver。

    Environment

     环境,比如JDK环境,Servlet环境,Spring环境等等;每个环境都有自己的配置数据,如System.getProperties()、System.getenv()等可以拿到JDK环境数据;ServletContext.getInitParameter()可以拿到Servlet环境配置数据等等;也就是说Spring抽象了一个Environment来表示环境配置。

     1 public interface Environment extends PropertyResolver {//继承PropertyResolver
     2 
     3         //得到当前明确激活的剖面
     4     String[] getActiveProfiles();
     5 
     6         //得到默认激活的剖面,而不是明确设置激活的
     7     String[] getDefaultProfiles();
     8  
     9         //是否接受某些剖面
    10     boolean acceptsProfiles(String... profiles);
    11 
    12 }

    从API上可以看出,除了可以解析相应的属性信息外,还提供了剖面相关的API,目的是: 可以根据剖面有选择的进行注册组件/配置。比如对于不同的环境注册不同的组件/配置(正式机、测试机、开发机等的数据源配置)。它的主要几个实现如下所示:

    MockEnvironment:模拟的环境,用于测试时使用;

    StandardEnvironment:标准环境,普通Java应用时使用,会自动注册System.getProperties() 和 System.getenv()到环境;

    StandardServletEnvironment:标准Servlet环境,其继承了StandardEnvironment,Web应用时使用,除了StandardEnvironment外,会自动注册ServletConfig(DispatcherServlet)、ServletContext及JNDI实例到环境;

    除了这些,我们也可以根据需求定义自己的Environment。示例如下:

    1     @Test
    2     public void test() {
    3         //会自动注册 System.getProperties() 和 System.getenv()
    4         Environment environment = new StandardEnvironment();
    5         System.out.println(environment.getProperty("file.encoding"));
    6     }

    其默认有两个属性:systemProperties(System.getProperties())和systemEnvironment(System.getenv())。

    在程序中通过如下代码注入Environment: 

    1 @Autowired
    2 Environment env;

    另外也可以直接使用ApplicationContext.getEnvironment()获取;接着就可以用如下代码获取配置:

    1 System.out.println(env.getProperty("myConfig"));  
    2 System.out.println(env.getProperty("contextConfigLocation"));  

    另外我们在运行应用时可以通过-D传入系统参数(System.getProperty()),如java -Ddata=123  com.sishuok.spring3.EnvironmentTest,那么我们可以通过environment.getProperty("data") 获取到。

    如果我们拿到的上下文是ConfigurableApplicationContext类型,那么可以:ctx.getEnvironment().getPropertySources() ;然后通过PropertySources再添加自定义的PropertySource。

    Profile

    profile,剖面,大体意思是:我们程序可能从某几个剖面来执行应用,比如正式机环境、测试机环境、开发机环境等,每个剖面的配置可能不一样(比如开发机可能使用本地的数据库测试,正式机使用正式机的数据库测试)等;因此呢,就需要根据不同的环境选择不同的配置;如果用过maven,maven中就有profile的概念。

    profile有两种:

    默认的:通过“spring.profiles.default”属性获取,如果没有配置默认值是“default”

    明确激活的:通过“spring.profiles.active”获取

    查找顺序是:先进性明确激活的匹配,如果没有指定明确激活的(即集合为空)就找默认的;配置属性值从Environment读取。

    API请参考Environment部分。设置profile属性,常见的有三种方式:

    一、启动Java应用时,通过-D传入系统参数

    -Dspring.profiles.active=dev

    二、如果是web环境,可以通过上下文初始化参数设置

    spring.profiles.active=dev

    三 、通过自定义添加PropertySource

    1 Map<String, Object> map = new HashMap<String, Object>();
    2 map.put("spring.profiles.active", "dev");
    3 MapPropertySource propertySource = new MapPropertySource("map", map);
    4 env.getPropertySources().addFirst(propertySource);

    四、直接设置Profile

    1 env.setActiveProfiles("dev", "test");

    以上方式都可以设置多个profile,多个之间通过如逗号/分号等分隔。  

    接着我们就可以通过如下API判断是否激活相应的Profile了:

    1 if(env.acceptsProfiles("dev", "test"))) {
    2     //do something
    3 }

    它们之间是或的关系;即找到一个即可;如果有人想不匹配某个profile执行某些事情,可以通过如"!dev"  即没有dev激活时返回true。

    <context:property-placeholder/>

    ${key}占位符属性替换器,配置如下:

     1     <context:property-placeholder 
     2             location="属性文件,多个之间逗号分隔"
     3             file-encoding="文件编码"
     4             ignore-resource-not-found="是否忽略找不到的属性文件"
     5             ignore-unresolvable="是否忽略解析不到的属性,如果不忽略,找不到将抛出异常"
     6             properties-ref="本地Properties配置"
     7             local-override="是否本地覆盖模式,即如果true,那么properties-ref的属性将覆盖location加载的属性,否则相反"
     8             system-properties-mode="系统属性模式,默认ENVIRONMENT(表示先找ENVIRONMENT,再找properties-ref/location的),NEVER:表示永远不用ENVIRONMENT的,OVERRIDE类似于ENVIRONMENT"
     9             order="顺序"
    10             />

    location:表示属性文件位置,多个之间通过如逗号/分号等分隔;

    file-encoding:文件编码;

    ignore-resource-not-found:如果属性文件找不到,是否忽略,默认false,即不忽略,找不到将抛出异常

    ignore-unresolvable:是否忽略解析不到的属性,如果不忽略,找不到将抛出异常

    properties-ref:本地java.util.Properties配置

    local-override:是否本地覆盖模式,即如果true,那么properties-ref的属性将覆盖location加载的属性

    system-properties-mode:系统属性模式,ENVIRONMENT(默认),NEVER,OVERRIDE

       ENVIRONMENT:将使用Spring 3.1提供的PropertySourcesPlaceholderConfigurer,其他情况使用Spring 3.1之前的PropertyPlaceholderConfigurer

           如果是本地覆盖模式:那么查找顺序是:properties-ref、location、environment,否则正好反过来;

       OVERRIDE: PropertyPlaceholderConfigurer使用,因为在spring 3.1之前版本是没有Enviroment的,所以OVERRIDE是spring 3.1之前版本的Environment

           如果是本地覆盖模式:那么查找顺序是:properties-ref、location、System.getProperty(),System.getenv(),否则正好反过来; 

       NEVER:只查找properties-ref、location;

    order:当配置多个<context:property-placeholder/>时的查找顺序,关于顺序问题请参考:http://www.iteye.com/topic/1131688 

    具体使用请参考如下文件中的如dataSource:

    https://github.com/zhangkaitao/es/blob/master/web/src/main/resources/spring-config.xml

    @PropertySource()

    Spring 3.1提供的Java Config方式的注解,其属性会自动注册到相应的Environment;如:

    1 @Configuration
    2 @PropertySource(value = "classpath:resources.properties", ignoreResourceNotFound = false)
    3 public class AppConfig {
    4 }

    接着就可以使用env.getProperty("encoding")得到相应的属性值。 

    另外如果想进行Bean属性的占位符替换,需要注册PropertySourcesPlaceholderConfigurer:

    1 @Bean
    2 public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
    3     return new PropertySourcesPlaceholderConfigurer();
    4 }

    如上配置等价于XML中的<context:property-placeholder/>配置。

    如果想导入多个,在Java8之前需要使用@PropertySources注册多个@PropertySource()。

    此处要注意:

    使用<context:property-placeholder/>不会自动把属性注册到Environment中,而@PropertySource()会;且在XML配置中并没有@PropertySource()等价的XML命名空间配置,如果需要,可以自己写一个。

    占位符替换

    使用Environment属性替换,如:

    <context:property-placeholder location="classpath:${env}/resources.properties"/>  

    <context:component-scan base-package="com.sishuok.${package}"/> 

    <import resource="classpath:${env}/ctx.xml"/>

    @PropertySource(value = "classpath:${env}/resources.properties")

    @ComponentScan(basePackages = "com.sishuok.${package}")

    @ImportResource(value = {"classpath:${env}/cfg.xml"})

    @Value("${env}")   

    new ClassPathXmlApplicationContext("classpath:${env}/cfg.xml")

    使用PropertySourcesPlaceholderConfigurer / PropertyPlaceholderConfigurer进性Bean属性替换,如:

    1 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    2         <!-- 基本属性 url、user、password -->
    3         <property name="url" value="${connection.url}"/>
    4         <property name="username" value="${connection.username}"/>
    5         <property name="password" value="${connection.password}"/>
    6 </bean>

    SpEL表达式:

    请参考【第五章】Spring表达式语言 之 5.4在Bean定义中使用EL—跟我学spring3

    通过如上方式可以实现不同的环境有不同的属性配置,但是如果我们想不同的环境加载不同的Bean呢,比如测试机/正式机环境可能使用远程方式访问某些API,而开发机环境使用本地方式进行开发,提高开发速度,这就需要profile了。

    <beans  profile="">

    通过在beans标签上加上profile属性,这样当我们激活相应的profile时,此beans标签下的bean就会注册,如下所示:

     1 <beans xmlns="http://www.springframework.org/schema/beans"
     2        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     3        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
     4 
     5 <beans profile="dev">
     6     <bean id="dataSource" class="本地DataSource">
     7     </bean>            
     8 </beans>
     9 
    10 <beans profile="test">
    11     <bean id="dataSource" class="测试环境DataSource">
    12     </bean>
    13 </beans>
    14 
    15 </beans>

    启动应用时设置相应的“spring.profiles.active”即可。另外,如果想指定一个默认的,可以使用<beans profile="default">指定(如果不是default,可以通过“spring.profiles.default”指定)。

    @Profile()

    Java Config方式的Profile,功能等价于XML中的<beans profiles>,使用方式如下:

     1 @Profile("dev")
     2 @Configuration
     3 @PropertySource(value = "classpath:resources.properties", ignoreResourceNotFound = false)
     4 public class AppConfig {
     5 
     6     @Bean
     7     public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
     8         return new PropertySourcesPlaceholderConfigurer();
     9     }
    10 }

    Spring4提供了一个新的@Conditional注解,请参考

    http://jinnianshilongnian.iteye.com/blog/1989379

    @ActiveProfiles()

    在测试时,有时候不能通过系统启动参数/上下文参数等指定Profile,此时Spring测试框架提供了@ActiveProfiles()注解,示例如下:

    1 @ActiveProfiles("test")
    2 @RunWith(SpringJUnit4ClassRunner.class)
    3 @ContextConfiguration(classes = GenericConfig.class)
    4 public class GenricInjectTest {
    5 ……
    6 }

    通过这种方式,我们就激活了test profile。 

    到此整个Spring的属性管理API就介绍完了,对于属性管理,核心是Environment,所以以后请使用Environment来进行属性管理吧。

  • 相关阅读:
    设计模式之Builder (创建者模式)的一些个人理解(转)
    SystemClock.sleep和Thread.sleep的区别(转)
    Android应用开发基础之四:网络编程(一)
    handler机制的原理(转)
    NullPointerException at android.widget.AbsListView.obtainView at android.widget.ListView.makeAndAddView
    Android之Adapter用法总结(转)
    [Done]ibatis/mybatis: java.lang.NoSuchMethodException
    [Done]java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
    [Done]FindBugs: boxing/unboxing to parse a primitive
    Python中的基本运算符
  • 原文地址:https://www.cnblogs.com/lgjava/p/11263998.html
Copyright © 2011-2022 走看看