spring boot项目自动加载子模块配置数据

1,187 阅读2分钟

问题:将一个业务系统拆分为多个功能模块分开,每个模块有其特有的配置数据;目前是将各个模块的配置数据全都放在一个公共的application.yml文件中,修改A模块配置数据可能误改到B模块的配置数据。 愿景:实现每个模块各自管理各自的配置,每次修改配置都在各自模块内完成,不需要去修改公共的application.yml文件。

方案一: @Configuration注解+@PropertySource注解

@Configuration
@PropertySource(value="子模块配置-${spring.profiles.active}.yml", ignoreResourceNotFound = true)
public class InitConfiguration{

    @Bean
    public LeaderLatch leaderLatch(@Value("${zk.ip}") final String zkIp,
                                   @Value("${zk.leader.latch.path}") final String latchPath) {
        CuratorFramework client = CuratorFrameworkFactory.newClient(zkConnect, new RetryNTimes(5, 2000));
        client.start();
        return new LeaderLatch(client, latchPath);
    }
}

优点:使用spring自带功能集成子模块配置,没有额外开发成本。 缺点:InitConfiguration中不能使用@Value功能,必须在@Service、@Component类中使用。即上例中的zk.ip属性值不能注入成功。

方案二: 使用java SPI技术 1:创建一个模块管理接口类

package com.deon;
public interface ModelLauncher extends Ordered, Comparable<ModelLauncher> {
    /**
     * 获取模块名称
     *
     * @return
     */
    String getModelName();

    /**
     * 排序
     *
     * @return
     */
    @Override
    default int getOrder() {
        return 0;
    }

    /**
     * 排序比较
     *
     * @param o
     * @return
     */
    @Override
    default int compareTo(ModelLauncher o) {
        return Integer.compare(this.getOrder(), o.getOrder());
    }
}

2:让每个子模块实现该接口,子模块打包时在/META-INF/services目录加入文件名称为com.deon.ModelLauncher的文件,文件内容为“com.deon.settlement.ModelSettlementLauncher”

package com.deon.settlement;
public class ModelSettlementLauncher implements ModelLauncher {
    public static final String NAME = "settlement";

    @Override
    public String getModelName() {
        return NAME;
    }
}

3:在启动类中通过SPI技术获取所有ModelLauncher接口的实现,获取所有的子模块的配置文件名称,并写入spring 的配置文件变量“spring.config.additional-location”

import static org.springframework.boot.context.config.ConfigFileApplicationListener.CONFIG_ADDITIONAL_LOCATION_PROPERTY;
@SpringBootApplication 
public class Application {

    public static void main(String[] args) {
        Properties props = System.getProperties();
        String location = props.getProperty(CONFIG_ADDITIONAL_LOCATION_PROPERTY);
        String profile = props.getProperty("spring.profiles.active");

        StringBuilder sb = new StringBuilder();
        List<ModelLauncher> modelList = new ArrayList();
        ServiceLoader.load(ModelLauncher.class).forEach(modelList::add);
        modelList.stream().sorted(Comparator.comparing(ModelLauncher::getOrder))
        .collect(Collectors.toList()).forEach((model) -> {
            if (StringUtils.isNotBlank(model.getModelName())) {
                String file = String.format("application-%s-%s.yml", model.getModelName(), profile);
                ClassPathResource classPathResource = new ClassPathResource(file);
                if (classPathResource.exists()) {
                    sb.append("classpath:").append(file).append(",");
                    log.info("添加配置文件:{}", file);
                }
            }
        });
        if (StringUtils.isBlank(location)) {
            props.setProperty(CONFIG_ADDITIONAL_LOCATION_PROPERTY, sb.toString());
        } else {
            props.setProperty(CONFIG_ADDITIONAL_LOCATION_PROPERTY, String.format("%s,%s", location, sb.toString()));
        }
        
        SpringApplication.run(Application.class);
    }
}

优点:可以解决@Configuration中不能使用@Value的问题 缺点:有代码开发,需要子模块实现接口