spring-boot-源码阅读-如何加载环境变量(二)

说明

在Spring Boot-源码阅读-启动主流程(一) 8-11处触发了环境变量的加载

时序图

 

 

<1>

org.springframework.boot.SpringApplication#prepareEnvironment

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                                                       DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
        // <2>内部封装的PropertySource集合 封装各个环境变量 如默认会初始化系统环境变量 和jvm环境变量
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        //将系统变量设置到environment
        configureEnvironment(environment, applicationArguments.getSourceArgs());      
        ConfigurationPropertySources.attach(environment);
        //<4>发送listener ApplicationEnviromentPreparedEvent 对应消费将触发我们application.yml配置文件加载
        /**
         * # Run Listeners
         * org.springframework.boot.SpringApplicationRunListener=\
         * org.springframework.boot.context.event.EventPublishingRunListener 默认
         */
        listeners.environmentPrepared(bootstrapContext, environment);
        DefaultPropertiesPropertySource.moveToEnd(environment);
        Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
                "Environment prefix cannot be set via properties.");
        bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {
            environment = convertEnvironment(environment);
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

<2>

private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        switch (this.webApplicationType) {
            case SERVLET:
                 //<3>默认返回此 父类会触发初始化默认的PropertySource
                return new ApplicationServletEnvironment();
            case REACTIVE:
                return new ApplicationReactiveWebEnvironment();
            default:
                return new ApplicationEnvironment();
        }
    }

<3>

继承链

 

 

 org.springframework.core.env.AbstractEnvironment

    public AbstractEnvironment() {
        this(new MutablePropertySources());
    }
    protected AbstractEnvironment(MutablePropertySources propertySources) {
        this.propertySources = propertySources;
//当我调用get 就是委托给Resolver 遍历调用propertSource获取对应的值。比如我们可以实现自定义key解析 get("${age}")内部委托给propertySroce就传age
this.propertyResolver = createPropertyResolver(propertySources); //<3-1>被子类重写 StandardServletEnvironment customizePropertySources(propertySources); }

<3-1>

org.springframework.web.context.support.StandardServletEnvironment#customizePropertySources

 public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
    public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
    protected void customizePropertySources(MutablePropertySources propertySources) {
        //新增2个servlet相关的propertySource 但是是空的
        propertySources.addLast(new PropertySource.StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
        propertySources.addLast(new PropertySource.StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
        if (jndiPresent && JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
            propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
        }
        //<3-2>调用父类StandardEnvironment 的自定义方法
        super.customizePropertySources(propertySources);
    }

<3-2>

org.springframework.core.env.StandardEnvironment#customizePropertySources

 public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        //新增2各个System相关环境变量 并加载
        //内部调用System.getProperties() 获得map jvm参数相关环境变量
        propertySources.addLast(
                new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        //内部调用 (Map) System.getenv() 获得map 主要获取系统相关环境变量
        propertySources.addLast(
                new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }

<4>

  //EventPublishingRunListener构造函数
    public EventPublishingRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        //EventPublishingRunListener初始化的时候会初始化一个广播器,注册了我们ApplicationListener监听器 初始化处可以查看<点击跳转>
        for (ApplicationListener<?> listener : application.getListeners()) {
            this.initialMulticaster.addApplicationListener(listener);
        }
    }
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
                                    ConfigurableEnvironment environment) {
        //<5>发送ApplicationEnvironmentPreparedEvent事件 将由监听器org.springframework.boot.env.EnvironmentPostProcessorApplicationListener监听处理  在spring.boot由ConfigFileApplicationListener
        this.initialMulticaster.multicastEvent(
                new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
    }

<5>

我们主要关注org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor 这个就是如何加载application.yml

org.springframework.boot.env.EnvironmentPostProcessorApplicationListener#onApplicationEnvironmentPreparedEvent

    private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment environment = event.getEnvironment();
        SpringApplication application = event.getSpringApplication();
        //<6>这里也是获取spring.factories的EnvironmentPostProcessor实现类 这里也是我们加载自定义环境变量的扩展点
        /**
         * # Environment Post Processors
         * org.springframework.boot.env.EnvironmentPostProcessor=\
         * org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
         * org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor,\
         * org.springframework.boot.env.RandomValuePropertySourceEnvironmentPostProcessor,\ 针对于随机数可以自行百度 如@value(${random.int}),@value(${random.int[1,1024]}),
         * org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\ 针对json的配置如:-d {"age":12} @value(${age})
         * org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
         * org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor
         */
        for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),
                event.getBootstrapContext())) {
            postProcessor.postProcessEnvironment(environment, application);
        }
    }

<6>

