# SpringBootSupportOutsideConfig **Repository Path**: mistone/SpringBootSupportOutsideConfig ## Basic Information - **Project Name**: SpringBootSupportOutsideConfig - **Description**: spring boot 支持加载外部配置文件 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2019-09-20 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SpringBootSupportOutsideConfig # 增加外置适配代码 增加spring启动监听器AppConfigFileApplicationListener ``` package com.mistone.cs; import org.springframework.boot.context.event.ApplicationPreparedEvent; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; /** * 注册应用自定义配置文件路径解析 * * @author mistone * @version 1.0 * @created **/ public class AppConfigFileApplicationListener implements ApplicationListener { @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); } } private void onApplicationPreparedEvent(ApplicationEvent event) { addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext()); } protected void addPostProcessors(ConfigurableApplicationContext context) { context.addBeanFactoryPostProcessor(new AppConfigFileBeanFactoryPostProcess(context)); } } ``` 增加bean 定义factoryAppConfigFileBeanFactoryPostProcess ``` package com.mistone.cs; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; import org.springframework.core.io.ResourceLoader; /** * 注册应用自定义配置文件路径解析 * * @author mistone * @version 1.0 * @created **/ public class AppConfigFileBeanFactoryPostProcess implements BeanDefinitionRegistryPostProcessor, PriorityOrdered { private AbstractApplicationContext context; public AppConfigFileBeanFactoryPostProcess(ConfigurableApplicationContext context) { if(context instanceof AbstractApplicationContext){ this.context = (AbstractApplicationContext) context; } } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { if(registry instanceof ConfigurableListableBeanFactory){ ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) registry; AppConfigFileBeanPostProcess acf = new AppConfigFileBeanPostProcess(context); beanFactory.addBeanPostProcessor(acf); beanFactory.registerResolvableDependency(ResourceLoader.class, acf); } } } ``` 增加bean 后置处理器AppConfigFileBeanPostProcess ``` package com.mistone.cs; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.context.config.ConfigFileApplicationListener; import org.springframework.context.ApplicationContext; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.Resource; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; import java.io.IOException; import java.util.*; /** * 描述 * * @author mistone * @version 1.0 * @created **/ public class AppConfigFileBeanPostProcess implements ResourcePatternResolver, BeanPostProcessor { private static final String[] DEFAULT_SEARCH_LOCATIONS = new String[]{"file:./config/","file:./","classpath:/config/","classpath:/"}; private ApplicationContext applicationContext; private String[] searchLocations; public AppConfigFileBeanPostProcess(ApplicationContext applicationContext) { this.applicationContext = applicationContext; searchLocations = getLocationsFinal(); } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { // 偷梁换柱 if (bean instanceof ResourceLoaderAware) { ((ResourceLoaderAware) bean).setResourceLoader(this); } return bean; } /** * 查找spring.config.location和spring.config.additional-location配置 * @return */ private Set getSearchLocations() { if (applicationContext.getEnvironment().containsProperty(ConfigFileApplicationListener.CONFIG_LOCATION_PROPERTY)) { return getSearchLocations(ConfigFileApplicationListener.CONFIG_LOCATION_PROPERTY); } Set locations = getSearchLocations(ConfigFileApplicationListener.CONFIG_ADDITIONAL_LOCATION_PROPERTY); return locations; } /** * 处理参数 * @param propertyName * @return */ private Set getSearchLocations(String propertyName) { Set locations = new LinkedHashSet<>(); if (applicationContext.getEnvironment().containsProperty(propertyName)) { for (String path : asResolvedSet(applicationContext.getEnvironment().getProperty(propertyName), null)) { if (!path.contains("$")) { path = StringUtils.cleanPath(path); if (!ResourceUtils.isUrl(path)) { path = ResourceUtils.FILE_URL_PREFIX + path; } } locations.add(path); } } return locations; } /** * 和默认配置进行合并 * @return */ private String[] getLocationsFinal(){ Set sets = getSearchLocations(); Set floderSets = new LinkedHashSet<>(); // 只做目录级别支持 sets.forEach((name)->{ if(name.endsWith("/")){ floderSets.add(name); } }); floderSets.addAll(Arrays.asList(DEFAULT_SEARCH_LOCATIONS)); return floderSets.toArray(new String[0]); } /** * 处理参数,并反转 * @param value * @param fallback * @return */ private Set asResolvedSet(String value, String fallback) { List list = Arrays.asList(StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray( (value != null) ? applicationContext.getEnvironment().resolvePlaceholders(value) : fallback))); Collections.reverse(list); return new LinkedHashSet<>(list); } @Override public Resource getResource(String location) { // 交个spring处理 if(location==null||location.startsWith("classpath:")||location.startsWith("file:")||location.startsWith("/")) { return applicationContext.getResource(location); }else{ Resource resource; // 查找并加工一下 for(String path:searchLocations){ resource = applicationContext.getResource(path+location); if(resource.exists()){ return resource; } } return applicationContext.getResource(location); } } @Override public ClassLoader getClassLoader() { return applicationContext.getClassLoader(); } /** * 不对正则的进行处理了 * @param locationPattern * @return * @throws IOException */ @Override public Resource[] getResources(String locationPattern) throws IOException { return applicationContext.getResources(locationPattern); } } ``` 增加spring.facotries配置 在resources下配置MATE-INF/spring.factories内容如下 ``` # Application Listeners org.springframework.context.ApplicationListener=\ com.mistone.cs.AppConfigFileApplicationListener ``` # 使用与配置 默认支持按照 classpath:/,classpath:/config/,file:./,file:./config/ 这个优先级查找不带file,/或者classpath前缀的配置文件,会优先加载高优先级的文件,带file:,/和classpath:前缀的依然按照原方式处理 使用spring.config.location环境变量 指定配置文件路径 使用spring.config.additional-location环境变量 添加优先查找路径,优先级高于默认配置(推荐) 以上两个配置兼容spring boot指定applicaiton路径的配置,不只指定到文件的,只支持指定查找目录,会自动忽略指定到文件的设置 如java -Dspring.config.additional-location=file:/cofig -jar test.jar # 支持范围 像PropertySource注解这样使用spring的resourceLoader加载配置文件的都会被替换,按照我们外置方案查找配置文件 resourLoader注入的两种方式 使用@Autowired 或者@Resource 注入 解决方案 AppConfigFileBeanFactoryPostProcess类的 beanFactory.registerResolvableDependency(ResourceLoader.class, acf); 实现ResourceLoaderAware接口 ConfigurationClassPostProcessor属于这种 解决方案 AppConfigFileBeanPostProcess类 ((ResourceLoaderAware) bean).setResourceLoader(this); PropertuSource解析过程见org.springframework.context.annotation.ConfigurationClassPostProcessor application配置文件加载过程见org.springframework.boot.context.config.ConfigFileApplicationListener # 注意 spring.config.location和spring.config.additional-location 指定路径为目录需要以/结尾 # 结语 目前classpth*:这样的还使用原来的加载方式加载,暂不考虑做处理, 可以视需求考虑忽略项目中的classpath:和file:配置都进行自动查找,目前暂不特殊处理。