SpringBoot学习之旅终章---超越外部化配置

9 篇文章 0 订阅
9 篇文章 1 订阅

       通常,对于可扩展性应用,尤其是中间件,它们的功能性组件是可配置化的,如:认证信息、端口范围、线程池规模以及连接时间等。假设需要设置 Spring 应用的 Profile 为 "dev" ,可通过调用 Spring ConfigurableEnvironment 的 setActiveProfiles("dev") 方法实现。这种方式是一种显示地代码配置,配置数据来源于应用内部实现,所以称之为"内部化配置"。“内部化配置” 虽能达成目的,然而配置行为是可以枚举的,必然缺少相应的弹性。所以我们需要外部化配置来解决这个问题,比如我们在SpringBoot中经常用到application.properties来进行外部化配置。

SpringBoot是如何应用外部化配置的呢?Spring Boot 官方说明应用场景有以下3种方式:

  1.        Bean 的@Value 注入
  2.        Spring Environment 读取
  3.        @ConfigurationProperties 绑定到结构化对象

实际应用场景

  1. 用于 XML Bean 定义的属性占位符
  2. 用于 @Value 注入
  3. 用于 Environment 读取
  4. 用于 @ConfigurationProperties Bean 绑定
  5. 用于 @ConditionalOnProperty 判断

我们通过一个熟悉的 Spring 示例先看看用于 XML Bean 定义的属性占位符: 
1.配有 PropertyPlaceholderConfigurer Bean 的 Spring 上下文 XML 配置文件( META-INF/spring/spring-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- 属性占位符配置-->
<bean
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- Properties 文件 classpath 路径 -->
<property name="location" value="classpath:/META-INF/default.properties"/>
<!-- 文件字符编码 -->
<property name="fileEncoding" value="UTF-8"/>
</bean>
</beans>

2.模型类 User

public class User {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}

3.配置属性文件( META-INF/default.properties )

# 用户配置属性
user.id = 1
user.name = 光哥

4.User Spring 上下文XML 配置文件( META-INF/spring/user-context.xml )

<?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- User Bean -->
<bean id="user"
class="com.imooc.diveinspringboot.externalized.configuration.domain.User">
<property name="id" value="${user.id}"/>
<property name="name" value="${user.name}"/>
</bean>
</beans>

5.实现 Spring Framework 引导类

public class SpringXmlConfigPlaceholderBootstrap {
public static void main(String[] args) {
String[] locations = {"META-INF/spring/spring-context.xml", "METAINF/
spring/user-context.xml"};
ClassPathXmlApplicationContext applicationContext = new
ClassPathXmlApplicationContext(locations);
User user = applicationContext.getBean("user", User.class);
System.out.println("用户对象 : " + user);
// 关闭上下文
applicationContext.close();
}
}

上面的示例就是通过一种外部化配置来帮助我们实例化一个Bean,那么,如果我们调整为SpringBoot的时候又是怎样的呢?

1.使用 application.properties 替换 META-INF/default.properties

# 用户配置属性(Spring Boot)
user.id = 1
user.name = 光哥

2.实现 Spring Boot 引导类

@ImportResource("META-INF/spring/user-context.xml") // 加载 Spring 上下文 XML 文件
@EnableAutoConfiguration
public class XmlPlaceholderExternalizedConfigurationBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context =
new
SpringApplicationBuilder(XmlPlaceholderExternalizedConfigurationBootstrap.class)
.web(WebApplicationType.NONE) // 非 Web 应用
.run(args);
User user = context.getBean("user", User.class);
System.out.println("用户对象 : " + user);
// 关闭上下文
context.close();
}
}

但是使用这种方式进行外部化配置的时候,我们会发现,user对象的name不是我们所期望的光哥字样,这是为什么呢?其实SpringBoot在使用外部化配置的时候,会有一个优先级顺序,优先级最高的顺序是我们的JVM命令中的参数,但是这里我们没有使用JVM命令参数,那么是什么把user.name给替换了呢?答案是我们的环境变量参数,这个参数的优先级比user-context.xml的优先级要高很多,所以导致了这个原因。

如何使用Value来进行外部化配置呢?

用于 @Value 注入的方式有以下几种:

我们在下面演示一个使用了@Value构造器注入和默认值支持的使用方式,其中最复杂的是对age的使用,如果说能够找到user.age的配置,那么就使用user.age,否则就使用my.user.age,但是如果my.user.age还是没有结果的话,那么它的默认值就是32: 

  1. @Value 字段注入(Field Injection)
  2. @Value 构造器注入(Constructor Injection)
  3. @Value 方法注入(Method Injection)
  4. @Value 默认值支持
@EnableAutoConfiguration
public class ValueAnnotationBootstrap {