org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor#postProcessEnvironment

 @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        postProcessEnvironment(environment, application.getResourceLoader(), application.getAdditionalProfiles());
    }
    void postProcessEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
                                Collection<String> additionalProfiles) {
        try {
            this.logger.trace("Post-processing environment to add config data");
            resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
            //委托给ConfigDataEnvironment 触发加载<7>初始化 <10>加载
            getConfigDataEnvironment(environment, resourceLoader, additionalProfiles).processAndApply();
        }
        catch (UseLegacyConfigProcessingException ex) {
            this.logger.debug(LogMessage.format("Switching to legacy config file processing [%s]",
                    ex.getConfigurationProperty()));
            configureAdditionalProfiles(environment, additionalProfiles);
            postProcessUsingLegacyApplicationListener(environment, resourceLoader);
        }
    }

<7>

org.springframework.boot.context.config.ConfigDataEnvironment#ConfigDataEnvironment

    ConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
                          ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles,
                          ConfigDataEnvironmentUpdateListener environmentUpdateListener) {
        Binder binder = Binder.get(environment);
        UseLegacyConfigProcessingException.throwIfRequested(binder);
        this.logFactory = logFactory;
        this.logger = logFactory.getLog(getClass());
        this.notFoundAction = binder.bind(ON_NOT_FOUND_PROPERTY, ConfigDataNotFoundAction.class)
                .orElse(ConfigDataNotFoundAction.FAIL);
        this.bootstrapContext = bootstrapContext;
        this.environment = environment;
        this.resolvers = createConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);
        this.additionalProfiles = additionalProfiles;
        this.environmentUpdateListener = (environmentUpdateListener != null) ? environmentUpdateListener
                : ConfigDataEnvironmentUpdateListener.NONE;
        this.loaders = new ConfigDataLoaders(logFactory, bootstrapContext, resourceLoader.getClassLoader());
        //<8>我的理解这个是解析器
        this.contributors = createContributors(binder);
    }

<8>

org.springframework.boot.context.config.ConfigDataEnvironment#createContributors

   private ConfigDataEnvironmentContributors createContributors(Binder binder) {
        this.logger.trace("Building config data environment contributors");
        MutablePropertySources propertySources = this.environment.getPropertySources();
        List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>(propertySources.size() + 10);
        PropertySource<?> defaultPropertySource = null;
        for (PropertySource<?> propertySource : propertySources) {
            if (DefaultPropertiesPropertySource.hasMatchingName(propertySource)) {
                defaultPropertySource = propertySource;
            }
            else {
                this.logger.trace(LogMessage.format("Creating wrapped config data contributor for '%s'",
                        propertySource.getName()));
                contributors.add(ConfigDataEnvironmentContributor.ofExisting(propertySource));
            }
        }
        //<9>主要是这里创建负责解析指定目录文件下配置文件的解析器
        contributors.addAll(getInitialImportContributors(binder));
        if (defaultPropertySource != null) {
            this.logger.trace("Creating wrapped config data contributor for default property source");
            contributors.add(ConfigDataEnvironmentContributor.ofExisting(defaultPropertySource));
        }
        return createContributors(contributors);
    }

<9>

org.springframework.boot.context.config.ConfigDataEnvironment#getInitialImportContributors

 static final ConfigDataLocation[] DEFAULT_SEARCH_LOCATIONS;
    static {
        List<ConfigDataLocation> locations = new ArrayList<>();
        locations.add(ConfigDataLocation.of("optional:classpath:/;optional:classpath:/config/"));
        locations.add(ConfigDataLocation.of("optional:file:./;optional:file:./config/;optional:file:./config/*/"));
        DEFAULT_SEARCH_LOCATIONS = locations.toArray(new ConfigDataLocation[0]);
    }

    private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Binder binder) {
        List<ConfigDataEnvironmentContributor> initialContributors = new ArrayList<>();
        addInitialImportContributors(initialContributors, bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS));
        addInitialImportContributors(initialContributors,
                bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS));
        //主要是这里新增了从哪些目录下查找我们的properties文件 指定定义还未加载
        addInitialImportContributors(initialContributors,
                bindLocations(binder, LOCATION_PROPERTY, DEFAULT_SEARCH_LOCATIONS));
        return initialContributors;
    }

<10>

