SpringBoot之属性配置

SpringBoot 配置

一、属性配置方式

新建获取属性值的启动加载器,用来验证设置的属性值:

@Component
public class GetAttributesApplicationRunner implements ApplicationRunner, EnvironmentAware {

    private Environment environment;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(environment.getProperty("ConfigurationMethod"));
    }
    
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }    
}

1.1 硬编码配置

public static void main(String[] args) {
    // SpringApplication.run(SpringbootApplication.class, args);
    Properties properties = new Properties();
    properties.setProperty("ConfigurationMethod","硬编码");
    SpringApplication springApplication = new SpringApplication(SpringbootApplication.class);
    springApplication.setDefaultProperties(properties);
    springApplication.run();
}

启动项目,查看控制台输出:

硬编码

1.2 @PropertySource 注解绑定配置

resources 目录下新建 default.properties 文件:

ConfigurationMethod=PropertySource

在启动类上新增 @PropertySource 注解:

@SpringBootApplication
@PropertySource("default.properties")
public class SpringbootApplication {
    ......
}        

启动项目,查看控制台输出:

PropertySource

可以看到硬编码设置的值被覆盖了,证明 @PropertySource 优先级比硬编码形式高

1.3 application.yml 配置

resources 目录下新建 application.yml 文件:

ConfigurationMethod: application.yml

启动项目,查看控制台输出:

application.yml

1.4 application.properties 配置

resources 目录下新建 application-dev.properties 文件:

ConfigurationMethod=application.properties

启动项目,查看控制台输出:

application.properties

1.5 application-{profile}.properties 配置

resources 目录下新建 application-dev.properties 文件:

ConfigurationMethod=application-{profile}.properties

application-dev.properties 文件中启用:

spring.profiles.active=dev

启动项目,查看控制台输出:

application-{profile}.properties

二、Aware 介绍

在上面的 GetAttributesApplicationRunner 启动加载器中,我们为了获取 Environment 对象,实现了 EnvironmentAware 接口,我们先来了解下 Aware 是什么?

2.1 Aware 是什么?

Aware 是一个具有标识作用的超级接口,实现该接口的 bean 是具有被 spring 容器通知的能力的,而被通知的方式就是通过回调。也就是说:直接或间接实现了这个接口的类,都具有被 spring容器通知的能力。

2.2 常见 Aware

名称 功能
BeanNameAware 获取容器中 bean 名称
BeanClassLoaderAware 获得类加载器
BeanFactoryAware 获得 bean 创建工厂
EnviromentAware 获得环境变量
EnvironmentValueResolverAware 获取 spring 容器加载的 properties文件 属性值
ResourceLoaderAware 获得资源加载器
ApplicationEventPublisherAware 获得应用程序发布器
MessageSourceAware 获得文本信息
ApplicationContextAware 获得当前应用上下文

三、environment 原理

进入 SpringApplication.run 方法:

public ConfigurableApplicationContext run(String... args) {
    ......
    ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
    ......
}

进入到 prepareEnvironment 方法:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // 创建和配置环境
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

3.1 getOrCreateEnvironment

进入getOrCreateEnvironment方法,当前会实例化 StandardServletEnvironment:

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    switch (this.webApplicationType) {
    case SERVLET:
        return new StandardServletEnvironment();
    case REACTIVE:
        return new StandardReactiveWebEnvironment();
    default:
        return new StandardEnvironment();
    }
}

进入父类 AbstractEnvironment 的构造函数:

public AbstractEnvironment() {
    customizePropertySources(this.propertySources);
}

调用子类的 customizePropertySources 方法

protected void customizePropertySources(MutablePropertySources propertySources) {
    // 增加servletConfigInitParams 属性源和 servletContextInitParams 属性源
    propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
    propertySources.addLast(new StubPropertySource("servletContextInitParams"));
    // 添加 jndi 属性源
    if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
        propertySources.addLast(new JndiPropertySource("jndiProperties"));
    }
    
    super.customizePropertySources(propertySources);
}

然后调用父类 StandardEnvironment.customizePropertySources:

