问题:将一个业务系统拆分为多个功能模块分开,每个模块有其特有的配置数据;目前是将各个模块的配置数据全都放在一个公共的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的问题 缺点:有代码开发,需要子模块实现接口