org.springframework.boot.context.config.ConfigDataEnvironment#processAndApply

    void processAndApply() {
        ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers,
                this.loaders);
        registerBootstrapBinder(this.contributors, null, DENY_INACTIVE_BINDING);
        //这里先加载我们的application.yml文件
        ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);
        ConfigDataActivationContext activationContext = createActivationContext(
                contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));
        //根据application.yml配置的环境,加载对应的yml文件
        /**
         * 如
         * spring:
         *   profiles:
         *     active: dev,test
         */
        contributors = processWithoutProfiles(contributors, importer, activationContext);
        //解析出环境配置如 dev,test
        activationContext = withProfiles(contributors, activationContext);
        //进行对应环境的yml文件加载
        contributors = processWithProfiles(contributors, importer, activationContext);
        //转成PropertySource add到Environment
        applyToEnvironment(contributors, activationContext, importer.getLoadedLocations(),
    }

 applo扩展点:

com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer

如何实现解密

比如数据库配置配置的是密文

读取配置文件是委托PropertySourcesPropertyResolver  它支持解析动态表达式 如biz.age=${age}  内部是通过解析表达式重新在环境变量获取 我们就可以实现自己的环境变量解析器 biz.age=${myEncrypt.des(密文)}

package cn.datax.webfull.config.process;

import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import org.apache.commons.codec.binary.Hex;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;

/**
 * @Author liqiang
 * @Description 解密环境变量处理器
 * 扩展点:org.springframework.boot.context.config.ConfigFileApplicationListener#onApplicationEnvironmentPreparedEvent
 * @Date 2025/09/23/14:58
 */

public class DecryptEnvironmentPostProcessor implements EnvironmentPostProcessor {

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        environment.getPropertySources().addLast(new YxtEncryptPropertySource());
    }

    public static class YxtEncryptPropertySource extends
        PropertySource<YxtEncrypt> {

        private static final String ENCYPT_PROPERTY_SOURCE_NAME = "myEncrypt";
        private static final String PREFIX = "myEncrypt.";
        private static final String TYPE_DES = "des";

        YxtEncryptPropertySource() {
            super("myEncrypt", new YxtEncrypt());
        }

        public Object getProperty(String name) {
            if (!name.startsWith(PREFIX)) {
                return null;
            } else {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Generating encrypt property for '" + name + "'");
                }

                return this.decrypt(name.substring(PREFIX.length()));
            }
        }

        private String decrypt(String expr) {
            int idx = expr.indexOf("(");
            String type = expr.substring(0, idx);
            String value = expr.substring(idx + 1, expr.length() - 1);
            if ("des".equals(type)) {
                try {
                    return getSource().decrypt(value, (String) null);
                } catch (Exception e) {
                    throw new IllegalArgumentException("decrypt error! value is: " + value, e);
                }
            } else {
                return null;
            }
        }
    }

    private static class YxtEncrypt {

        static final String DEFAULT_KEY = "3333";
        private static Key convertSecretKey;

        private YxtEncrypt() {
        }

        private static Key generateSecret(String key) throws Exception {
            if (key == null) {
                key = System.getProperty("biz.config.encrypt.key", "3333");
            }

            byte[] bytesKey = key.getBytes();
            DESKeySpec desKeySpec = new DESKeySpec(bytesKey);
            SecretKeyFactory factory = SecretKeyFactory.getInstance("DES");
            return factory.generateSecret(desKeySpec);
        }

        //密钥加密demo
        public static void main(String[] args) throws Exception {
            String result = YxtEncrypt.encrypt("aa1353434", null);
            System.out.println(result);
        }

        private static String encrypt(String data, String key) throws Exception {
            Key secretKey = convertSecretKey;
            if (key != null && !key.isEmpty()) {
                secretKey = generateSecret(key);
            }

            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
            cipher.init(1, secretKey);
            byte[] enData = Base64.getEncoder().encode(data.getBytes(StandardCharsets.UTF_8));
            byte[] result = cipher.doFinal(enData);
            return Hex.encodeHexString(result);
        }

        private static String decrypt(String data, String key) throws Exception {
            Key secretKey = convertSecretKey;
            if (key != null && !key.isEmpty()) {
                secretKey = generateSecret(key);
            }

            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
            cipher.init(2, secretKey);
            byte[] hdata = Hex.decodeHex(data.toCharArray());
            byte[] result = cipher.doFinal(hdata);
            byte[] decode = Base64.getDecoder().decode(result);
            return new String(decode, StandardCharsets.UTF_8);
        }

        static {
            try {
                convertSecretKey = generateSecret((String) null);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }
}

image增加配置

org.springframework.boot.env.EnvironmentPostProcessor=\
${包名字}.DecryptEnvironmentPostProcessor

  

properties 配置

password: ${myEncrypt.des(30909f5adcccb9f70f69334bb3edabb3d809e27dbd7f883e)}

 

posted @ 2022-02-26 14:38  意犹未尽  阅读(1268)  评论(1)    收藏  举报