美文网首页
Spring依赖Jar包同名配置文件冲突

Spring依赖Jar包同名配置文件冲突

作者: insomniaLee | 来源:发表于2020-04-28 21:27 被阅读0次

    问题描述

    昨天接到了一个需求是这样的,项目A作为公有工具类项目,被项目B和项目C所依赖,同时项目C依赖项目B。A中的配置依赖于外部的properties,在B和C中均配有同名properties以启动项目A,但是在实际运行过程中,项目B和项目C加载的是同一配置文件(可能取决于B和C的加载顺序)。现在的要求是在A中能读到所有配置的配置信息(同名配置append到属性上而不是覆盖)。之前的开发中一直用的都是SpringBoot不是Spring,对使用xml配置的这种方式感到很陌生,只能硬着头皮上。

    项目A的xml配置信息

    A是一个spring项目,使用classpath*的方式将外部配置加载到项目中。并使用@Value(${})的方式获取Properties里面的值。

    <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
        <property name="ignoreUnresolvablePlaceholders" value="true" />
        <property name="ignoreResourceNotFound" value="true"/>
        <property name="order" value="2018062911" />
        <property name="locations">
            <list>
                <value>classpath*:conf_default.properties</value>
                <value>classpath*:conf.properties</value>
            </list>
        </property>
    </bean>
    

    探索

    有关classpath和classpath*

    Spring可以通过指定classpath*:或classpath:前缀加路径的方式从classpath下加载文件。

    • classpath*:可以从多个jar文件中加载相同的文件。
    • classpath:只能加载找到的第一个文件。
      而使用classpath加载一般的优先级为:当前classes > jar包中的classes

    解决方案

    找遍了也没找到这种类似的解决方案,但是有个自定义加载类从而不用@Value方式而是用静态方法读取配置的代码给了我思路。经过自己验证过目前有两种解决方案:

    通过Resource读取

    这种方法使用

    Resource[] resources=resolver.getResources("classpath*:conf.properties" );
    

    这样的方法来将所有的conf.properties配置文件加载到项目当中。经过检验,这种方式确实可以获取到所有的配置文件。后续思路是定义一个Properties的工具类,实现Properties的懒加载,后续项目中使用配置信息的时候使用静态方法来获取,而不是使用@Value注解的方式。

    自定义加载类,重写重要方法

    经过单步调试发现,Properties加载类中对于需要merge的属性,有一个方法进行处理。

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        try {
            Properties mergedProps = this.mergeProperties();
            this.convertProperties(mergedProps);
            this.processProperties(beanFactory, mergedProps);
        } catch (IOException var3) {
            throw new BeanInitializationException("Could not load properties", var3);
        }
    }
    

    再经过漫长的调用链发现,最终调用对properties赋值时,Spring采用的是对value直接覆盖的做法(Properties继承自HashTable)

    String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
    String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
    put(key, value);
    

    到这里我们的目标就很明确了,要做的有如下几点。

    • xml中自定义加载类PropertyUtil,其他不用改变
    <bean class="com.zombie.a.PropertyUtil">
        <property name="ignoreUnresolvablePlaceholders" value="true" />
        <property name="ignoreResourceNotFound" value="true"/>
        <property name="order" value="2018062911" />
        <property name="locations">
            <list merge="true">
                <value>classpath*:conf-default.properties</value>
                <value>classpath*:conf.properties</value>
            </list>
        </property>
    </bean>
    
    • 自定义MyProperties 继承自Properties,修改赋值逻辑
      实际这一步只要改一行代码,但是很多依赖的函数我直接也一道copy到MyProperties了
    put(key, get(key)==null?value:get(key)+","+value);
    
    • 自定义加载类中替换Properties
      我的做法是直接重写mergeProperties方法,其实也只用该一行代码,即:Properties result = new MyProperties();
    protected Properties mergeProperties() throws IOException {
        Properties result = new MyProperties();
        if (this.localOverride) {
            this.loadProperties(result);
        }
    
        if (this.localProperties != null) {
            Properties[] var2 = this.localProperties;
            int var3 = var2.length;
    
            for(int var4 = 0; var4 < var3; ++var4) {
                Properties localProp = var2[var4];
                CollectionUtils.mergePropertiesIntoMap(localProp, result);
            }
        }
        if (!this.localOverride) {
            this.loadProperties(result);
        }
        return result;
    }
    

    验证

    最后做了下验证,我定义了3个conf.properties 1个conf_default.properties最终同一属性的所有值都读到了。

    default,prod,aaaa,bbb
    

    完美!收工。

    相关文章

      网友评论

          本文标题:Spring依赖Jar包同名配置文件冲突

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