对nacos 的启动配置加载源码解析
nacos 源码的个人随笔记录
com.alibaba.nacos.spring.util.NacosUtils#toProperties(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
com.alibaba.nacos.spring.util.ConfigParseUtils
配置文本解析的工具类
nacos配置中心配置的内容,被nacos读取后,最原始是一个文本,解析这个文本nacos是定义了一个解析接口 com.alibaba.nacos.spring.util.ConfigParse
核心方法就是parse方法,传入的String类型configText 就是读取到原始的配置文本。nacos 默认配置了4中解析方式,分别是 json properties xml yaml 格式解析器
AbstractConfigParse 这是一个抽象接口,作用看类上注释,用户可以继承,自己实现自定义的解析器,只需要把自己实现的解析器方法类目录
META-INF/services/com.alibaba.nacos.spring.util.ConfigParse 这个文件下,主要原理还是 使用的Java SPI
自定义的解析器也是在com.alibaba.nacos.spring.util.ConfigParseUtils 类中使用静态代码块进行加载,加载到CUSTOMER_CONFIG_PARSE_MAP 这个变量中
可以随便看一个内置的解析器是如何编写的
加载到自定义解析器如何使用呢?
还是在 com.alibaba.nacos.spring.util.ConfigParseUtils 这个类中 com.alibaba.nacos.spring.util.ConfigParseUtils#toProperties(java.lang.String, java.lang.String, java.lang.String, java.lang.String) 这样一个方法,这个类整体比较简单,直接把完整代码贴出来
public final class ConfigParseUtils { private static final String LINK_CHAR = "#@#"; private static Map<String, ConfigParse> DEFAULT_CONFIG_PARSE_MAP = new HashMap(8); private static Map<String, Map<String, ConfigParse>> CUSTOMER_CONFIG_PARSE_MAP = new HashMap( 8); static { DefaultJsonConfigParse jsonConfigParse = new DefaultJsonConfigParse(); DefaultPropertiesConfigParse propertiesConfigParse = new DefaultPropertiesConfigParse(); DefaultYamlConfigParse yamlConfigParse = new DefaultYamlConfigParse(); DefaultXmlConfigParse xmlConfigParse = new DefaultXmlConfigParse(); // register nacos default ConfigParse DEFAULT_CONFIG_PARSE_MAP.put(jsonConfigParse.processType().toLowerCase(), jsonConfigParse); DEFAULT_CONFIG_PARSE_MAP.put(propertiesConfigParse.processType().toLowerCase(), propertiesConfigParse); DEFAULT_CONFIG_PARSE_MAP.put(yamlConfigParse.processType().toLowerCase(), yamlConfigParse); DEFAULT_CONFIG_PARSE_MAP.put(xmlConfigParse.processType().toLowerCase(), xmlConfigParse); // register customer ConfigParse ServiceLoader<ConfigParse> configParses = ServiceLoader.load(ConfigParse.class); StringBuilder sb = new StringBuilder(); for (ConfigParse configParse : configParses) { String type = configParse.processType().toLowerCase(); if (!CUSTOMER_CONFIG_PARSE_MAP.containsKey(type)) { CUSTOMER_CONFIG_PARSE_MAP.put(type, new HashMap<String, ConfigParse>(1)); } sb.setLength(0); sb.append(configParse.dataId()).append(LINK_CHAR).append(configParse.group()); if (LINK_CHAR.equals(sb.toString())) { // If the user does not set the data id and group processed by config // parse, // this type of config is resolved globally by default DEFAULT_CONFIG_PARSE_MAP.put(type, configParse); } else { CUSTOMER_CONFIG_PARSE_MAP.get(type).put(sb.toString(), configParse); } } DEFAULT_CONFIG_PARSE_MAP = Collections.unmodifiableMap(DEFAULT_CONFIG_PARSE_MAP); CUSTOMER_CONFIG_PARSE_MAP = Collections .unmodifiableMap(CUSTOMER_CONFIG_PARSE_MAP); } // 使用默认的解析器进行解析 public static Map<String, Object> toProperties(final String context, String type) { if (context == null) { return new LinkedHashMap<String, Object>(); } // Again the type lowercase, ensure the search type = type.toLowerCase(); if (DEFAULT_CONFIG_PARSE_MAP.containsKey(type)) { ConfigParse configParse = DEFAULT_CONFIG_PARSE_MAP.get(type); return configParse.parse(context); } else { throw new UnsupportedOperationException( "Parsing is not yet supported for this type profile : " + type); } } public static Map<String, Object> toProperties(final String dataId, final String group, final String context, String type) { if (context == null) { return new LinkedHashMap<String, Object>(); } // Again the type lowercase, ensure the search type = type.toLowerCase(); String configParseKey = dataId + LINK_CHAR + group; // 如果自定义解析器存储的map不为空,读取到了解析器,那么直接走else 使用自定义解析器 if (CUSTOMER_CONFIG_PARSE_MAP.isEmpty() || LINK_CHAR.equals(configParseKey)) { return toProperties(context, type); } if (CUSTOMER_CONFIG_PARSE_MAP.get(type) == null || CUSTOMER_CONFIG_PARSE_MAP.get(type).isEmpty()) { return toProperties(context, type); } if (CUSTOMER_CONFIG_PARSE_MAP.get(type).get(configParseKey) == null) { return toProperties(context, type); } else { if (CUSTOMER_CONFIG_PARSE_MAP.containsKey(type)) { ConfigParse configParse = CUSTOMER_CONFIG_PARSE_MAP.get(type) .get(configParseKey); if (configParse == null) { throw new NoSuchElementException( "This config can't find ConfigParse to parse"); } return configParse.parse(context); } else { throw new UnsupportedOperationException( "Parsing is not yet supported for this type profile : " + type); } } } } |
我们自定义一个解析器,这里直接把源码中的解析器的代码复制出来,到一个新的类文件中,最后debug运行,可以看到我们自定义的解析器被加载到了
如果使用自定义的解析器,需要注意的是 一定要重写com.imobpay.ifeeling.core.MyPropertiesConfigParse#dataId 和 com.imobpay.ifeeling.core.MyPropertiesConfigParse#group 否则,最后即使加载进入,但是最后还是使用默认的解析器,因为在 ConfigParseUtils 类中 CUSTOMER_CONFIG_PARSE_MAP.isEmpty() || LINK_CHAR.equals(configParseKey) 有个这样的判断,在83行。
自定义的解析器 dataId 方法和group 方法返回值是什么呢,这个返回值其实是 nacos 配置中那两列的对应值,比如 ifeeling-order DEFAULT-GROUP 最后在源码中拼接成 ifeeling-order#@#DEFAULT-GROUP,在ConfigParseUtils类中最终需要以这个key 从自定义的解析器存储map中获取,如果没有找到,那么还是去使用默认定义好的解析器,在ConfigParseUtils 类 90 行代码
此外,nacos 和 spring boot 整合时, @NacosPropertySource(dataId = "delay-queue.yml", autoRefreshed = true) 这个注解中 dataId 参数的值,如果不加文件后缀.yml 那么即使你nacos服务端配置的是yaml 格式,那么最终在应用启动时nacos还是按.properties 格式进行读取配置,这个需要注意!!具体可以打断点在 com.alibaba.nacos.spring.core.env.AbstractNacosPropertySourceBuilder#doBuild 这个方法
并且 nacos 配置中心配置的值 dataId 也必须是和上面的dataId一致
本文来自博客园,作者:margo,转载请注明原文链接:https://www.cnblogs.com/ZMargo/articles/15007781.html