/** 系统环境属性源,name: {@value}. */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
/** JVM系统属性属性源 name: {@value}. */
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

@Override
protected void customizePropertySources(MutablePropertySources propertySources) {    
    propertySources.addLast(
            new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));        
    propertySources.addLast(
            new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

3.2 configureEnvironment

private boolean addConversionService = true;

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {   
    if (this.addConversionService) {
        // 添加转换服务对象
        ConversionService conversionService = ApplicationConversionService.getSharedInstance();
        environment.setConversionService((ConfigurableConversionService) conversionService);
    }
    // 配置属性源
    configurePropertySources(environment, args);
    configureProfiles(environment, args);
}

进入 configurePropertySources 方法

private Map<String, Object> defaultProperties;
private boolean addCommandLineProperties = true;

protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
    MutablePropertySources sources = environment.getPropertySources();
    // 添加 defaultProperties 默认属性源
    if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
        sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
    }    
    if (this.addCommandLineProperties && args.length > 0) {
        String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
        if (sources.contains(name)) {
            PropertySource<?> source = sources.get(name);
            CompositePropertySource composite = new CompositePropertySource(name);
            composite.addPropertySource(
                    new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
            composite.addPropertySource(source);
            sources.replace(name, composite);
        }
        else {
            // 添加命令属性源
            sources.addFirst(new SimpleCommandLinePropertySource(args));
        }
    }
}

进入 SimpleCommandLinePropertySource 方法:

public SimpleCommandLinePropertySource(String... args) {
    super(new SimpleCommandLineArgsParser().parse(args));
}

public CommandLineArgs parse(String... args) {
    CommandLineArgs commandLineArgs = new CommandLineArgs();
    for (String arg : args) {
        if (arg.startsWith("--")) {
            String optionText = arg.substring(2);
            String optionName;
            String optionValue = null;
            int indexOfEqualsSign = optionText.indexOf('=');
            if (indexOfEqualsSign > -1) {
                optionName = optionText.substring(0, indexOfEqualsSign);
                optionValue = optionText.substring(indexOfEqualsSign + 1);
            }
            else {
                optionName = optionText;
            }
            if (optionName.isEmpty()) {
                throw new IllegalArgumentException("Invalid argument syntax: " + arg);
            }
            commandLineArgs.addOptionArg(optionName, optionValue);
        }
        else {
            commandLineArgs.addNonOptionArg(arg);
        }
    }
    return commandLineArgs;
}   
    

可以看到主要通过 parse 方法进行解析命令,解析命令的时候,判断是否以 “--” 开头。

回到 configureProfiles 方法,这里主要是配置 profile 信息,关于这块我们之后还会讲解到:

// 获取 profile 信息并配置
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
    Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
    profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
    environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}

3.3 ConfigurationPropertySources.attach(environment);

// 增加 configurationProperties 属性源
public static void attach(Environment environment) {
    Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
    MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
    PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
    if (attached != null && attached.getSource() != sources) {
        sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
        attached = null;
    }
    if (attached == null) {
        sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
                new SpringConfigurationPropertySources(sources)));
    }
}

3.4 listeners.environmentPrepared(environment)

void environmentPrepared(ConfigurableEnvironment environment) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.environmentPrepared(environment);
    }
}

发布 environmentPrepared 事件, 进入 environmentPrepared 方法:

@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
    this.initialMulticaster
            .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}

调用广播器广播事件,这一块可以看之前的 监听器

3.5 bindToSpringApplication

// 将属性绑定到 SpringApplication
protected void bindToSpringApplication(ConfigurableEnvironment environment) {
    try {
        Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
    }
    catch (Exception ex) {
        throw new IllegalStateException("Cannot bind to SpringApplication", ex);
    }
}

3.6 new EnvironmentConverter

if (!this.isCustomEnvironment) {
    environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
            deduceEnvironmentClass());
}

判断 convertEnvironmentIfNecessary 当前环境是否是指定类型,是的话就返回。否的话再 new 一个环境,把值赋值到新的实例化的环境中

猜你喜欢

转载自www.cnblogs.com/markLogZhu/p/12515654.html