全方位解析Spring IoC:(六)其它特性

由于掘金文章存在字数限制,本文拆开了各个章节分开发布,全文可点击以下链接进行阅读:blog.omghaowan.com/archives/sp...

Environment

Environment(接口)表示当前应用环境的抽象,在Spring定义中它包含两大模块:ProfilesProperties

  • Profiles相当于逻辑bean定义分组的抽象(bean在定义时可被分配一个profile(通过XML或者Java注解)),只有profile被激活分组才能将它所包含的bean定义注册到容器中。我们可以使用(与Profiles所关联的)Environment决定默认的profile和激活的profile
  • Properties相当于应用配置属性的抽象,其中属性来源可源于: properties filesJVM system propertiessystem environment variablesJNDIservlet context parametersad-hoc Properties objectsMap objects等。我们可以使用Environment获取、解析和配置与其所关联的Properties属性。

Profiles

通过Profiles机制我们可以在不同环境Environment中将不同的bean注册到容器,例如:

  • 对开发、测试和生产环境分别使用不同的数据源进行工作。
  • 在生产环境注册监控设施,而在开发和测试环境下忽略监控设施的注册。

在使用上,我们仍然可以使用XMLJava注解的方式进行配置,不过由于篇幅的原因这里就不对XML的方式展示了,有兴趣的读者可以查阅相关资料。那么,下面我们将围绕着Java注解的方式展示说明。

在基于Java注解的配置方式中,我们可以将@Profile注解标注在类上或者@Bean方法上,使得它们只有在对应的profile被激活时才注册到容器中。

  • @Profile注解标注在配置类

    如果将@Profile注解标注在@Configuration类,那么它所包含的所有@Bean方法和通过@Import引入的类都只能在对应的profile被激活时才能注册到容器中。

    java 复制代码
    @Configuration
    @Profile("development")
    public class DevConfig {
        @Bean
        public DataSource dataSource() {
            return ...;
        }
    }
    
    @Configuration
    @Profile("production")
    public class ProConfig {
    
        @Bean
        public DataSource dataSource() throws Exception {
            return ...;
        }
    }
  • @Profile注解标注在@Bean方法

    java 复制代码
    @Configuration
    public class AppConfig {
    
        @Bean("dataSource")
        @Profile("development") 
        public DataSource devDataSource() {
            return ...;
        }
    
        @Bean("dataSource")
        @Profile("production") 
        public DataSource proDataSource() throws Exception {
            return ...;
        }
    }

除了直接使用@Profile注解外,我们还能通过将@Profile变成元注解来衍生出各种自定义的组合注解使其生效,例如直接使用如下的@Production来代替@Profile("production")

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}