    @Bean
    public User user(@Value("${user.id}") Long id, @Value("${user.name}") String name,
                     @Value("${user.age:${my.user.age:32}}") Integer age) {
        User user = new User();
        user.setId(id);
        user.setName(name);
        user.setAge(age);
        return user;
    }

    public static void main(String[] args) {

        ConfigurableApplicationContext context =
                new SpringApplicationBuilder(ValueAnnotationBootstrap.class)
                        .web(WebApplicationType.NONE) // 非 Web 应用
                        .run(args);

        User user = context.getBean("user", User.class);
        User user2 = context.getBean("user2", User.class);

        System.err.println("用户对象 : " + user);
        System.err.println("用户对象2 : " + user2);

        // 关闭上下文
        context.close();
    }

}

 获取 Environment Bean,Environment也可以帮助我们获取外部化的配置,其具体使用方式如下:

  1. Environment 方法/构造器依赖注入
  2. Environment @Autowired 依赖注入
  3. EnvironmentAware 接口回调
  4. BeanFactory 依赖查找 Environment
@EnableAutoConfiguration
public class ValueAnnotationBootstrap implements
        BeanFactoryAware, EnvironmentAware { // Configuration Class -> @Configuration Class

    @Autowired
    @Qualifier(ENVIRONMENT_BEAN_NAME)
    private Environment environment;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        if (this.environment != beanFactory.getBean(ENVIRONMENT_BEAN_NAME, Environment.class)) {
            throw new IllegalStateException();
        }
    }

    @Override
    public void setEnvironment(Environment environment) {
        if (this.environment != environment) {
            throw new IllegalStateException();
        }
    }


    @Bean
    public User user2(
    ) {
        Long id = environment.getRequiredProperty("user.id", Long.class);
        String name = environment.getRequiredProperty("user.name");
        Integer age = environment.getProperty("user.age", Integer.class,
                environment.getProperty("my.user.age", Integer.class, 32));
        User user = new User();
        user.setId(id);
        user.setName(name);
        user.setAge(age);
        return user;
    }


    public static void main(String[] args) {

        ConfigurableApplicationContext context =
                new SpringApplicationBuilder(ValueAnnotationBootstrap.class)
                        .web(WebApplicationType.NONE) // 非 Web 应用
                        .run(args);

        User user = context.getBean("user", User.class);
        User user2 = context.getBean("user2", User.class);

        System.err.println("用户对象 : " + user);
        System.err.println("用户对象2 : " + user2);

        // 关闭上下文
        context.close();
    }

}

 用于 @ConfigurationProperties Bean 绑定

  1. @ConfigurationProperties 类级别标注

  2. @ConfigurationProperties @Bean 方法声明

  3. @ConfigurationProperties 嵌套类型绑定

优先级配置
Java System Properties
       -Duser.city.post_code=0731
OS Environment Variables
       USER_CITY_POST_CODE=001
application.properties
       user.city.post-code=0571


@ConfigurationProperties Bean 校验
用于 @ConditionalOnProperty 判断。@ConditionalOnProperty prefix name 要与 application.properties 完全一致,在环境变量里面,允许松散绑定。

@EnableAutoConfiguration
@EnableConfigurationProperties
public class ConfigurationPropertiesBootstrap {

    @Bean
    @ConfigurationProperties(prefix = "user")
    @ConditionalOnProperty(prefix = "user.",name = "city.post_code", matchIfMissing = false, havingValue = "0571")
    public User user() {
        return new User();
    }

    public static void main(String[] args) {

        Locale.setDefault(Locale.US);

        ConfigurableApplicationContext context =
                new SpringApplicationBuilder(ConfigurationPropertiesBootstrap.class)
                        .web(WebApplicationType.NONE) // 非 Web 应用
                        .run(args);

        User user = context.getBean(User.class);

        System.err.println("用户对象 : " + user);

        // 关闭上下文
        context.close();
    }
}

扩展外部化配置:

定位外部化配置属性源,我们知道外部属性配置源是一个一个的PropertySource,每个PropertySource有自己的优先级顺序,大致的优先级顺序如下:

如何理解 PropertySource ?其实是带有名称的属性源, Properties 文件、Map、YAML 文件
什么是 Envrioment 抽象?Environment 与 PropertySources 是1对1, PropertySources 与 PropertySource 是 1 对 N,所以我们经常在SpringBoot应用中可以使用Environment来读取所有的配置。

其实我们还可以自定义SpringBoot的各个Listener或者Initializer中来定义自己的配置员的优先级,但是这里笔者就不把代码列出来了,因为如果多个人开发的时候,如果别人对其中的加载优先级不了解,对于排查问题会非常困难,除非你是大神~对一些配置的把握成竹在胸,那么可以使用这种方式。

好了到这里为止,SpringBoot的学习就告一段落了~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值