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相关源码阅读和理解,有兴趣的可以自行观赏。
AbstractApplicationContext AbstractApplicationContext此方法主要记录一下初始化的时候或active的标记,并对一些properties进行初始化
prepareRefresh() 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()▪️初始化一个MessageSource
▪️判断beanFactory是否有name=messageSource的MessageSource
▪️生成一个默认的MessageSource DelegatingMessageSource并注册到beanFactory中
initApplicationEventMulticaster()
initApplicationEventMulticaster()▪️初始化ApplicationEvent类
▪️判断beanFactory是否有name=applicationEventMulticaster的ApplicationEventMulticaster
▪️生成默认的SimpleApplicationEventMulticaster并注册到beanFactory中
onRefresh()此方法由子类实现
onRefresh()的实现 registerListeners()加载所有ApplicationListener并注册到ApplicationEventMulticaster中。
finishBeanFactoryInitialization(beanFactory)
finishBeanFactoryInitialization(beanFactory)▪️处理beanFactory初始化结束的操作
▪️beanFactory.preInstantiateSingletons开始实例化所有未实例化的bean
▪️生成一个LifecycleProcessor默认为DefaultLifecycleProcessor
▪️执行LifecycleProcessor的onrefresh方法,执行所有属于Lifecycle的bean
▪️推送ApplicationEvent
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▪️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你也许会好奇,为什么使用"${}"就能获取properties文件中的属性值?PropertyPlaceholderConfigurer会在在创建bean之前,将"${}"替换成对应的属性,并赋值给成员变量。
▪️parseStringValue(String strVal, Properties props, Set visitedPlaceholders)
parseStringValue▪️作用:解析给定字符串的占位符,并赋值。
现在我们看看红框中的都是什么?答案在PlaceholderConfigurerSupport的相关成员变量中。
PlaceholderConfigurerSupport看到这里你就能立马明白,需要进行处理的"${}"在这里进行了判断和替换。到此spring如何加载Java Properties文件,如何进行"${}"替换属性都已经十分清晰明了。
如果需要給我修改意见的发送邮箱:erghjmncq6643981@163.com
本博客的代码示例已上传GitHub:分布式配置中心
GitHub转发博客,请注明,谢谢。
网友评论