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));
}
进入 listener.environmentPrepared(environment); 里面调用广播器广播事件
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
进入广播事件方法:
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
}
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
// 获得对该事件感兴趣的监听器,遍历监听器,调用invokeListener方法。
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
进入invokeListener 方法:
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
doInvokeListener(listener, event);
}
catch (Throwable err) {
errorHandler.handleError(err);
}
}
else {
doInvokeListener(listener, event);
}
}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for
// -> let's suppress the exception and just log a debug message.
Log logger = LogFactory.getLog(getClass());
if (logger.isTraceEnabled()) {
logger.trace("Non-matching event type for listener: " + listener, ex);
}
}
else {
throw ex;
}
}
}
进入 listener.onApplicationEvent(event);方法, 它有多个实现类,我们这里以 ConfigFileApplicationListener 为例:
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
进入方法 loadPostProcessors :
List<EnvironmentPostProcessor> loadPostProcessors() {
return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());
}
里面是获取属性文件中对该监听器的实现类有哪些,配置文件内容如下:
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor
然后依次遍历这几个EnvironmentPostProcessor 的 postProcessEnvironment 方法:
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
}
// 添加属性值
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
// 添加application-profile.(properties|yml)属性集
void load() {
FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
(defaultProperties) -> {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
initializeProfiles();
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (isDefaultProfile(profile)) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
applyActiveProfiles(defaultProperties);
});
}
load 这块我们之后会再详细讲解 SpringBott之Profile的理解和使用。
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 一个环境,把值赋值到新的实例化的环境中