美文网首页java jvmJava技术升华java技术分享
如何在旧项目中实现从配置中心加载配置

如何在旧项目中实现从配置中心加载配置

作者: Chandler_珏瑜 | 来源:发表于2018-03-29 11:19 被阅读267次

    前言

        spring cloud相关组件很强大,可以轻松实现配置中心。但是会遇到一个很实际的问题:spring cloud config所依赖的spring相关组件包版本比较高,而公司中很多重要的工程都是以前开发的,不是spring boot工程,而且spring版本可能为3.X,如何解决这个问题呢?

    配置中心

    如何获取配置中心的配置信息

    配置信息调用测试

        先来看一下如何在配置中心访问,如上图所示,通过URL路径来获取对应的配置文件。

        ▪️/{application}/{profile}[/{label}]

        ▪️/{application}-{profile}.yml

        ▪️/{application}-{profile}.properties

        ▪️/{label}/{application}-{profile}.properties

        上面的url会映射{application}-{profile}.properties对应的配置文件,其中{label}对应Git上不同的分支,默认为master。我们可以尝试构造不同的url来访问不同的配置内容,比如要访问kyle分支,didispace应用的prod不同的配置内容,比如,要访问kyle分支,didispace应用的prod环境,就可以访问这个url:http://localhost:8888/didispace/prod/kyle,并获得如下返回信息。

    调用测试

        如果我们能写一个组件,它可以实现如上http调用,不就能实现在非spring boot工程实现配置获取了嘛?

    LocalPropertyPlaceholderConfigurer LocalPropertyPlaceholderConfigurer

        ▪️继承PropertyPlaceholderConfigurer重写mergeProperties(),PropertyPlaceholderConfigurer主要做了两件事:

             ▪️读取某个地方的配置信息,到底读取哪里的配置信息,由mergeProperties()决定。

            ▪️在bean实例获取之前,逐个替换${}形式的参数。实现这一点意义很大,比如可以直接使用@Value("${}")获取配置中心的配置信息,也可以在spring的XML配置文件中使用"${}"进行赋值。

        ▪️使用BasicAuthorizationInterceptor在restTemplate中放入Basic验证信息,与配置中心中心权限管理相呼应。在低版本spring(3.1.4)并不存在,可以根据高版本spring中的类,自己创建。

    BasicAuthorizationInterceptor

        ▪️通过restTemplate封装http请求,从配置中心服务端获取配置信息。

        ▪️将配置中心放入本地Properties,方便客户端使用。

    补充:

    config-center.properties spring.xml

        简单来说就是创建了一个自定义的property-placeholder,专职于获取配置中心的配置信息。

    如何实现旧工程热更新

    ContextRefresher

        上图所示为spring容器的refresh方法。

    总结

        当然还有其他事情需要完成,比如更新前后配置差异反馈,监控数据获取端点,在此我就不累赘讲述了,大家自行研究。如此将公司旧系统纳入spring cloud config配置中心管理,以后还要纳入集中监控(spring boot admin),慢慢将旧系统(低版本,非spring boot,未拆分)向新系统(spring boot,微服务化)平滑迁移。提供旧系统过渡方案,组件包支持,可以减少系统迁移改造成本,比用一堆新组件进行微服务化,服务治理,容器化等改造等意义更加重大。

    以下为spring相关源码阅读和理解,有兴趣的可以自行观赏。

    Spring容器的refresh方法

    AbstractApplicationContext

    Spring容器的refresh方法

    AbstractApplicationContext

    prepareRefresh函数

    此方法主要记录一下初始化的时候或active的标记,并对一些properties进行初始化

    prepareRefresh()

    obtainFreshBeanFactory函数

    obtainFreshBeanFactory()

    此方法全部由子类实现。

        ▪️refreshBeanFactory()

    refreshBeanFactory()的实现类

        在AbstractRefreshApplicationContext实现了refreshBeanFactory方法,此方法最主要的就是loadBeanDefinitions(beanFactory),由子类去实现load所有的beanDefinition。Spring在启动时会通过AbstractApplicationContext#refresh启动容器初始化工作,期间会委托loadBeanDefinitions配置文件。

    refreshBeanFactory()默认实现

        loadBeanDefinitions通过层层委托,找到DefaultBeanDefinitionDocumentReader #parseBeanDefinitions解析具体的bean。

    parseBeanDefinitions()

        这边由于不是标准类定义,所以委托BeanDefinitionParserDelegate解析通过NamespaceHandler查找到对应的处理器是ContextNamespaceHandler,再通过id找到PropertyPlaceholderBeanDefinitionParser解析器解析

    BeanDefinitionParserDelegate ContextNamespaceHandler

        创建一个默认的BeanFactory。使用了DefaultListableBeanFactory。

        ▪️getBeanFactory()

    getBeanFactory()的实现类 getBeanFactory()默认实现

    prepareBeanFactory(beanFactory)函数

    prepareBeanFactory(beanFactory)

        预处理beanFactory容器,配置一些beanFactory的classLoader和post-processor等。

    postProcessBeanFactory(beanFactory)

        此方法由子类执行beanFactory之前的操作。

    invokeBeanFactoryPostProcessors(beanFactory)

        用于执行BeanFactoryPostProcessors中的postProcessBeanFactory,beanFactoryPostProcessor是优先于BeanPostProcessor执行。

        先执行BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry。再执行BeanFactoryPostProcessor.postProcessBeanFactory。

    registerBeanPostProcessors(beanFactory)

        注册beanPostProcessor,注意要和beanFactoryPostProcessor区分开,   beanFactoryPostProcessor是执行。此时这里只是注册beanPostProcessor,beanFactoryPostProcessory优先于beanPostProcessor执行beanPostProcessor的流程与beanFactoryPostProcessor执行流程类似。

            ▪️先查找所有BeanPostProcessor类型的bean

            ▪️注册属于PriorityOrdered的beanPostProcessor

            ▪️注册属于Ordered类型的beanPostProcessor

            ▪️注册剩余的beanPostProcessor

            ▪️增加一个继承了ApplicationListener的beanPostProcessor检查ApplicationListenerDetector。

    TestBeanPostProcessor

    initMessageSource()函数

    initMessageSource()

            ▪️初始化一个MessageSource

                    ▪️判断beanFactory是否有name=messageSource的MessageSource

                    ▪️生成一个默认的MessageSource DelegatingMessageSource并注册到beanFactory中

    initApplicationEventMulticaster()

    initApplicationEventMulticaster()

            ▪️初始化ApplicationEvent类

                    ▪️判断beanFactory是否有name=applicationEventMulticaster的ApplicationEventMulticaster

                    ▪️生成默认的SimpleApplicationEventMulticaster并注册到beanFactory中

    onRefresh()

    onRefresh()

    此方法由子类实现

    onRefresh()的实现

    registerListeners()

    registerListeners()

        加载所有ApplicationListener并注册到ApplicationEventMulticaster中。

    finishBeanFactoryInitialization(beanFactory)

    finishBeanFactoryInitialization(beanFactory)

            ▪️处理beanFactory初始化结束的操作

            ▪️beanFactory.preInstantiateSingletons开始实例化所有未实例化的bean

    finishRefresh()

            ▪️生成一个LifecycleProcessor默认为DefaultLifecycleProcessor

            ▪️执行LifecycleProcessor的onrefresh方法,执行所有属于Lifecycle的bean

            ▪️推送ApplicationEvent

    Spring加载Java Properties

        Spring中PropertyPlaceholderConfigurer类,它是用来解析Java Properties文件属性值,并提供在Spring期间替换使用属性值。常用的使用方式:

            ▪️使用注解@Value("${}"),加载Java Properties文件属性值

            ▪️在spring.xml中使用"${}"赋值,加载Java Properties文件属性值

    Spring容器加载Java Properties文件

        上图为核心类UML图,涉及类:PropertyPlaceholderConfigurer,PlaceholderConfigurerSupport,PropertyResourceConfigurer,PropertiesLoaderSupport;涉及接口:BeanNameAware,BeanFactoryAware,BeanFactoryPostProcessor,PriorityOrdered。

    PropertiesLoaderSupport

    PropertiesLoaderSupport

            ▪️localOverride:加载的数据是否覆盖spring之前加载的同名属性

            ▪️locations:配置properties文件路径,例如"classpath:xxxx";"file:xxxx"

            ▪️ignoreResourceNotFound:加载多个properties文件,是否忽略不存在的文件。

        看了上图,应该立马能够理解PropertiesLoaderSupport相关属性的作用了。当我们需要扩展PropertyPlaceholderConfigurer的实现时,需要重写mergeProperties(),然后将配置加载到PropertiesLoaderSupport。

     ▪️mergeProperties()

    mergeProperties()

            ▪️作用一:返回包含合并多个Properties属性实例

            ▪️作用二:在这个FactoryBean上设置了加载的属性

    ▪️loadProperties(Properties props)

    loadProperties

            ▪️作用一:加载路径locations的Java Properties文件

    PropertyPlaceholderConfigurer

    PropertyPlaceholderConfigurer

        你也许会好奇,为什么使用"${}"就能获取properties文件中的属性值?PropertyPlaceholderConfigurer会在在创建bean之前,将"${}"替换成对应的属性,并赋值给成员变量。

    ▪️parseStringValue(String strVal, Properties props, Set visitedPlaceholders)

    parseStringValue

            ▪️作用:解析给定字符串的占位符,并赋值。

            现在我们看看红框中的都是什么?答案在PlaceholderConfigurerSupport的相关成员变量中。

    PlaceholderConfigurerSupport

            看到这里你就能立马明白,需要进行处理的"${}"在这里进行了判断和替换。到此spring如何加载Java Properties文件,如何进行"${}"替换属性都已经十分清晰明了。

    如果需要給我修改意见的发送邮箱:erghjmncq6643981@163.com

    本博客的代码示例已上传GitHub:分布式配置中心

    GitHub

    转发博客,请注明,谢谢。

    相关文章

      网友评论

      本文标题:如何在旧项目中实现从配置中心加载配置

      本文链接:https://www.haomeiwen.com/subject/uffmcftx.html