Как SpringBoot загружает файлы конфигурации вне пакета jar?

Spring Boot Java

Оригинал: Miss Sister Taste (идентификатор публичной учетной записи WeChat: xjjdog), добро пожаловать, пожалуйста, сохраните источник для перепечатки.

Хотя Springboot теперь обеспечивает поддержку нескольких сред, обычно его необходимо переупаковывать, чтобы изменить файл конфигурации.

В разработке Transboot Integrated Framework я столкнулся с проблемой, как сделать@PropertySourceВозможность «сканировать» и загружать банки外面файл свойств.

Таким образом, я могу изменить файл конфигурации в любое время и в любом месте без переупаковки.

Самый грубый способ — указать эти файлы с помощью --classpath. Но это приводит к другим проблемам, «простым в развертывании», «не зависящим от контейнера», что сложно. Более того, эту проблему по-прежнему трудно решить умно в тестовой среде, многокомнатном развертывании и сотрудничестве с центром конфигурации, потому что это связано с множеством жестких спецификаций и даже затратами на связь.

Возвращаясь к природе технологии, я надеюсь разработать комплект совместимости на основе контейнера Spring, способный сканировать файл свойств за пределами JAR, учитывать удобство реализации, и мы согласны с тем, что эти файлы свойств всегда находятся в природе JAR-файла.

предпосылка дизайна

1. Каталог файлов

Каталог файлов выглядит следующим образом. Вы можете увидеть, что файл конфигурации параллельна пакету JAR.

----application.jar  (springboot项目,jarLaucher)  
     |  
     | sample.properties  
     | config/  
             |  
             | sample.properties  

2, стратегия сканирования (включая вопросы приоритета наложения)

1) Мы соглашаемся с тем, что каталог файлов конфигурации по умолчанию — config, что является наивысшим приоритетом. Остальная часть application.jar находится на том же уровне, относительный путь начинается с пути jar.

2) Сначала выясните, существует ли файл ./config/sample.properties, и загрузите его, если он существует.

3) Ищите ./sample.properties файл существует, если есть загружено.

4) В противном случае загрузите этот файл, используя classpath.

3. Стратегия развития

1) Максимально использовать пружинный механизм, т.е.ResourceМеханизм загрузки не применяет локальные файлы или вмешательство сценариев развертывания и т. Д.

2) Благодаря исследованиям расширить обычайResourceLoaderЭта цель может быть достигнута, но потенциальный риск очень высок, потому что в рамках springboot и облачных фреймворков поддержка различных Contexts имеет свои собственные реализации ResourceLoader, Если мы расширим свой собственный загрузчик, это вызовет какие-то неизвестные проблемы? Поэтому от этой стратегии отказались.

3) Весна обеспечиваетProtocolResolverМеханизм используется для сопоставления пользовательской схемы файла для загрузки файла, и он не мешает механизму ResourceLoader, самое главное, что он будет добавлен ко всем загрузчикам в среде spring. Нам просто нужно расширить класс ProtocolResolver и добавить его в ResourceLoader, где это необходимо, и тогда наш ProtocolResolver всегда будет выполняться при загрузке файла свойств.

код

Ниже приведена конкретная реализация кода. Самое главное написать парсер конфигурационного файла. Заметки очень подробные, поэтому я не буду вдаваться в подробности.

1. XPathProtocolResolver.java

import org.springframework.core.io.ProtocolResolver;  
import org.springframework.core.io.Resource;  
import org.springframework.core.io.ResourceLoader;  
import org.springframework.util.ResourceUtils;  
  
import java.util.Collection;  
import java.util.LinkedHashSet;  
  
/** 
 * 用于加载jar外部的properties文件,扩展classpath : xjjdog
 * -- app.jar 
 * -- config/a.property   INSIDE order=3 
 * -- a.property          INSIDE order=4 
 * -- config/a.property       OUTSIDE order=1 
 * -- a.property              OUTSIDE order=2 
 * <p> 
 * 例如: 
 * 1、@PropertySource("::a.property") 
 * 查找路径为:./config/a.property,./a.property,如果找不到则返回null,路径相对于app.jar 
 * 2、@PropertySource("::x/a.property") 
 * 查找路径为:./config/x/a.property,./x/a.property,路径相对于app.jar 
 * 3、@PropertySource("*:a.property") 
 * 查找路径为:./config/a.property,./a.property,CLASSPATH:/config/a.property,CLASSPATH:/a.property 
 * 4、@PropertySource("*:x/a.property") 
 * 查找路径为:./config/x/a.property,./x/a.property,CLASSPATH:/config/x/a.property,CLASSPATH:/x/a.property 
 * <p> 
 * 如果指定了customConfigPath,上述路径中的/config则会被替换 
 * 
 * @author xjjdog 
 **/  
public class XPathProtocolResolver implements ProtocolResolver {  
  
    /** 
     * 查找OUTSIDE的配置路径,如果找不到,则返回null 
     */  
    private static final String X_PATH_OUTSIDE_PREFIX = "::";  
  
    /** 
     * 查找OUTSIDE 和inside,其中inside将会转换为CLASS_PATH 
     */  
    private static final String X_PATH_GLOBAL_PREFIX = "*:";  
  
    private String customConfigPath;  
  
    public XPathProtocolResolver(String configPath) {  
        this.customConfigPath = configPath;  
    }  
  
