为什么-@Value-可以获取配置中心的值,Java教学视频百度网盘

}


## 总结

1.  Spring 通过 `PropertySource` 来抽象配置属性源, `PropertySource` 允许有多个。`MutablePropertySources`
2.  在 Spring 容器启动的时候,会默认加载 systemEnvironment 和 systemProperties。`StandardEnvironment#customizePropertySources`
3.  我们可以通过 `@PropertySource` 注解或者 `MutablePropertySources API` 来添加自定义配置属性源
4.  `Environment` 是 Spring 对 profiles 和 properties 的抽象,默认实现是 `StandardEnvironment`
5.  `@Value` 的注入由 `AutowiredAnnotationBeanPostProcessor` 来提供支持,数据源来自于 `PropertySource`

public class Demo {

@Value("${os.name}") // 来自 system properties
private String osName;

@Value("${user.name}") // 通过 MutablePropertySources API 来注册
private String username;

@Value("${os.version}") // 测试先后顺序
private String osVersion;

public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(Demo.class);
    ConfigurableEnvironment environment = context.getEnvironment();
    MutablePropertySources propertySources = environment.getPropertySources();

    Map<String, Object> source = new HashMap<>();
    source.put("user.name", "xiaohei");
    source.put("os.version", "version-for-xiaohei");
    // 添加自定义 MapPropertySource,且放在第一位
    propertySources.addFirst(new MapPropertySource("coder-xiao-hei-test", source));
    // 启动容器
    context.refresh();

    Demo bean = context.getBean(Demo.class);
    // Mac OS X
    System.out.println(bean.osName);
    // xiaohei
    System.out.println(bean.username);
    // version-for-xiaohei
    System.out.println(bean.osVersion);
    // Mac OS X
    System.out.println(System.getProperty("os.name"));
    // 10.15.7
    System.out.println(System.getProperty("os.version"));
    // xiaohei
    System.out.println(environment.getProperty("user.name"));
    //xiaohei
    System.out.println(environment.resolvePlaceholders("${user.name}"));

    context.close();
}

}


# 简易版配置中心

## @Value 支持配置中心数据来源

`@Value` 的值都来源于 `PropertySource` ,而我们可以通过 API 的方式来向 Spring Environment 中添加自定义的 `PropertySource`。

在此处,我们选择通过监听 `ApplicationEnvironmentPreparedEvent` 事件来实现。

@Slf4j
public class CentralConfigPropertySourceListener implements ApplicationListener {

private final CentralConfig centralConfig = new CentralConfig();

@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    centralConfig.loadCentralConfig();
    event.getEnvironment().getPropertySources().addFirst(new CentralConfigPropertySource(centralConfig));
}

static class CentralConfig {
    private volatile Map<String, Object> config = new HashMap<>();

    private void loadCentralConfig() {
        // 模拟从配置中心获取数据
        config.put("coder.name", "xiaohei");
        config.put("coder.language", "java");

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 模拟配置更新
            config.put("coder.language", "java222");
            System.out.println("update 'coder.language' success");
        }).start();

    }
}

static class CentralConfigPropertySource extends EnumerablePropertySource<CentralConfig> {

    private static final String PROPERTY_SOURCE_NAME = "centralConfigPropertySource";

    public CentralConfigPropertySource(CentralConfig source) {
        super(PROPERTY_SOURCE_NAME, source);
    }

    @Override
    @Nullable
    public Object getProperty(String name) {
        return this.source.config.get(name);
    }

    @Override
    public boolean containsProperty(String name) {
        return this.source.config.containsKey(name);
    }

    @Override
    public String[] getPropertyNames() {
        return StringUtils.toStringArray(this.source.config.keySet());
    }
}

}


通过 `META-INF/spring.factories` 文件来注册:

org.springframework.context.ApplicationListener=com.example.config.CentralConfigPropertySourceListener


## 实时发布更新配置

一般来说有两种方案:

*   客户端拉模式:客户端长轮询服务端,如果服务端数据发生修改,则立即返回给客户端

*   服务端推模式:发布更新配置之后,由配置中心主动通知各客户端

    *   在这里我们选用服务端推模式来进行实现。在集群部署环境下,一旦某个配置中心服务感知到了配置项的变化,就会通过 redis 的 pub/sub 来通知客户端和其他的配置中心服务节点
    *   轻量级实现方案,代码简单,但强依赖 redis,pub/sub 可以会有丢失

