bean中字段获取配置文件中的数据

  • Post author:
  • Post category:其他

如果想要将配置文件中的配置信息赋于bean中字段可以使用@Value和@ConfigurationProperties这两种形式。在@Value中一般使用的是@Value(“${xxx.yyy.zzz:defaultValue}”)这种形式,我们可以获取配置文件中key对应的值,如果找不到该配置则使用后面跟的那个默认值。

例如配置文件application.properties中有
spring.application.name=myProject
feign.hystrix.enabled=true


可以使用该种形式获取对应的配置key对应的value值 
@Value("${spring.application.name}”)
String applicationName; 

@Value的实现方式主要与org.springframework.core.env.PropertySource 、org.springframework.core.env.Environment 有关。PropertySource可以理解为数据源,它其中有个方法叫做getProperty(String name),通过key的名字获取数据源中对应的key-value对的value值,我们的配置文件就是一个数据源。
Environment 即项目的环境配置信息它其中有两个方法,String resolvePlaceholders(String text) 解析@Value(“${xxxx}”)xxxx,

ConfigurableEnvironment 中包含MutablePropertySources getPropertySources()获取所有的PropertySource实例,PS:MutablePropertySource实现了PropertySource接口。那么从上我们就可以大概推出@Value的执行过程
(1)environment使用resolvePlaceholders解析@Value中的配置key
(2)environment使用getPropertysources方法获取MutablePropertySources,它其中有一个包含所有的PropertySource的list
(3)利用MutablePropertySources对所有的PropertySource进行遍历,使用getProperty查询对应的value

那么除了将配置信息放在配置文件中,从上面我们看出来其实是从propertySource中获取key对应的value,所有我们还可以将配置信息放在数据库或其他存储媒介中,只是在项目初始化时,需要将对应的配置信息生成一个propertySource对象,放入MutablePropertySources中,简单给个代码。

/**
 * 邮件配置信息
 */
@Data
@Component
public class MailConfig {
 
    @Value("${mail.host}")
    private String host;
 
    @Value("${mail.username}")
    private String username;
 
    @Value("${mail.password}")
    private String password;
}


@Test
public void test2() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
 
    //模拟从db中获取配置信息
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    /*上面这段是关键 end*/
 
    context.register(MainConfig2.class);
    context.refresh();
    MailConfig mailConfig = context.getBean(MailConfig.class);
    System.out.println(mailConfig);
}