    @Override  
    public Resource resolve(String location, ResourceLoader resourceLoader) {  
        if (!location.startsWith(X_PATH_OUTSIDE_PREFIX) && !location.startsWith(X_PATH_GLOBAL_PREFIX)) {  
            return null;  
        }  
  
        String real = path(location);  
  
        Collection<String> fileLocations = searchLocationsForFile(real);  
        for (String path : fileLocations) {  
            Resource resource = resourceLoader.getResource(path);  
            if (resource != null && resource.exists()) {  
                return resource;  
            }  
        }  
        boolean global = location.startsWith(X_PATH_GLOBAL_PREFIX);  
        if (!global) {  
            return null;  
        }  
  
        Collection<String> classpathLocations = searchLocationsForClasspath(real);  
        for (String path : classpathLocations) {  
            Resource resource = resourceLoader.getResource(path);  
            if (resource != null && resource.exists()) {  
                return resource;  
            }  
        }  
        return resourceLoader.getResource(real);  
    }  
  
    private Collection<String> searchLocationsForFile(String location) {  
        Collection<String> locations = new LinkedHashSet<>();  
        String _location = shaping(location);  
        if (customConfigPath != null) {  
            String prefix = ResourceUtils.FILE_URL_PREFIX + customConfigPath;  
            if (!customConfigPath.endsWith("/")) {  
                locations.add(prefix + "/" + _location);  
            } else {  
                locations.add(prefix + _location);  
            }  
        } else {  
            locations.add(ResourceUtils.FILE_URL_PREFIX + "./config/" + _location);  
        }  
        locations.add(ResourceUtils.FILE_URL_PREFIX + "./" + _location);  
        return locations;  
    }  
  
    private Collection<String> searchLocationsForClasspath(String location) {  
        Collection<String> locations = new LinkedHashSet<>();  
        String _location = shaping(location);  
        if (customConfigPath != null) {  
            String prefix = ResourceUtils.CLASSPATH_URL_PREFIX + customConfigPath;  
            if (!customConfigPath.endsWith("/")) {  
                locations.add(prefix + "/" + _location);  
            } else {  
                locations.add(prefix + _location);  
            }  
        } else {  
            locations.add(ResourceUtils.CLASSPATH_URL_PREFIX + "/config/" + _location);  
        }  
  
        locations.add(ResourceUtils.CLASSPATH_URL_PREFIX + "/" + _location);  
        return locations;  
    }  
  
    private String shaping(String location) {  
        if (location.startsWith("./")) {  
            return location.substring(2);  
        }  
        if (location.startsWith("/")) {  
            return location.substring(1);  
        }  
        return location;  
    }  
  
    /** 
     * remove protocol 
     * 
     * @param location 
     * @return 
     */  
    private String path(String location) {  
        return location.substring(2);  
    }  
}  

2. ResourceLoaderPostProcessor.java

import org.springframework.context.ApplicationContextInitializer;  
import org.springframework.context.ConfigurableApplicationContext;  
import org.springframework.core.Ordered;  
import org.springframework.core.env.Environment;  
  
/** 
 * @author xjjdog 
 * 调整优化环境变量,对于boot框架会默认覆盖一些环境变量,此时我们需要在processor中执行 
 * 我们不再需要使用单独的yml文件来解决此问题。原则: 
 * 1)所有设置为系统属性的,初衷为"对系统管理员可见"、"对外部接入组件可见"(比如starter或者日志组件等) 
 * 2)对设置为lastSource,表示"当用户没有通过yml"配置选项时的默认值--担保策略。 
 **/  
public class ResourceLoaderPostProcessor implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {  
  
    @Override  
    public void initialize(ConfigurableApplicationContext applicationContext) {  
        Environment environment = applicationContext.getEnvironment();  
        String configPath = environment.getProperty("CONF_PATH");  
        if (configPath == null) {  
            configPath = environment.getProperty("config.path");  
        }  
        applicationContext.addProtocolResolver(new XPathProtocolResolver(configPath));  
    }  
  
    @Override  
    public int getOrder() {  
        return HIGHEST_PRECEDENCE + 100;  
    }  
}  

Плюс пружина. Вопросы, мы делаем все больше и больше, как стартер вверх. Да, то есть делать.

3. Spring.Factory

org.springframework.context.ApplicationContextInitializer=\  
com.github.xjjdog.commons.spring.io.ResourceLoaderPostProcessor  

PropertyConfiguration.java (Springboot Окружающая среда, свойства погрузчика)

@Configuration  
@PropertySources(  
    {  
            @PropertySource("*:login.properties"),  
            @PropertySource("*:ldap.properties")  
    }  
)  
public class PropertyConfiguration {  
   
    @Bean  
    @ConfigurationProperties(prefix = "login")  
    public LoginProperties loginProperties() {  
        return new LoginProperties();  
    }  
   
    @Bean  
    @ConfigurationProperties(prefix = "ldap")  
    public LdapProperties ldapProperties() {  
        return new LdapProperties();  
    }  
}  

Таким образом, наш пользовательский загрузчик готов. Мы также добавляем компоненты SpringBoot, добавляя новые функции.

End

SpringBoot может указать разные среды, установив «spring.profiles.active», но требования всегда изменчивы. Например, требования к конфигурации в этой статье могут быть болезненным соглашением компании.

SpringBoot предоставляет множество расширений для поддержки этих пользовательских операций, что также является прелестью. Нет ничего, что нельзя было бы решить, разработав стартер Spring Boot.

Об авторе:Мисс сестра вкус(xjjdog), публичная учетная запись, которая не позволяет программистам идти в обход. Сосредоточьтесь на инфраструктуре и Linux. Десять лет архитектуры, десятки миллиардов ежедневного трафика, обсуждение с вами мира высокой параллелизма, дающие вам другой вкус. Мой личный WeChat xjjdog0, добро пожаловать в друзья для дальнейшего общения.​