其中所设置的profile值,我们可以将它设置为简单profile名字(例如development或者production),也可以将它设置为profile表达式。其中,通过profile表达式我们就可以设置更多复杂的逻辑profile去表达我们想要指定的注册范围,具体如下所示:

  • !: profile值的逻辑"否"(NOT
  • &: profile值的逻辑"与"(AND
  • |: profile值的逻辑"或"(OR

需要注意,我们不能在不使用括号的情况下直接使用混合&|,例如production & us-east | eu-central是无效表达式,要将它改成production & (us-east | eu-central)才能成立。

在完成上述对bean的分组后我们就可以向Spring指定我们需要激活的profile了。而对激活profile的指定存在几种不同的方式,最直接方式的就是使用Environment接口(通过ApplicationContext获取)来设置,即:

java 复制代码
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();

另外,我们还可以通过spring.profiles.active属性来指定需要激活的profile(可以在system environment variables或者JVM system properties等配置中指定),例如:

java 复制代码
-Dspring.profiles.active="development"

对于激活profile的设置完全是可以多选的,例如:

  • 通过Environment接口设置多个激活profile(用逗号(,)隔开):

    java 复制代码
    ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
  • 通过spring.profiles.active属性设置多个激活profile(用逗号(,)隔开):

    java 复制代码
    -Dspring.profiles.active="profile1,profile2"

需要注意,如果我们没有设置激活profile,那么可能会由于某些bean的缺失导致Spring在启动时或者在运行时发生错误。对于这种情况,我们也可以这是默认的profile,即如果没有显式设置激活profile,那么默认profile组内的bean就会被创建;否则默认profile组内的bean就会被忽略。默认情况下,对于默认profile的值为default,我们也可以使用Environment#setDefaultProfiles方法或者设置spring.profiles.default属性对其进行修改。

java 复制代码
@Configuration
@Profile("default")
public class DefaultDataConfig {

    @Bean
    public DataSource dataSource() {
        return ...;
    }
}

更多详情可阅读:

Properties

Properties模型中会将不同的属性来源(PropertySource)组合为层级结构(可配置的),但在访问时我们不需要管每个配置在具体哪个层级,而是使用SpringEnvironment接口(抽象)来方法,即:

java 复制代码
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会将每个属性来源构造为PropertySource,也就是说在查询属性时实际上就是使用Environment是在一系列PropertySource中寻找出对应的属性。例如,Spring中的StandardEnvironment就包含了两个PropertySource,其一为JVM system properties属性集合(可通过System.getProperties()获取);其二为system environment variables属性集合(可通过System.getenv()获取)。如果在JVM system properties或者在system environment variables中包含my-property属性,那么env.containsProperty("my-property")的调用就会返回true

对于PropertySource,我们可以将它看作是一个属性来源中的键值对(集合)。

需要注意,实际上Environment是按照层级从上往下查询的,如果在上层PropertySource中查询到结果则会立刻返回(顶层优先法则),而不会继续往下查询并执行类似于合并的操作(也可以看作是顶层的属性将底层的覆盖了)。

默认情况下,JVM system properties属性优先级高于system environment variables属性。

对于常见的StandardServletEnvironment,它完整的层级优先级如下所示(顶层是最高优先级):

  1. ServletConfig parameters (if applicable --- for example, in case of a DispatcherServlet context)
  2. ServletContext parameters (web.xml context-param entries)
  3. JNDI environment variables (java:comp/env/ entries)
  4. JVM system properties (-D command-line arguments)
  5. JVM system environment (operating system environment variables)

除此之外,这种层级结构也是可配置的。比如,如果我们想将自定义的属性来源加入到Environment的查询中(类似于配置中心),那么我们只需将自定义的PropertySource实例加入到当前EnvironmentPropertySources集合即可。

java 复制代码
ConfigurableApplicationContext ctx = new GenericApplicationContext();
// The MutablePropertySources API exposes a number of methods that allow for precise manipulation of the set of property sources.
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());

在上述代码中,MyPropertySource属性来源就已经被设置为Environment查询的最高优先级。

或者我们也可以采用一种更简单的方式,即使用@PropertySource注解将PropertySource添加到SpringEnvironment中。

java 复制代码
/**
 * Given a file called app.properties that contains the key-value pair testbean.name=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注解中的资源路径中的占位符${...​}Spring也可以使用已经注册到Environment中的属性来源(PropertySource)来进行解析。

java 复制代码
@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属性已经随着优先级更高的属性来源(PropertySource)被注册到Environment中,它将会得到解析;否则my.placeholder属性会使用默认值default/path(如果不存在默认值,则会抛出IllegalArgumentException异常)。

更多详情可阅读:

相关推荐
Dola_Pan2 小时前
Linux文件IO(二)-文件操作使用详解
java·linux·服务器
wang_book2 小时前
Gitlab学习(007 gitlab项目操作)
java·运维·git·学习·spring·gitlab
蜗牛^^O^3 小时前
Docker和K8S
java·docker·kubernetes
从心归零3 小时前
sshj使用代理连接服务器
java·服务器·sshj
一个诺诺前行的后端程序员4 小时前
springcloud微服务实战<1>
spring·spring cloud·微服务
IT毕设梦工厂4 小时前
计算机毕业设计选题推荐-在线拍卖系统-Java/Python项目实战
java·spring boot·python·django·毕业设计·源码·课程设计
Ylucius5 小时前
动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?
java·c语言·javascript·c++·python·学习
七夜zippoe5 小时前
分布式系统实战经验
java·分布式
是梦终空5 小时前
JAVA毕业设计176—基于Java+Springboot+vue3的交通旅游订票管理系统(源代码+数据库)
java·spring boot·vue·毕业设计·课程设计·源代码·交通订票
落落落sss5 小时前
sharding-jdbc分库分表
android·java·开发语言·数据库·servlet·oracle