<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          為什么 @Value 可以獲取配置中心的值?

          共 11997字,需瀏覽 24分鐘

           ·

          2021-01-11 15:29

          點(diǎn)擊藍(lán)字關(guān)注不迷路


          hello,大家好,我是小黑,好久不見~~

          這是關(guān)于配置中心的系列文章,會(huì)分多篇發(fā)布,內(nèi)容大致包括:

          1、Spring 是如何實(shí)現(xiàn) @Value 注入的

          2、一個(gè)簡(jiǎn)易版配置中心的關(guān)鍵技術(shù)

          3、開源主流配置中心相關(guān)技術(shù)分享

          移動(dòng)設(shè)備閱讀體驗(yàn)可能較差,建議在 PC 上閱讀

          @Value 注入過程

          從一個(gè)最簡(jiǎn)單的程序開始:

          @Configuration
          @PropertySource("classpath:application.properties")
          public?class?ValueAnnotationDemo?{

          ????@Value("${username}")
          ????private?String?username;

          ????public?static?void?main(String[]?args)?{
          ????????AnnotationConfigApplicationContext?context?=?new?AnnotationConfigApplicationContext(ValueAnnotationDemo.class);

          ????????System.out.println(context.getBean(ValueAnnotationDemo.class).username);

          ????????context.close();
          ????}
          }

          application.properties 文件內(nèi)容:

          username=coder-xiao-hei

          AutowiredAnnotationBeanPostProcessor 負(fù)責(zé)來處理 @Value ,此外該類還負(fù)責(zé)處理 @Autowired@Inject。

          AutowiredAnnotationBeanPostProcessor 構(gòu)造器

          AutowiredAnnotationBeanPostProcessor 中有兩個(gè)內(nèi)部類:AutowiredFieldElementAutowiredMethodElement

          當(dāng)前為 Field 注入,定位到 AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject 方法。

          AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject

          通過 debug 可知,整個(gè)調(diào)用鏈如下:

          AutowiredFieldElement#inject ->?DefaultListableBeanFactory#resolveDependency -> DefaultListableBeanFactory#doResolveDependency -> AbstractBeanFactory#resolveEmbeddedValue
          DefaultListableBeanFactory#doResolveDependency

          通過上述的 debug 跟蹤發(fā)現(xiàn)可以通過調(diào)用 ConfigurableBeanFactory#resolveEmbeddedValue 方法可以獲取占位符的值。

          ConfigurableBeanFactory#resolveEmbeddedValue

          這里的 resolver 是一個(gè) lambda 表達(dá)式,繼續(xù) debug 我們可以找到具體的執(zhí)行方法:

          AbstractApplicationContext#finishBeanFactoryInitialization

          到此,我們簡(jiǎn)單總結(jié)下:

          1. @Value 的注入由 AutowiredAnnotationBeanPostProcessor 來提供支持
          2. AutowiredAnnotationBeanPostProcessor 中通過調(diào)用 ConfigurableBeanFactory#resolveEmbeddedValue 來獲取占位符具體的值
          3. ConfigurableBeanFactory#resolveEmbeddedValue 其實(shí)是委托給了 ConfigurableEnvironment 來實(shí)現(xiàn)

          Spring Environment

          Environment 概述

          https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-environment

          The Environment interface is an abstraction integrated in the container that models two key aspects of the application environment: profiles and properties.

          A profile is a named, logical group of bean definitions to be registered with the container only if the given profile is active. Beans may be assigned to a profile whether defined in XML or with annotations. The role of the Environment object with relation to profiles is in determining which profiles (if any) are currently active, and which profiles (if any) should be active by default.

          Properties play an important role in almost all applications and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects, Map objects, and so on. The role of the Environment object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them.

          Environment 是對(duì) profiles 和 properties 的抽象:

          • 實(shí)現(xiàn)了對(duì)屬性配置的統(tǒng)一存儲(chǔ),同時(shí) properties 允許有多個(gè)來源
          • 通過 Environment profiles 來實(shí)現(xiàn)條件化裝配 Bean

          現(xiàn)在我們主要來關(guān)注 Environment 對(duì) properties 的支持。

          StandardEnvironment

          下面,我們就來具體看一下 AbstractApplicationContext#finishBeanFactoryInitialization 中的這個(gè) lambda 表達(dá)式。

          strVal?->?getEnvironment().resolvePlaceholders(strVal)

          首先,通過 AbstractApplicationContext#getEnvironment 獲取到了 ConfigurableEnvironment 的實(shí)例對(duì)象,這里創(chuàng)建的其實(shí)是 StandardEnvironment 實(shí)例對(duì)象。

          StandardEnvironment 中,默認(rèn)添加了兩個(gè)自定義的屬性源,分別是:systemEnvironment 和 systemProperties。

          StandardEnvironment

          也就是說,@Value 默認(rèn)是可以注入 system properties 和 system environment 的。

          PropertySource

          StandardEnvironment 繼承了 AbstractEnvironment

          AbstractEnvironment 中的屬性配置被存放在 MutablePropertySources 中。同時(shí),屬性占位符的數(shù)據(jù)也來自于此。

          AbstractEnvironment 成員變量

          MutablePropertySources 中存放了多個(gè) PropertySource ,并且這些 PropertySource 是有順序的。

          MutablePropertySources#get

          PropertySource 是 Spring 對(duì)配置屬性源的抽象。

          PropertySource

          name 表示當(dāng)前屬性源的名稱。source 存放了當(dāng)前的屬性。

          讀者可以自行查看一下最簡(jiǎn)單的基于 Map 的實(shí)現(xiàn):MapPropertySource。

          配置屬性源

          有兩種方式可以進(jìn)行屬性源配置:使用 @PropertySource 注解,或者通過 MutablePropertySources 的 API。例如:

          @Configuration
          @PropertySource("classpath:application.properties")
          public?class?ValueAnnotationDemo?{

          ????@Value("${username}")
          ????private?String?username;

          ????public?static?void?main(String[]?args)?{
          ????????AnnotationConfigApplicationContext?context?=?new?AnnotationConfigApplicationContext(ValueAnnotationDemo.class);

          ????????Map?map?=?new?HashMap<>();
          ????????map.put("my.name",?"coder小黑");
          ????????context.getEnvironment()
          ????????????????.getPropertySources()
          ????????????????.addFirst(new?MapPropertySource("coder-xiaohei-test",?map));
          ????}
          }

          總結(jié)

          1. Spring 通過 PropertySource 來抽象配置屬性源, PropertySource 允許有多個(gè)。MutablePropertySources
          2. 在 Spring 容器啟動(dòng)的時(shí)候,會(huì)默認(rèn)加載 systemEnvironment 和 systemProperties。StandardEnvironment#customizePropertySources
          3. 我們可以通過 @PropertySource 注解或者 MutablePropertySources API 來添加自定義配置屬性源
          4. Environment 是 Spring 對(duì) profiles 和 properties 的抽象,默認(rèn)實(shí)現(xiàn)是 StandardEnvironment
          5. @Value 的注入由 AutowiredAnnotationBeanPostProcessor 來提供支持,數(shù)據(jù)源來自于 PropertySource
          public?class?Demo?{

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

          ????@Value("${user.name}")?//?通過?MutablePropertySources?API?來注冊(cè)
          ????private?String?username;

          ????@Value("${os.version}")?//?測(cè)試先后順序
          ????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?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));
          ????????//?啟動(dòng)容器
          ????????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();
          ????}
          }

          簡(jiǎn)易版配置中心

          @Value 支持配置中心數(shù)據(jù)來源

          @Value 的值都來源于 PropertySource ,而我們可以通過 API 的方式來向 Spring Environment 中添加自定義的 PropertySource

          在此處,我們選擇通過監(jiān)聽 ApplicationEnvironmentPreparedEvent 事件來實(shí)現(xiàn)。

          @Slf4j
          public?class?CentralConfigPropertySourceListener?implements?ApplicationListener<ApplicationEnvironmentPreparedEvent>?{

          ????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?config?=?new?HashMap<>();

          ????????private?void?loadCentralConfig()?{
          ????????????//?模擬從配置中心獲取數(shù)據(jù)
          ????????????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 文件來注冊(cè):

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

          實(shí)時(shí)發(fā)布更新配置

          一般來說有兩種方案:

          • 客戶端拉模式:客戶端長(zhǎng)輪詢服務(wù)端,如果服務(wù)端數(shù)據(jù)發(fā)生修改,則立即返回給客戶端

          • 服務(wù)端推模式:發(fā)布更新配置之后,由配置中心主動(dòng)通知各客戶端

            • 在這里我們選用服務(wù)端推模式來進(jìn)行實(shí)現(xiàn)。在集群部署環(huán)境下,一旦某個(gè)配置中心服務(wù)感知到了配置項(xiàng)的變化,就會(huì)通過 redis 的 pub/sub 來通知客戶端和其他的配置中心服務(wù)節(jié)點(diǎn)
            • 輕量級(jí)實(shí)現(xiàn)方案,代碼簡(jiǎn)單,但強(qiáng)依賴 redis,pub/sub 可以會(huì)有丟失

          自定義注解支持動(dòng)態(tài)更新配置

          Spring 的 @Value 注入是在 Bean 初始化階段執(zhí)行的。在程序運(yùn)行過程當(dāng)中,配置項(xiàng)發(fā)生了變更, @Value 并不會(huì)重新注入。

          我們可以通過增強(qiáng) @Value 或者自定義新的注解來支持動(dòng)態(tài)更新配置。這里小黑選擇的是第二種方案,自定義新的注解。

          @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?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;
          ????}

          ????/**
          ?????*?當(dāng)配置發(fā)生了修改
          ?????*
          ?????*?@param?key?配置項(xiàng)
          ?????*/

          ????public?void?update(String?key)?{
          ????????List?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;
          ????}
          }

          主測(cè)試代碼:

          @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);
          ????????ConfigApplication?bean?=?context.getBean(ConfigApplication.class);
          ????????//?xiaohei
          ????????System.out.println(bean.coderName);
          ????????//?java
          ????????System.out.println(bean.language);

          ????????ConfigValueAnnotationBeanPostProcessor?processor?=?context.getBean(ConfigValueAnnotationBeanPostProcessor.class);

          ????????//?模擬配置發(fā)生了更新
          ????????TimeUnit.SECONDS.sleep(10);

          ????????processor.update("coder.language");

          ????????//?java222
          ????????System.out.println(bean.language);
          ????}
          }


          后臺(tái)回復(fù)?學(xué)習(xí)資料?領(lǐng)取學(xué)習(xí)視頻


          如有收獲,點(diǎn)個(gè)在看,誠(chéng)摯感謝

          瀏覽 44
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人免费黄色网址 | 我要看国产靠逼的 | 五月丁香色色网 | 五月婷婷深爱网 | 国产中文字幕网 |