一般情况下我们是使用@Value(“${xxx.yyy}”)去获取对应配置文件中的value,其实还有一种形式是@Value(“#{}”),那么这个#标记的@Value是干嘛的呢?#表示执行一个spel表达式
(1) 获取常量 @Value(“#{1}”)、 @Value(“#{‘字符串常量’}”)
(2) 获取bean的属性,但是要确保被获取属性的bean和本bean在同一个容器中,如@Value(“#{aBean.name}”),获取aBean的name属性
(3) 调用bean的方法,如@Value(“#{aBean.getName}”),获取aBean的getName方法返回值下面再简单说一下@ConfigurationProperties,它可以利用配置前缀获取配置中的配置信息,一般用于配置类之上,举个简单的小例子。以七牛公共和私有bucket配置为例

bootstrap.yml中有
qiniu:
  buckets:
    private:
      bucket: yqg-private-secret
      domain: http://private-domain/
    public:
       bucket: yqg-public-test
       domain: https://public-domain/

那么其对应的配置类格式为,可以看到我们甚至可以根据配置文件中配置属性的层级去定义配置类的结构
@Data
@ConfigurationProperties(prefix = "qiniu")
public class QiniuProperties {
    private Map<String, BucketConfig> buckets = new HashMap<>();

    @Data
    public static class BucketConfig {
        private String bucket;
        private String domain;
    }
}

由此我们可以看出一些@Value和@ConfigurationProperties的区别,如果仅仅想获取其中一个配置的value那么就用@value,如果想要获取一类配置的value,那么可以使用@ConfigurationProperties,它一个用在属性字段上,一个用在类上,且@Value支持$和#两种形式,#可以获取常量、其他bean的属性字段,方法返回值。

@Value和@ConfigurationProperties动态刷新

现在大家的项目中一般都会使用配置中心用来动态获取配置信息,不管是Spring-boot的配置管理、zookeeper还是携程的apollo都为我们提供了便捷的配置管理功能。当配置中心的配置发生改变时,会通知客户端服务,更改environment中的对应的配置信息,当environment发生更改时会触发EnvironmentChangeEvent事件,其中包括了keys字段,就是发生变更的配置名。
@ConfigurationProperties原本就支持配置自动刷新,在ConfigurationPropertiesRebinder中会监听EnvironmentChangeEvent事件,将所有被@ConfigurationProperties注解修饰的bean进行重新绑定,此时就会刷新@ConfigurationProperties标识bean中的属性值。而@Value注解标识的字段并不会自动刷新,需要搭配@RefreshScope进行刷新。

如果我们使用了@Value,创建bean时获取的配置value值会赋值给在bean中对应属性字段,它只会在bean创建的过程中获取一次,即使以后通过配置中心改变了environment中key对应的value,@Value修饰字段的值也不会改变。那么该如何对它们进行动态刷新呢?
       在spring-cloud中提供了一个注解叫做@RefreshScope,它可以提供配置的动态刷新。先进到@RefreshScope这个注解中去看一下,注解的内容很简单就是把使用了该注解的类,它的@Scope作用域置为fresh,ScopeProxyMode使用TARGET_CLASS,看一下它的英文注释,大概就是说使用该注解修饰的bean可以在运行时刷新,每次使用bean时都会创建一个新的bean,而ScopedProxyMode.TARGET_CLASS表示创建该bean时实际返回的通过cglib产生的一个代理对象。

/**
 * Convenience annotation to put a <code>@Bean</code> definition in
 * {@link org.springframework.cloud.context.scope.refresh.RefreshScope refresh scope}.
 * Beans annotated this way can be refreshed at runtime and any components that are using
 * them will get a new instance on the next method call, fully initialized and injected
 * with all dependencies.
 * 
 * @author Dave Syer
 *
 */
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {
 /**
  * @see Scope#proxyMode()
  */
 ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}

它的实现类RefreshScope.java->GenericScope->Scope(接口),可以看一下它的类注释,我就不贴了,其中写了被@RefreshScope修饰的bean会被置为懒加载,只有在第一次使用它的时候才会进行创建,如果有其他的bean依赖与它,那么它获取的只是被@RefreshScope修饰bean的一个代理。
(1)当第一次使用时,会将对应bean创建并生成代理,然后放入到一个cache之中,当不刷新时,获取的代理对象都是这一个
(2)当属性发生改变,environment中对应的value发送改变,然后会发出refresh请求,会调用RefreshScope.refreshAll()或refresh(string name)方法,前者为刷新全部配置,后者为刷新指定bean的配置,执行GenericScope的destory()或destory(name)方法,并发送refreshScope事件,供监听处理。
(3)在GenericScope的destory方法中会将cache中全部或指定name的代理对象缓存删除,然后对应代理对象销毁
(4)当下次在使用对象中的属性或方法时会调用GenericScope的get方法重新利用新的配置value值生成代理对象,并放入缓存

下面简单给个代码

//bootstrap.yml
mail:
  username: myUserName


//配置类
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

@Component
@RefreshScope
public class TestRefreshScope {

  @Value("${mail.username}")
  private String name;

  public String getName() {
    return name;
  }
}

//直接在idea里建了个eureka项目,在它启动类里写的测试代码
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(EurekaServerApplication.class, args);

        System.out.println("在修改配置值之前");
        for (int i=1; i<=3; i++) {
            TestRefreshScope obejct = context.getBean(TestRefreshScope.class);
            System.out.println(obejct);
            System.out.println(obejct.getName());
            System.out.println("================================");
        }

        System.out.println("刷新后");
        //模拟自定义propertySource并刷新现有的邮箱配置,它可以来自数据库等其他存储中介
        for (int i=1; i<=3; i++) {
            refreshMailPropertySource(context);
            RefreshScope refreshScope = context.getBean(RefreshScope.class);
            //手动触发refresh,正常应该是执行一个refresh post请求
            refreshScope.refresh("testRefreshScope");
            TestRefreshScope obejct = context.getBean(TestRefreshScope.class);
            System.out.println(obejct);
            System.out.println(obejct.getName());
            System.out.println("================================");
        }
    }

    private static void refreshMailPropertySource(ConfigurableApplicationContext context) {
        Map<String, Object> mailInfo = new HashMap<>();
        String uuid = UUID.randomUUID().toString();
        System.out.println("新生成的uuid="+uuid);
        mailInfo.put("mail.username", uuid);
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfo);
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    }
}

执行结果如下

在修改配置值之前
com.springboot.eurekaserver.TestRefreshScope@6546371
myUserName
================================
com.springboot.eurekaserver.TestRefreshScope@6546371
myUserName
================================
com.springboot.eurekaserver.TestRefreshScope@6546371
myUserName
================================
刷新后
新生成的uuid=f48dd171-440b-48cd-b5be-0e3ebd6d2668
com.springboot.eurekaserver.TestRefreshScope@227a933d
f48dd171-440b-48cd-b5be-0e3ebd6d2668
================================
新生成的uuid=c13c562c-5d5f-4607-ac5b-46356fb448a1
com.springboot.eurekaserver.TestRefreshScope@7bc2ae16
c13c562c-5d5f-4607-ac5b-46356fb448a1
================================
新生成的uuid=d49de0bf-11eb-4589-8eb7-f6d5ec0022ba
com.springboot.eurekaserver.TestRefreshScope@64910b2d
d49de0bf-11eb-4589-8eb7-f6d5ec0022ba
================================


版权声明:本文为swl1993831原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。