## 自定义注解支持动态更新配置

Spring 的 `@Value` 注入是在 Bean 初始化阶段执行的。在程序运行过程当中,配置项发生了变更, `@Value` 并不会重新注入。

我们可以通过增强 `@Value` 或者自定义新的注解来支持动态更新配置。这里小黑选择的是第二种方案,自定义新的注解。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigValue {
String value();
}


@Component
public class ConfigValueAnnotationBeanPostProcessor implements BeanPostProcessor, EnvironmentAware {

private static final PropertyPlaceholderHelper PROPERTY_PLACEHOLDER_HELPER =
        new PropertyPlaceholderHelper(
                SystemPropertyUtils.PLACEHOLDER_PREFIX,
                SystemPropertyUtils.PLACEHOLDER_SUFFIX,
                SystemPropertyUtils.VALUE_SEPARATOR,
                false);

private MultiValueMap<String, ConfigValueHolder> keyHolder = new LinkedMultiValueMap<>();

private Environment environment;

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

    ReflectionUtils.doWithFields(bean.getClass(),
            field -> {
                ConfigValue annotation = AnnotationUtils.findAnnotation(field, ConfigValue.class);
                if (annotation == null) {
                    return;
                }
                String value = environment.resolvePlaceholders(annotation.value());
                ReflectionUtils.makeAccessible(field);
                ReflectionUtils.setField(field, bean, value);
                String key = PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders(annotation.value(), placeholderName -> placeholderName);
                ConfigValueHolder configValueHolder = new ConfigValueHolder(bean, beanName, field, key);
                keyHolder.add(key, configValueHolder);
            });

    return bean;
}

/**
 * 当配置发生了修改
 *
 * @param key 配置项
 */
public void update(String key) {
    List<ConfigValueHolder> configValueHolders = keyHolder.get(key);
    if (CollectionUtils.isEmpty(configValueHolders)) {
        return;
    }
    String property = environment.getProperty(key);
    configValueHolders.forEach(holder -> ReflectionUtils.setField(holder.field, holder.bean, property));
}

@Override
public void setEnvironment(Environment environment) {
    this.environment = environment;
}

@AllArgsConstructor
static class ConfigValueHolder {
    final Object bean;
    final String beanName;
    final Field field;
    final String key;
}

}


主测试代码:

@SpringBootApplication
public class ConfigApplication {

@Value("${coder.name}")
String coderName;

@ConfigValue("${coder.language}")
String language;

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

难道这样就够了吗?不,远远不够!

提前多熟悉阿里往年的面试题肯定是对面试有很大的帮助的,但是作为技术性职业,手里有实打实的技术才是你面对面试官最有用的利器,这是从内在散发出来的自信。

备战阿里时我花的最多的时间就是在学习技术上,占了我所有学习计划中的百分之70,这是一些我学习期间觉得还是很不错的一些学习笔记,以及学习视频都是可以点击这里免费领取的!

我为什么要写这篇文章呢,其实我觉得学习是不能停下脚步的,在网络上和大家一起分享,一起讨论,不单单可以遇到更多一样的人,还可以扩大自己的眼界,学习到更多的技术,我还会在csdn、博客、掘金等网站上分享技术,这也是一种学习的方法。

今天就分享到这里了,谢谢大家的关注,以后会分享更多的干货给大家!

阿里一面就落马,恶补完这份“阿里面试宝典”后,上岸蚂蚁金服

阿里一面就落马,恶补完这份“阿里面试宝典”后,上岸蚂蚁金服

image.png

的,在网络上和大家一起分享,一起讨论,不单单可以遇到更多一样的人,还可以扩大自己的眼界,学习到更多的技术,我还会在csdn、博客、掘金等网站上分享技术,这也是一种学习的方法。

今天就分享到这里了,谢谢大家的关注,以后会分享更多的干货给大家!

[外链图片转存中…(img-U9OaSCx3-1628608484554)]

[外链图片转存中…(img-J1v3arMe-1628608484557)]

[外链图片转存中…(img-3ObpijfQ-1628608484558)]

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值