Springboot默认加载application.yml原理以及扩展

Springboot默认加载application.yml原理以及扩展

SpringApplication.run(...)默认会加载classpath下的application.yml或application.properties配置文件。公司要求搭建的框架默认加载一套默认的配置文件demo.properties,让开发人员实现“零”配置开发,但是前提如果开发人员在application.yml或application.properties文件中自定义配置,则会“覆盖”默认的demo.properties文件,按照Springboot外部化配置的特性(优先使用先加载的),只要demo.properties配置在application.yml或application.properties 配置之后加载到environment中即可。

一、SpirngApplication.run(...)源码分析

通过源码分析,得知Springboot加载配置文件,是利用Spring的事件机制,通过EventPublishingRunListener取发布准备资源事件ApplicationEnvironmentPreparedEvent,被ConfigFileApplicationListener监听到,从而来实现资源的加载

具体源码如下:

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        //这里是扩展的关键点
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            //这里是加载资源的关键
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            ....
    }
        
    //从方法名称来看就是准备environment的即配置信息
    private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        
        //这里默认EventPublishingRunListener发布ApplicationEnvironmentPreparedEvent事件
        //让监听器ConfigFileApplicationListener加载配置文件
        //这个listeners就是我们扩展的地方
        listeners.environmentPrepared(environment);
        bindToSpringApplication(environment);
        if (this.webApplicationType == WebApplicationType.NONE) {
            environment = new EnvironmentConverter(getClassLoader())
                    .convertToStandardEnvironmentIfNecessary(environment);
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

SpirngApplication.run(...)方法中有个重要的扩展点方法getRunListeners(args);

private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
                SpringApplicationRunListener.class, types, this, args));
    }

    //可扩展的关键点SpringFactoriesLoader
    //SpringFactoriesLoader会去加载META-INF/spring.factories文件,并根据
    //type过滤出符合要求的类
    //比如这里的type对应的是:SpringApplicationRunListener
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // Use names and ensure unique to protect against duplicates
        Set<String> names = new LinkedHashSet<>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

Springboot默认提供的META-INF/spring.factories,这里就是我们可以扩展的地方

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

至此资源加载的大概流程就分析完了,下面是我们的扩展

二、扩展——自定义加载配置文件(demo.properties)

通过上述源码分析得知:只需要在项目中添加META-INF/spring.factories,并配置SpringApplicationRunListener为我们自定义的来即可

1、在项目中的resources下创建META-INF/spring.factories

org.springframework.boot.SpringApplicationRunListener=\
com.demo.module.ApplicatonEnvironDemoListener

2、ApplicatonEnvironDemoListener的代码

package com.chyjr.hyboot.demo.module;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.SpringApplicationRunListener;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.core.PriorityOrdered;
    import org.springframework.core.env.ConfigurableEnvironment;
    import org.springframework.core.env.MutablePropertySources;
    import org.springframework.core.env.PropertiesPropertySource;
    import org.springframework.core.env.PropertySource;
    import java.io.IOException;
    import java.util.Properties;
    
    public class ApplicatonEnvironDemoListener implements 
        SpringApplicationRunListener,PriorityOrdered {
    
        private SpringApplication application;
    
        private String[] args;
        /**
         * 通过反射创建该实例对象的,构造方法中的参数要加上如下参数
         */
        public ApplicatonEnvironDemoListener(SpringApplication application,String[] args){
            this.application = application;
            this.args = args;
        }
        
        /**
         * 在准备环境之间调用
         * SpringApplication#run -> listeners.starting();
         */
        @Override
        public void starting() {
            System.out.println("starting-----");
        }
    
        @Override
        public void environmentPrepared(ConfigurableEnvironment environment) {
            Properties properties = new Properties();
            try {
                 //demo.properties就是我们自定义的配置文件,extension是自定义目录
                properties.load(this.getClass().getClassLoader().
                                getResourceAsStream("extension/demo.properties"));
                PropertySource propertySource =new 
                    PropertiesPropertySource("demo",properties);
                 //PropertySource是资源加载的核心
                MutablePropertySources propertySources = environment.getPropertySources();
                 //这里添加最后
                propertySources.addLast(propertySource);
            } catch (IOException e) {
                e.printStackTrace();
            }
    
    
        }


        //省略其他方法
        ...
        
        /**
         * 这里可以设置该配置文件加载的顺序,在application.yml之前还是之后
         * EventPublishingRunListener#getOrder方法返回 “0”,按照需求这里我们这是比0大,
         * 即在application.yml之后加载,这样在application.yml配置时,可以“覆盖”my.yml
         * 这里用“覆盖”可能不合适,意思到了就好
         */
        @Override
        public int getOrder() {
            return 1;
        }
    }