【NACOS自定义配置读取和映射】一个简易的Nacos配置读取和映射处理器,自定义注解+BeanPostProcessor,简单方便高效的属性注入解决方法

引言

Nacos作为一款优秀的开源注册中心框架,同时提供了强大的配置管理功能。然而,在使用原生的Nacos注解读取配置,并映射到Bean时,我们可能会遇到一些问题:

  • 部分API不会直接生效,或者说需要做一些额外的操作,具体就是直接使用了@NacosConfigurationProperties后也没效果,使用了@NacosPropertySource标注了也没有效果,从源码里找处理的部分,在nacos-client包里没有的。感觉这块的api设计上本身就缺乏一些考究,既然加上没效果就放在有效果的包里好了,放在nacos-client包里面,它只是个摆设。
  • 不能像使用spring的@ConfigurationProperties那样方便、生效简单、轻量级,还需要依赖nacos-spring,而nacos-spring这个包的最后一次更新也已经比较早了,依赖的spring版本比较老。
  • api缺陷(待实际印证):好像使用中还有一些限制,比如set和map不支持直接映射(看到网上有人说这个,具体没试,因为配置了@NacosConfigProperties等一系列注解均不生效,没有试的机会)

于是就想自己利用现有依赖,编写一个简单易用的版本,在项目里作为基础组件提供,提供给项目其他成员使用。

本文介绍了一种基于自定义注解加Spring的BeanPostProcessor实现的Nacos配置自动读取和映射到配置类的处理器,它能够轻松地将属性从Nacos配置文件注入到目标类中,解决了一些原生注解的限制和不便,提供了更便捷、高效的配置读取和映射方式。

一、直接上代码:

以下为自定义注解及自定义BeanPostProcessor的完整代码:

package com.kamjin.common.nacos

import com.alibaba.cloud.nacos.*
import com.alibaba.nacos.api.config.listener.*
import com.kamjin.common.ext.*
import com.kamjin.common.utils.*
import org.springframework.beans.factory.config.*
import org.yaml.snakeyaml.*
import java.util.concurrent.*

/**
 * @author kamjin
 * @date 2023年6月7日
 * @description
 * 

* 简易版Nacos的配置类处理器 * 通过注解 [EasyNacosConfigurationProperties] 标注在类上,以将属性从 Nacos 端的配置文件读取,并注入到目标类中 * 使用当前功能需要将当前类注入到 Spring IOC 中 *

*/
class EasyNacosConfigurationPropertiesProcessor(private val nacosConfigManager: NacosConfigManager) : BeanPostProcessor { private val logger = getLogger<EasyNacosConfigurationPropertiesProcessor>() override fun postProcessAfterInitialization(bean: Any, beanName: String): Any { if (bean.javaClass.isAnnotationPresent(EasyNacosConfigurationProperties::class.java)) { //解析注解属性 val easyNacosConfigurationProperties = bean.javaClass.getAnnotation(EasyNacosConfigurationProperties::class.java) val dataId = easyNacosConfigurationProperties.dataId val group = easyNacosConfigurationProperties.group.takeIf { it.isNotBlank() } ?: nacosConfigManager.nacosConfigProperties.group //从nacos获取配置内容 val configInfo = nacosConfigManager.configService.getConfig(dataId, group, easyNacosConfigurationProperties.timeoutMs) //刷新配置到bean对象 refreshConfiguration(configInfo, easyNacosConfigurationProperties.prefix, bean) //如果开启了自动刷新,则注册一个配置监听器 if (easyNacosConfigurationProperties.autoRefresh) { nacosConfigManager.configService.addListener(dataId, group, object : Listener { override fun getExecutor(): Executor? { return null } override fun receiveConfigInfo(configInfo: String?) { configInfo ?: return refreshConfiguration(configInfo, easyNacosConfigurationProperties.prefix, bean) } }) } } return bean } /** * 刷新配置文件内容到配置类对象 * * @param configStr 配置文件内容 * @param prefix 配置所在前缀 * @param configurationBean 配置类对象 * @return */ private fun refreshConfiguration(configStr: String, prefix: String, configurationBean: Any) { val yamlConfigMap = Yaml().load<Map<String, Any>>(configStr) var beanConfigMap = yamlConfigMap.toCamelCaseKeys() if (prefix.isNotBlank()) { val prefixKeys = prefix.split(".") prefixKeys.forEach { try { beanConfigMap = beanConfigMap[it.toCamelCase()] as Map<String, Any> } catch (e: ClassCastException) { logger.error("配置的 prefix:$prefix 无效,对应配置无法转换到配置类", e) } } } val convertedConfig = JSONUtil.convertValue(beanConfigMap, configurationBean::class) BeanMapper.copy(convertedConfig, configurationBean) } /** * 将 map 中所有的 key 的中划线转为驼峰 * @return this */ private fun Map<String, Any>.toCamelCaseKeys(): Map<String, Any> { return mapValues { (key, value) -> if (value is Map<*, *>) { (value as Map<String, Any>).toCamelCaseKeys() } else { key.toCamelCase() } } } /** * 将字符串中的中划线转为驼峰 * @return this */ private fun String.toCamelCase(): String { val words = this.split("-") val result = StringBuilder(words[0]) for (i in 1 until words.size) { result.append(words[i].replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }) } return result.toString() } } @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.CLASS) annotation class EasyNacosConfigurationProperties( /** * Nacos 配置中心配置文件的 dataId */ val dataId: String, /** * Nacos 配置分组 */ val group: String = "", /** * 配置前缀,参考 [@ConfigurationProperties] */ val prefix: String = "", /** * 读取 Nacos 配置的超时时间 */ val timeoutMs: Long = 5000, /** * 是否需要自动刷新配置,为 true 会注册一个配置监听器来监听 Nacos 上配置的变化,以刷新到配置类 */ val autoRefresh: Boolean = false )

注:
代码中的JSONUtil(对json字符串的操作工具)、BeanMapper(对bean的操作工具)、getLogger(log的获取封装)都是自行封装的,可以替换为你项目中的实际封装。

二、使用

描述:
1.需要将EasyNacosConfigurationPropertiesProcessor类实例注册到Spring IOC容器中。这样的话,在Spring IOC初始化完成后会调用该处理器处理每个bean。

2.在目标配置类上使用@EasyNacosConfigurationProperties注解进行标注,指定相关的配置信息,如dataId、group等(用法类似@ConfigurationProperties和@NacosConfigurationProperties)。

这样,当Spring IOC容器初始化时,自定义的BeanPostProcessor会处理每个标注了EasyNacosConfigurationProperties注解的类,并将属性从Nacos配置中心的配置文件中读取,注入到目标类中。实现自动属性配置。

参考代码示例java也可以使用,代码改成java版即可):
配置方式与@ConfigurationProperties类似,默认不配置prefix,会读取整个配置文件内容并映射到该对象。

// 配置类示例代码
@EasyNacosConfigurationProperties(dataId = "app-config.yaml", group = "example-group", autoRefresh = true)
@Component
class AppConfig {
    // 配置属性
    var appName: String? = null
	
