spring--DI--3
Spring3.1提供了新的属性管理API,而且功能非常强大且很完善,对于一些属性配置信息都应该使用新的API来管理。虽然现在Spring已经到4版本了,这篇文章来的晚点。
新的属性管理API
PropertySource:属性源,key-value属性对抽象,比如用于配置数据
PropertyResolver:属性解析器,用于解析相应key的value
Environment:环境,本身是一个PropertyResolver,但是提供了Profile特性,即可以根据环境得到相应数据(即激活不同的Profile,可以得到不同的属性数据,比如用于多环境场景的配置(正式机、测试机、开发机DataSource配置))
Profile:剖面,只有激活的剖面的组件/配置才会注册到Spring容器,类似于maven中profile
也就是说,新的API主要从配置属性、解析属性、不同环境解析不同的属性、激活哪些组件/配置进行注册这几个方面进行了重新设计,使得API的目的更加清晰,而且功能更加强大。
PropertySource
key-value对,API如下所示:
- public String getName() //属性源的名字
- public T getSource() //属性源(比如来自Map,那就是一个Map对象)
- public boolean containsProperty(String name) //是否包含某个属性
- public abstract Object getProperty(String name) //得到属性名对应的属性值
非常类似于Map;用例如下:
- @Test
- public void test() throws IOException {
- Map<String, Object> map = new HashMap<>();
- map.put("encoding", "gbk");
- PropertySource propertySource1 = new MapPropertySource("map", map);
- System.out.println(propertySource1.getProperty("encoding"));
- ResourcePropertySource propertySource2 = new ResourcePropertySource("resource", "classpath:resources.properties"); //name, location
- System.out.println(propertySource2.getProperty("encoding"));
- }
MapPropertySource的属性来自于一个Map,而ResourcePropertySource的属性来自于一个properties文件,另外还有如PropertiesPropertySource,其属性来自Properties,ServletContextPropertySource的属性来自ServletContext上下文初始化参数等等,大家可以查找PropertySource的继承层次查找相应实现。
- @Test
- public void test2() throws IOException {
- //省略propertySource1/propertySource2
- CompositePropertySource compositePropertySource = new CompositePropertySource("composite");
- compositePropertySource.addPropertySource(propertySource1);
- compositePropertySource.addPropertySource(propertySource2);
- System.out.println(compositePropertySource.getProperty("encoding"));
- }
CompositePropertySource提供了组合PropertySource的功能,查找顺序就是注册顺序。
另外还有一个PropertySources,从名字可以看出其包含多个PropertySource:
- public interface PropertySources extends Iterable<PropertySource<?>> {
- boolean contains(String name); //是否包含某个name的PropertySource
- PropertySource<?> get(String name); //根据name找到PropertySource
- }
示例如下:
- @Test
- public void test3() throws IOException {
- //省略propertySource1/propertySource2
- MutablePropertySources propertySources = new MutablePropertySources();
- propertySources.addFirst(propertySource1);
- propertySources.addLast(propertySource2);
- System.out.println(propertySources.get("resource").getProperty("encoding"));
- for(PropertySource propertySource : propertySources) {
- System.out.println(propertySource.getProperty("encoding"));
- }
- }
默认提供了一个MutablePropertySources实现,我们可以调用addFirst添加到列表的开头,addLast添加到末尾,另外可以通过addBefore(propertySourceName, propertySource)或addAfter(propertySourceName, propertySource)添加到某个propertySource前面/后面;最后大家可以通过iterator迭代它,然后按照顺序获取属性。
到目前我们已经有属性了,接下来需要更好的API来解析属性了。
PropertyResolver
属性解析器,用来根据名字解析其值等。API如下所示:
- public interface PropertyResolver {
- //是否包含某个属性
- boolean containsProperty(String key);
- //获取属性值 如果找不到返回null
- String getProperty(String key);
- //获取属性值,如果找不到返回默认值
- String getProperty(String key, String defaultValue);
- //获取指定类型的属性值,找不到返回null
- <T> T getProperty(String key, Class<T> targetType);
- //获取指定类型的属性值,找不到返回默认值
- <T> T getProperty(String key, Class<T> targetType, T defaultValue);
- //获取属性值为某个Class类型,找不到返回null,如果类型不兼容将抛出ConversionException
- <T> Class<T> getPropertyAsClass(String key, Class<T> targetType);
- //获取属性值,找不到抛出异常IllegalStateException
- String getRequiredProperty(String key) throws IllegalStateException;
- //获取指定类型的属性值,找不到抛出异常IllegalStateException
- <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
- //替换文本中的占位符(${key})到属性值,找不到不解析
- String resolvePlaceholders(String text);
- //替换文本中的占位符(${key})到属性值,找不到抛出异常IllegalArgumentException
- String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
- }
从API上我们已经看出解析器的作用了,具体功能就不要罗嗦了。示例如下:
- @Test
- public void test() throws Exception {
- //省略propertySources
- PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);
- System.out.println(propertyResolver.getProperty("encoding"));
- System.out.println(propertyResolver.getProperty("no", "default"));
- System.out.println(propertyResolver.resolvePlaceholders("must be encoding ${encoding}")); //输出must be encoding gbk
- }
从如上示例可以看出其非常简单。另外Environment也继承了PropertyResolver。
Environment
环境,比如JDK环境,Servlet环境,Spring环境等等;每个环境都有自己的配置数据,如System.getProperties()、System.getenv()等可以拿到JDK环境数据;ServletContext.getInitParameter()可以拿到Servlet环境配置数据等等;也就是说Spring抽象了一个Environment来表示环境配置。
- public interface Environment extends PropertyResolver {//继承PropertyResolver
- //得到当前明确激活的剖面
- String[] getActiveProfiles();
- //得到默认激活的剖面,而不是明确设置激活的
- String[] getDefaultProfiles();
- //是否接受某些剖面
- boolean acceptsProfiles(String... profiles);
- }
从API上可以看出,除了可以解析相应的属性信息外,还提供了剖面相关的API,目的是: 可以根据剖面有选择的进行注册组件/配置。比如对于不同的环境注册不同的组件/配置(正式机、测试机、开发机等的数据源配置)。它的主要几个实现如下所示:
MockEnvironment:模拟的环境,用于测试时使用;
StandardEnvironment:标准环境,普通Java应用时使用,会自动注册System.getProperties() 和 System.getenv()到环境;
StandardServletEnvironment:标准Servlet环境,其继承了StandardEnvironment,Web应用时使用,除了StandardEnvironment外,会自动注册ServletConfig(DispatcherServlet)、ServletContext及JNDI实例到环境;
除了这些,我们也可以根据需求定义自己的Environment。示例如下:
- @Test
- public void test() {
- //会自动注册 System.getProperties() 和 System.getenv()
- Environment environment = new StandardEnvironment();
- System.out.println(environment.getProperty("file.encoding"));
- }
其默认有两个属性:systemProperties(System.getProperties())和systemEnvironment(System.getenv())。
在web环境中首先在web.xml中配置:
- <context-param>
- <param-name>myConfig</param-name>
- <param-value>hello</param-value>
- </context-param>
- <servlet>
- <servlet-name>spring</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath:spring-mvc.xml</param-value>
- </init-param>
- </servlet>
使用StandardServletEnvironment加载时,默认除了StandardEnvironment的两个属性外,还有另外三个属性:servletContextInitParams(ServletContext)、servletConfigInitParams(ServletConfig)、jndiProperties(JNDI)。
然后在程序中通过如下代码注入Environment:
- @Autowired
- Environment env;
另外也可以直接使用ApplicationContext.getEnvironment()获取;接着就可以用如下代码获取配置:
- System.out.println(env.getProperty("myConfig"));
- 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环境,可以通过上下文初始化参数设置
- <context-param>
- <param-name>spring.profiles.active</param-name>
- <param-value>dev</param-value>
- </context-param>
三 、通过自定义添加PropertySource
- Map<String, Object> map = new HashMap<String, Object>();
- map.put("spring.profiles.active", "dev");
- MapPropertySource propertySource = new MapPropertySource("map", map);
- env.getPropertySources().addFirst(propertySource);
四、直接设置Profile
- env.setActiveProfiles("dev", "test");
以上方式都可以设置多个profile,多个之间通过如逗号/分号等分隔。
接着我们就可以通过如下API判断是否激活相应的Profile了:
- if(env.acceptsProfiles("dev", "test"))) {
- //do something
- }
它们之间是或的关系;即找到一个即可;如果有人想不匹配某个profile执行某些事情,可以通过如"!dev" 即没有dev激活时返回true。
当然这种方式还不是太友好,还需要我们手工编程使用,稍候会介绍如何更好的使用它们。
<context:property-placeholder/>
${key}占位符属性替换器,配置如下:
- <context:property-placeholder
- location="属性文件,多个之间逗号分隔"
- file-encoding="文件编码"
- ignore-resource-not-found="是否忽略找不到的属性文件"
- ignore-unresolvable="是否忽略解析不到的属性,如果不忽略,找不到将抛出异常"
- properties-ref="本地Properties配置"
- local-override="是否本地覆盖模式,即如果true,那么properties-ref的属性将覆盖location加载的属性,否则相反"
- system-properties-mode="系统属性模式,默认ENVIRONMENT(表示先找ENVIRONMENT,再找properties-ref/location的),NEVER:表示永远不用ENVIRONMENT的,OVERRIDE类似于ENVIRONMENT"
- order="顺序"
- />
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;如:
- @Configuration
- @PropertySource(value = "classpath:resources.properties", ignoreResourceNotFound = false)
- public class AppConfig {
- }
接着就可以使用env.getProperty("encoding")得到相应的属性值。
另外如果想进行Bean属性的占位符替换,需要注册PropertySourcesPlaceholderConfigurer:
- @Bean
- public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
- return new PropertySourcesPlaceholderConfigurer();
- }
如上配置等价于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属性替换,如:
- <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
- <!-- 基本属性 url、user、password -->
- <property name="url" value="${connection.url}"/>
- <property name="username" value="${connection.username}"/>
- <property name="password" value="${connection.password}"/>
- </bean>
SpEL表达式:
请参考【第五章】Spring表达式语言 之 5.4在Bean定义中使用EL—跟我学spring3
通过如上方式可以实现不同的环境有不同的属性配置,但是如果我们想不同的环境加载不同的Bean呢,比如测试机/正式机环境可能使用远程方式访问某些API,而开发机环境使用本地方式进行开发,提高开发速度,这就需要profile了。
<beans profile="">
通过在beans标签上加上profile属性,这样当我们激活相应的profile时,此beans标签下的bean就会注册,如下所示:
- <beans 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">
- <beans profile="dev">
- <bean id="dataSource" class="本地DataSource">
- </bean>
- </beans>
- <beans profile="test">
- <bean id="dataSource" class="测试环境DataSource">
- </bean>
- </beans>
- </beans>
启动应用时设置相应的“spring.profiles.active”即可。另外,如果想指定一个默认的,可以使用<beans profile="default">指定(如果不是default,可以通过“spring.profiles.default”指定)。
@Profile()
Java Config方式的Profile,功能等价于XML中的<beans profiles>,使用方式如下:
- @Profile("dev")
- @Configuration
- @PropertySource(value = "classpath:resources.properties", ignoreResourceNotFound = false)
- public class AppConfig {
- @Bean
- public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
- return new PropertySourcesPlaceholderConfigurer();
- }
- }
Spring4提供了一个新的@Conditional注解,请参考http://jinnianshilongnian.iteye.com/blog/1989379。
@ActiveProfiles()
在测试时,有时候不能通过系统启动参数/上下文参数等指定Profile,此时Spring测试框架提供了@ActiveProfiles()注解,示例如下:
- @ActiveProfiles("test")
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration(classes = GenericConfig.class)
- public class GenricInjectTest {
- ……
- }
通过这种方式,我们就激活了test profile。
到此整个Spring的属性管理API就介绍完了,对于属性管理,核心是Environment,所以以后请使用Environment来进行属性管理吧。
spring--DI--3的更多相关文章
- Spring DI
一. Spring DI 依赖注入 利用spring IOC实例化了对象,而DI将实例化的对象注入到需要对象的地方,完成初始化任务. 对象由spring创建,之后再由spring给属性赋值 spr ...
- 4.spring di
spring di,即依赖注入,从应用的浅显意义来讲就是对属性赋值 1.用setter赋值,在spring的applicationContext.xml配置文件的bean下的property标签 属性 ...
- Spring DI使用详解
Spring DI使用详解 一.介绍 DI的定义:依赖注入,为类里面的属性设值.例如,我们之前的setName方法就是在为name属性设值. IOC与DI的关系:IOC进行对象的创建,DI进行值的注入 ...
- 手写Spring DI依赖注入,嘿,你的益达!
目录 提前实例化单例Bean DI分析 DI的实现 构造参数依赖 一:定义分析 二:定义一个类BeanReference 三:BeanDefinition接口及其实现类 四:DefaultBeanFa ...
- Java Spring DI之旅
做过.NET的人很多都用过Microsoft Enterprise Library,里面有一个Dependency injection工具Unity,我们可以使用它来实现依赖注入:什么是依赖注入呢?我 ...
- Spring DI模式 小样例
今儿跟同事讨论起来spring早期的,通过大篇幅xml的配置演变到今天annotation的过程,然后随手写了个小样例,感觉还不错,贴到这里留个纪念. 样例就是用JAVA API的方式, ...
- Spring DI - 依赖注入
1.IOC(DI) - 控制反转(依赖注入) 所谓的IOC称之为控制反转,简单来说就是将对象的创建的权利及对象的生命周期的管理过程交由Spring框架来处理,从此在开发过程中不再需要关注对象的创建和生 ...
- spring Di依赖注入
依赖注入有两种方式 通过 get set 方法 Person.java package cn.itcast.spring.sh.di.set; import java.util.List; imp ...
- Spring知识点总结(三)之Spring DI
1. IOC(DI) - 控制反转(依赖注入) 所谓的IOC称之为控制反转,简单来说就是将对象的创建的权利及对象的生命周期的管理过程交由Spring框架来处理,从此在开发过程中不再需要关注对象的创建和 ...
- 吴裕雄--天生自然JAVA SPRING框架开发学习笔记:Spring DI(依赖注入)的实现方式属性注入和构造注入
依赖注入(Dependency Injection,DI)和控制反转含义相同,它们是从两个角度描述的同一个概念. 当某个 Java 实例需要另一个 Java 实例时,传统的方法是由调用者创建被调用者的 ...
随机推荐
- libipq —— iptables用户空间数据包排队库
LIBIPQ(3) NAMElibipq —— iptables用户空间数据包排队库 SYNOPSIS #include <linux/netfilter.h>#include <l ...
- checked
<!doctype html><html lang="en"> <head> <meta charset="UTF-8" ...
- 读书计划——javascript dom 编程艺术(一)
用脑图把基础体系再捋清楚一边.
- php中的修饰符
上面使用了一个修饰符U,详见关于修饰符的介绍. PHP正则表达式修饰符的理解: 在PHP正则表达式里面的修饰符可以改变正则的很多特性,使得正则表达式更加适合你的需要(注意:修饰符对于大小写是敏感的,这 ...
- linux点滴:rsync
rsync(remote sync)是一款远程同步工具,可以实现全量备份.增量备份.本地备份.删除,核心功能是远程数据备份. 工作原理 rsync核心算法 1.分块checksum算法 首先,把文件平 ...
- 浏览器打开应用指定的界面-b
iOS中提供了两种在浏览器中打开APP的方法: Smart App Banner 和schema协议,这里介绍schema 在实际开发中我们可能会在浏览器中唤醒我们自己的app,就像手机QQ唤醒快报一 ...
- js 转化类似这样的时间( /Date(1389060261000)/)问题
首先在你的js文件里添加这段代码: /** * 日期时间格式化方法, * 可以格式化年.月.日.时.分.秒.周 **/ Date.prototype.Format = function (format ...
- Discuz CDN优化方案
DZ整体来说CDN是有点蛋疼的,因为毕竟琐碎,貌似大部分帖子都没有说全,这里罗列一下,给在用的孩儿们一点参考: 1.在后台设置CSS/JS走CDN路径,具体[全局]-[性能优化]-[服务器优化] 2. ...
- 关于\t
tail -n 10000 sample_dbl.txt | python sitemap.py | sort -k 1 -t $'\t' | python sitecombiner.py > ...
- 基于jQuery的上下左右无缝滚动应用(单行或多行)
$(function(){ var _wrap=$('ul.line');//定义滚动区域 var _interval=2000;//定义滚动间隙时间 var _moving; ...