环境接口是一个集成在容器中的抽象,它主要的两个方面为:Profiles 和 Properties。
public interface Environment extends PropertyResolver { //活动的Profiles名称 String[] getActiveProfiles(); //默认Profiles名称 String[] getDefaultProfiles(); //返回一个或多个Profiles是否为正在活动 @Deprecated boolean acceptsProfiles(String... profiles); //返回该Profiles是否为正在活动 boolean acceptsProfiles(Profiles profiles); } public interface PropertyResolver { //返回是否包含此属性 boolean containsProperty(String key); //获取属性值 @Nullable String getProperty(String key); //获取属性值,如果没找到,则返回默认值 String getProperty(String key, String defaultValue); //获取指定类型的属性值 @Nullable <T> T getProperty(String key, Class<T> targetType); //获取指定类型的属性值,如果没找到,则返回默认值 <T> T getProperty(String key, Class<T> targetType, T defaultValue); //获取属性值,如果没找到,则抛出异常 String getRequiredProperty(String key) throws IllegalStateException; //获取指定类型的属性值,如果没找到,则抛出异常 <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException; //返回已解析的字符串 String resolvePlaceholders(String text); //返回已解析的字符串,若无法解析,则抛出异常 String resolveRequiredPlaceholders(String text) throws IllegalArgumentException; }通过Environment接口和PropertyResolver父接口,了解到Environment接口本身实现Profiles 方面的功能,PropertyResolver父接口则实现Properties方面的功能。
Profile机制实现在不同环境下注册不同的Bean,如:
在不同环境下配置不同的数据源为A客户和B客户定制不同的Bean实现仅在真正部署的性能环境下才注册的一些配置在以往,我们通常使用系统环境变量和带有${placeholder}令牌的XML<import/ >组合来根据系统环境变量值指定要导入的XML文件实现环境切换。然而,Profile机制才是提供解决这种问题的容器核心功能。
使用@Profile注解来指定该组件(或配置类)的概要名称,此概要名称的环境被激活的时候才能注册到容器,否则该组件失效的。
@Configuration @Profile("development") public class AConfig { @Bean public DataSource dataSource() { //... } } @Configuration @Profile("production") public class BConfig { @Bean public DataSource dataSource() { //... } }概要名称可以是一个简单的名称字符串,也可以是复杂的逻辑表达式,它可以使用&、|、!操作符。
当要混合使用操作符时,你必须加上括号区分,如production & (us-east | eu-central)
在配置类上使用@Profile时,在未激活此环境下,该配置类中的所有Bean方法和@Import注解都将失效。
当然,@Profile还可以在方法上使用,如一个配置类中指定的Bean方法。
@Configuration public class AConfig { @Profile("dev1") @Bean("dataSource") public DataSource standaloneDataSource() { //... } @Profile("dev2") @Bean("dataSource") public DataSource jndiDataSource() { //... } } //模拟测试 public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(); //手动设置环境 ac.getEnvironment().setActiveProfiles("dev1"); System.out.println(Arrays.toString(ac.getEnvironment().getActiveProfiles())); //注册配置类 ac.register(AConfig.class); //刷新容器 ac.refresh(); //查看该容器所注册的组件 System.out.println(Arrays.toString(ac.getBeanDefinitionNames())); } }当你想定义具有不同概要条件的同一类型Bean方法时,请使用不同的Java方法名称,同时用@Bean中的name属性指定相同Bean名称,参考上述例子。
对于@Bean上的@Profile,当重载了相同Java名称的Bean方法情况下,你需要在所有重载的Bean方法上声明一致的@Profile(@Bean不指定Bean名称时会选择参数签名最长的那一个注册),若概要条件不一致,则会根据情况存在不确定性的Bean注册。所以,@Profile并不能用于选择具有特定参数签名的重载方法。
//测试 @Configuration public class Aconfig { @Profile("dev1") @Bean public Adao adao(@Value("1") int a, @Value("2") int b){ System.out.println(3); return new Adao(); } @Profile("dev1") @Bean public Adao adao(){ System.out.println(2); return new Adao(); } @Profile("dev1") @Bean public Adao adao(@Value("1") int a,@Value("2") int b,@Value("3") int c){ System.out.println(1); return new Adao(); } }在XML配置中,profile是作为<beans>的属性进行概要配置的,可以在不同配置文件<beans>中使用,也可以嵌套<beans>使用。
<beans profile="development" xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> ... </beans> <beans profile="production" xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> ... </beans>XML配置的形式仅支持!操作符,但可以通过嵌套<beans>的方式来实现&。
而最直接激活Profile的方式就是通过容器上下文编程式的激活,上一节中就是采用这种方式。此外,还可以使用spring.profiles.active属性,它通过系统环境变量、JVM系统属性、web.xml的servlet上下文参数或者是作为JNDI中的一个条目来指定。
在单页测试中,还可以通过@ActiveProfiles指定环境概要。
Spring还提供一种默认概要的配置,若没有概要配置被激活,则采用默认的概要配置,反之则不采用。
@Configuration @Profile("default") public class DefaultDataConfig { @Bean public DataSource dataSource() { //... } }通过default的概要名称来指定默认概要配置,当然,你也可以通过容器上下文中的setDefaultProfiles()方法来在自定义默认概要的名称。
Spring环境抽象提供了对属性源方面的操作。如你可以通过容器上下文获取的环境对象来搜索属性。
ApplicationContext ac = new AnnotationConfigApplicationContext(); //获取环境对象 Environment env = ac.getEnvironment(); //搜索属性 boolean containsMyProperty = env.containsProperty("my-property"); System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);Spring的StandardEnvironment实现类默认使用了JVM系统属性集和系统环境变量属性集,而StandardEnvironment是用于单体应用程序。通俗的讲,当你使用StandardEnvironment时,若存在一个my-property系统属性或my-property环境变量时,则env.containsProperty("my-property")返回true。
StandardServletEnvironment默认使用其他属性源,包括servlet上下文参数和servlet配置等。
这种搜索是分层的。默认情况下,系统属性是优先于环境变量的。例如,若两个位置都存在my-property属性,则系统属性的my-property会被采用,而环境变量中的my-property被覆盖。
在公共的StandardServletEnvironment中,完整的层次结构如下,最高优先级从上到下:
servlet配置参数(如DispatcherServlet上下文)servlet上下文参数(web.xml的context-param中)JNDI环境变量JVM系统属性JVM系统环境变量Spring中,关于属性源的整个机制是可配置的。你可以加入自定义配置源到当前环境的属性源集合中,如:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); //获取自定义属性源集合 MutablePropertySources sources = ctx.getEnvironment().getPropertySources(); //添加自定义属性源到最高优先级位置,当然也可以选择添加的优先级位置,通过其他方法 sources.addFirst(new MyPropertySource());MutablePropertySources实现类提供许多精确操作属性源集合的API。
@PropertySource注解也是添加属性源至Spring环境的一种方式。
//导入属性源 @PropertySource("my-properties.properties") @Configuration public class UserConfig { //${name:ww}指若容器环境中有name属性则注入name值,若无则注入默认值default @Bean public User user(@Value("${name:default}") String name){ return new User(name); } } @PropertySource("my-properties.properties") @Configuration public class UserConfig { //通过环境对象方式获取属性 @Autowired Environment env; @Bean public User user(){ return new User(env.getProperty("name")); } }在Java8之后,@PropertySource是可以重复的。但是所有@PropertySource必须定义在同一级别上,要么是在配置类上,要么是在元注解上。两者不能混用。