	var rules: Map<String,RuleProperties> =  = mutableMapOf()

    var appVersion: String? = null
	
	class RuleProperties {
		
		var id:Long? = null
		var name:String? = null
	}
    // ...
}

// 在Spring IOC容器中注入EasyNacosConfigurationPropertiesProcessor
@Bean
fun easyNacosConfigProcessor(nacosConfigManager: NacosConfigManager): EasyNacosConfigurationPropertiesProcessor {
    return EasyNacosConfigurationPropertiesProcessor(nacosConfigManager)
}

三、优势和特点

  • 简单易用:通过使用自定义注解,我们可以轻松地将属性从Nacos配置文件中注入到目标类中,而无需繁琐的配置和额外的代码。在使用了nacos的项目中,无需再有额外的如nacos-spring这样的依赖引入。

  • 提供了自动刷新功能:通过nacosConfigManager这个api实现了配置文件的监听和自动刷新,即当Nacos上的配置文件发生变化时,listener接收到最新的配置,会自动将最新的配置信息注入到目标类中,无需手动重启应用或手动刷新配置。

  • 支持配置前缀:通过指定配置前缀,我们可以灵活地控制属性的注入范围,只将符合指定前缀的配置注入到目标类中,避免不必要的属性注入和冲突。支持与spring定义的配置一样的处理方式,会自动映射中线为驼峰,并映射到类属性上。

四、实际应用场景

简易版Nacos配置处理器适用于各种实际应用场景,特别是在以下情况下可以发挥优势:

  • 多配置文件管理:当我们需要自定义多个配置文件时,只需要指定datgId,就可以将配置映射到项目中的某个pojo类上,在其他地方注入使用,该实现可以提供更灵活的配置管理方式。省去了多个地方写重复的配置内容读取和映射的问题。额外的配置文件多的情况下很有用。

  • 动态配置更新:在需要实时更新配置的场景下,该实现的自动刷新功能非常有用。无需手动重启应用,即可实现配置的实时更新,提高应用的灵活性和可维护性。这与@RefreshScope实现的效果是一致的。

五. 可能的改进和扩展方向

尽管该实现已经提供了一种方便和灵活的配置管理方式,但我们仍可以进一步改进和扩展功能。以下是一些可能的改进和扩展方向:

  • 支持更多的配置源:除了Nacos配置中心,我们可以考虑支持其他配置源,如Spring Cloud Config、Apollo等,以满足不同项目的需求。

  • 支持属性校验和类型转换:在属性注入过程中,我们可以添加属性校验和类型转换的功能,确保注入的属性满足一定的规则和要求。

  • 支持不同的配置文件内容类型:例如JSON、Properties、甚至自定义配置类型的文件的配置、读取和映射到bean。只需要在注解中,添加自定义的配置解析器就可以解决。

  • 提供更方便的启用和使用方式:要让现在的EasyNacosConfigurationProperties注解生效,启用需要注册这个EasyNacosConfigurationPropertiesProcessor,可以考虑做成SpringBootStater依赖的方式省去这个步骤。

总结

该实现是一个方便、灵活且易于使用的配置映射实现,通过采用自定义注解和Spring的BeanPostProcessor接口,提供了更灵活的配置读取和映射方式,并支持自动刷新配置。
它解决了原生Nacos注解实际使用时的不生效,部分配置不映射的问题,以及某些依赖太旧的问题,可以在实际项目应用中得到良好的效果。
希望本文能够帮助读者更好地理解和应用自定义注解以及spring的BeanPostProcessor用途,并在实际项目中发挥其作用。

你可能感兴趣的:(springboot,解决方案,java,微服务,spring,boot,nacos,自定义注解)