Profile
profile 定义了一组有逻辑关系的 bean定义,当且仅当 profile 被激活的时候,才会注入到容器当中。也就是说,程序只需要构建一次,就可以部署到多个环境当中,而不用修改所有配置,指定哪一个profile需要被激活即可
源码分析(细节比较多,得捋清楚)
主要是通过ConfigFileApplicationListener的内部类Loader进行加载
1. 进入 onApplicationEnvironmentPreparedEvent(event)方法 -- ConfigFileApplicationListener类
该方法的作用是遍历所有的后处理器,依次调用后处理器,执行相应的方法
我们需要关注的是 ConfigFileApplicationListener类 这个后处理器执行的操作
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
// 遍历所有的后处理器,只要是 EnvironmentPostProcessor类型,就添加到 postProcessors列表当中
List<EnvironmentPostProcessor> postProcessors = this.loadPostProcessors();
// 把本身也添加到 postProcessors列表当中
// 因为 ConfigFileApplicationListener类 实现了 EnvironmentPostProcessor接口
postProcessors.add(this);
// 对 postProcessors列表中的后处理器按Order值进行排序
AnnotationAwareOrderComparator.sort(postProcessors);
Iterator var3 = postProcessors.iterator();
while(var3.hasNext()) {
// 获取后处理器
EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var3.next();
// 执行后处理器的 postProcessEnvironment方法
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
2. 进入 postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()) -- ConfigFileApplicationListener类
该方法的作用是调用自定义的 addPropertySources方法向环境中添加属性源
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
// 向环境中添加属性源
this.addPropertySources(environment, application.getResourceLoader());
}
3. 进入 addPropertySources(environment, application.getResourceLoader())方法
该方法的作用是向环境中添加属性源,主要是RandomValuePropertySource 和 application-{profile}.(properties|yml)配置文件中的属性源
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
// 添加 RandomValuePropertySource(随机类型)属性源
RandomValuePropertySource.addToEnvironment(environment);
// 从 application-{profile}.(properties|yml)配置文件中加载属性源
(new ConfigFileApplicationListener.Loader(environment, resourceLoader)).load();
}
== ConfigFileApplicationListener的内部类Loader 构造方法==
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
this.logger = ConfigFileApplicationListener.this.logger;
this.loadDocumentsCache = new HashMap();
this.environment = environment;
// 初始化占位符解析器
this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
// 一般情况下,使用默认的类加载器
this.resourceLoader = (ResourceLoader)(resourceLoader != null ? resourceLoader : new DefaultResourceLoader());
// 使用 SpringFactoriesLoader类加载所有jar包下的 META-INF目录的 spring.factories文件的 PropertySourceLoader类数组
// 最终会得到两个实现类,一个是PropertiesPropertySourceLoader类,一个是YamlPropertySourceLoader类
// PropertiesPropertySourceLoader类 支持properties和xml文件,解析成Properties,然后封装成PropertiesPropertySource
// YamlPropertySourceLoader类 支持yml和yaml文件,解析成Map,然后封装成MapPropertySource
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, this.getClass().getClassLoader());
}
4. 进入 load()方法
该方法的作用是加载所有可能的profile
void load() {
FilteredPropertySource.apply(this.environment, "defaultProperties", ConfigFileApplicationListener.LOAD_FILTERED_PROPERTY, (defaultProperties) -> {
// 初始化集合
// 未处理的数据集合
this.profiles = new LinkedList();
// 已处理的数据集合
this.processedProfiles = new LinkedList();
// 被 spring.profiles.active指定的集合
this.activatedProfiles = false;
this.loaded = new LinkedHashMap();
// 4.1 加载存在已经激活的 profiles
this.initializeProfiles();
// 遍历profiles
while(!this.profiles.isEmpty()) {
// 从 profiles集合中获取 profile
ConfigFileApplicationListener.Profile profile = (ConfigFileApplicationListener.Profile)this.profiles.poll();
// 如果 profile不是默认指定的 profile,且不为null
// 其中,isDefaultProfile方法体定义 profile != null && !profile.isDefaultProfile()
if (this.isDefaultProfile(profile)) {
// 添加 profiles资源到环境中
this.addProfileToEnvironment(profile.getName());
}
// 4.2 确定搜索范围,获取对应的配置文件名,并使用相应加载器加载
this.load(profile, this::getPositiveProfileFilter, this.addToLoaded(MutablePropertySources::addLast, false));
// 将处理完的 profile添加到 processedProfiles列表当中,表示已经处理完成
this.processedProfiles.add(profile);
}
// 采用消极策略的DocumentFilterFactory对象进行处理
this.load((ConfigFileApplicationListener.Profile)null, this::getNegativeProfileFilter, this.addToLoaded(MutablePropertySources::addFirst, true));
// 顺序颠倒下,保证了优先add的是带profile的,而默认的profile是优先级最低
this.addLoadedPropertySources();
// 更新 activeProfiles列表
this.applyActiveProfiles(defaultProperties);
});
}
4.1 进入 initializeProfiles( )方法,更新 profiles集
4.1 进入 this.initializeProfiles()方法
该方法的作用是加载存在已经激活的 profiles
private void initializeProfiles() {
// 默认配置文件为null,以便优先被处理,具有最小优先级
this.profiles.add((Object)null);
// 4.1.1 判断当前环境是否配置 spring.profiles.active属性
// 也就是遍历环境中所有的属性源集合,查看是否有名称为 spring.profiles.active的属性源
// 比如说,在命令行参数当中添加 --spring.profiles.active=dev,
// 或配置系统属性 System.setProperty("spring.profiles.active","dev"),那么就会去创建一个dev的 profile
// 如果没有,就返回空集;如果有,就添加到 activatedViaProperty集中
Set<ConfigFileApplicationListener.Profile> activatedViaProperty = this.getProfilesFromProperty("spring.profiles.active");
// 4.1.1 判断当前环境是否配置 spring.profiles.include属性
// 也就是遍历环境中所有的属性源集合,查看是否有名称为 spring.profiles.include的属性源
// 如果没有,就返回空集;如果有,就添加到 includedViaProperty集中
Set<ConfigFileApplicationListener.Profile> includedViaProperty = this.getProfilesFromProperty("spring.profiles.include");
// 4.1.2 如果没有特别指定的话,就是 application.properties 和 application-default.properties配置
// 如果特别指定的话,就是 application.properties 和 已经激活的 profile
// 返回该环境其他显式激活的配置文件集,已经添加过了,就不会再重复添加(除了 spring.profiles.active 和 spring.profiles.include指定的配置以外 )
List<ConfigFileApplicationListener.Profile> otherActiveProfiles = this.getOtherActiveProfiles(activatedViaProperty, includedViaProperty);
// 将 otherActiveProfiles集添加到 profiles集当中
this.profiles.addAll(otherActiveProfiles);
// 将 includedViaProperty集添加到 profiles集当中
this.profiles.addAll(includedViaProperty);
// 4.1.3
// 将 activatedViaProperty集添加到 profiles集中,以确保 spring.profiles.active指定的值生效
// 同时移除默认配置
this.addActiveProfiles(activatedViaProperty);
// 如果 profiles集仍然为null,即没有指定,就会创建默认的profile
!-- 这就说明了为什么 spring.profiles.active指定的配置文件会和 default配置文件互斥的原因 -- !
if (this.profiles.size() == 1) {
// 如果没有用 spring.profiles.default指定profile值,那么默认返回default
String[] var4 = this.environment.getDefaultProfiles();
int var5 = var4.length;
// 遍历返回的默认值,依次添加到profiles集当中
for(int var6 = 0; var6 < var5; ++var6) {
String defaultProfileName = var4[var6];
ConfigFileApplicationListener.Profile defaultProfile = new ConfigFileApplicationListener.Profile(defaultProfileName, true);
this.profiles.add(defaultProfile);
}
}
}
补充(initializeProfiles()方法里面的子方法阐述)
4.1.1
== getProfilesFromProperty("spring.profiles.active")方法 ==
该方法的作用是遍历环境中的已加载的所有属性源,以获取其中 spring.profiles.active指定的值,作为profile值
private Set<ConfigFileApplicationListener.Profile> getProfilesFromProperty(String profilesProperty) {
// 判断当前环境的已加载的所有属性源是否包含名为 spring.profiles.active指定的属性源
// 相比如通过命令行参数或者系统环境指定的 spring.profiles.active对应的值都会被扫描到
if (!this.environment.containsProperty(profilesProperty)) {
return Collections.emptySet(); // 一般情况下,返回空集
} else {
// 通过当前environment对象实例化Binder对象
Binder binder = Binder.get(this.environment);
// 遍历环境中所有的属性源集合,查看是否有名称为 profilesProperty的属性源
Set<ConfigFileApplicationListener.Profile> profiles = this.getProfiles(binder, profilesProperty);
return new LinkedHashSet(profiles);
}
}
对Binder类进行补充说明
Binder类是一个容器对象,其中绑定来自当前Environment对象的一个或多个ConfigurationPropertySources属性
下面这个方法就看出,通过传入的environment对象,去获取所有ConfigurationPropertySource类型的属性源
public static Binder get(Environment environment, BindHandler defaultBindHandler) {
Iterable<ConfigurationPropertySource> sources = ConfigurationPropertySources.get(environment);
PropertySourcesPlaceholdersResolver placeholdersResolver = new PropertySourcesPlaceholdersResolver(environment);
return new Binder(sources, placeholdersResolver, (ConversionService)null, (Consumer)null, defaultBindHandler);
}
4.1.2
== getOtherActiveProfiles(activatedViaProperty, includedViaProperty)方法 ==
该方法的作用是获取其他显式配置激活的profile,之前添加的,不再重复添加
private List<ConfigFileApplicationListener.Profile> getOtherActiveProfiles(Set<ConfigFileApplicationListener.Profile> activatedViaProperty, Set<ConfigFileApplicationListener.Profile> includedViaProperty) {
// 从 activeProfiles集获取之前注册的激活profile
return (List)Arrays.stream(this.environment.getActiveProfiles()).map(ConfigFileApplicationListener.Profile::new).filter((profile) -> {
// 其中,filter()方法保存当前满足过滤条件的元素
return !activatedViaProperty.contains(profile) && !includedViaProperty.contains(profile);
}).collect(Collectors.toList());
}
4.1.3
== addActiveProfiles(activatedViaProperty)方法 ==
该方法的作用是将 activatedViaProperty集添加到 profiles队列中
确保 spring.profiles.active指定的 profile会生效,也就是迭代的激活的 profiles会覆写默认的配置(队列)
void addActiveProfiles(Set<ConfigFileApplicationListener.Profile> profiles) {
if (!profiles.isEmpty()) {
// 之前已经将 spring.profiles.active指定的profile添加进去,就不会再次添加
// 判断激活标志是否为true
// 也就是判断 spring.profiles.active指定的profile是否添加到 profiles队列当中
if (this.activatedProfiles) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Profiles already activated, '" + profiles + "' will not be applied");
}
// 如果之前没有添加,就将 spring.profiles.active指定的profile添加到 profiles队列当中
// spring.profiles.active指定的 profile也就是 activatedViaProperty集
} else {
this.profiles.addAll(profiles);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Activated activeProfiles " + StringUtils.collectionToCommaDelimitedString(profiles));
}
this.activatedProfiles = true; // 将激活标志置为false
// 移除默认指定的 profile
// 如果此 profile不为null,并且是 spring.profiles.default指定的 profile
this.removeUnprocessedDefaultProfiles();
}
}
}
4.2 进入 load(ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer)方法
4.2 load(profile, this::getPositiveProfileFilter, this.addToLoaded(MutablePropertySources::addLast, false))
该方法的作用是确定搜索范围,获取对应的配置文件名,并使用相应的加载器加载
private void load(ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
// 4.2.1 获取加载配置文件的路径
this.getSearchLocations().forEach((location) -> {
// 判断指定的搜索范围是否是文件夹
// 如果是文件夹,需要进一步搜索,找到相应的配置文件
// 如果不是文件夹,说明有可能一次性提供配置文件,直接去加载即可
boolean isFolder = location.endsWith("/");
// 4.2.2 如果是文件夹,需要调用 getSearchNames()方法进一步找到配置文件
// 如果没有用 spring.config.name指定配置文件的前缀,默认是返回"application"
Set<String> names = isFolder ? this.getSearchNames() : ConfigFileApplicationListener.NO_SEARCH_NAMES;
names.forEach((name) -> {
// 4.2.3 加载相应路径下的配置文件,一般是 {name}-{profile}.(properties|yml)
this.load(location, name, profile, filterFactory, consumer);
});
});
}
4.2 补充(load(profile, filterFactory, consumer)方法里面的子方法阐述)
4.2.1
## 检索路径 ##
private Set<String> getSearchLocations() {
该方法的作用是获取搜索范围,从以下三个角度
1. spring.config.location 指定的路径
2. spring.config.addition-location 指定的路径
3. 默认路径(file:./config/,file:./,classpath:/config/,classpath:/)
// 如果环境中有名为 spring.config.location的属性源
// spring.config.location配置指定了搜索范围,则以它指定的为准
if (this.environment.containsProperty("spring.config.location")) {
// 获取 spring.config.location指定的搜索范围
return this.getSearchLocations("spring.config.location");
} else {
// 获取 spring.config.additional-location指定的搜索范围
Set<String> locations = this.getSearchLocations("spring.config.additional-location");
// 将默认的搜索范围添加进去
// 1. file:./config/
// 2. file:./
// 3. classpath:/config/
// 4. classpath:/
locations.addAll(this.asResolvedSet(ConfigFileApplicationListener.this.searchLocations, "classpath:/,classpath:/config/,file:./,file:./config/"));
return locations; // 返回处理后的搜索范围
}
}
private Set<String> getSearchLocations(String propertyName) {
该方法的作用是从指定的值获取搜索范围
Set<String> locations = new LinkedHashSet();
String path;
// 判断环境当中是否存在该属性
if (this.environment.containsProperty(propertyName)) {
// 如果存在该属性,则开始遍历解析后的值
for(Iterator var3 = this.asResolvedSet(this.environment.getProperty(propertyName), (String)null).iterator(); var3.hasNext(); locations.add(path)) {
path = (String)var3.next();
// 如果路径不存在"$",一般是占位符
if (!path.contains("$")) {
path = StringUtils.cleanPath(path);
// 如果路径不是以url形式表示,即只有提供相对路径
// 则添加前缀"file:",变成绝对路径,默认从文件系统加载
if (!ResourceUtils.isUrl(path)) {
path = "file:" + path;
}
}
}
}
return locations;
}
4.2.2
## 获取配置文件的前缀 ##
private Set<String> getSearchNames() {
该方法的作用是获取配置文件的前缀中name对应的值
即 {name}-{profile}.(properties|yml)前面的name值,一般默认是application
// 如果环境中包含 spring.config.name指定的属性值
if (this.environment.containsProperty("spring.config.name")) {
// 获取 spirng.config.name指定的属性值
String property = this.environment.getProperty("spring.config.name");
// 返回 spirng.config.name指定的配置文件前缀
return this.asResolvedSet(property, (String)null);
} else {
// 返回默认的配置文件前缀"application"
return this.asResolvedSet(ConfigFileApplicationListener.this.names, "application");
}
}
4.2.3
## 加载配置文件 ##
private void load(String location, String name, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
该方法的作用是针对配置文件的不同前缀,使用不同的方式进行相应的处理
此时,前缀等于location + name
// StringUtils.hasText(String str)方法是用来检查给定字符串是否包含实际文本
// 也就是判断name值是否为""," ",null;一般默认值是"application",所以不会进入if语句块当中
if (!StringUtils.hasText(name)) {
Iterator var13 = this.propertySourceLoaders.iterator();
PropertySourceLoader loader;
do {
if (!var13.hasNext()) {
throw new IllegalStateException("File extension of config file location '" + location + "' is not known to any PropertySourceLoader. If the location is meant to reference a directory, it must end in '/'");
}
loader = (PropertySourceLoader)var13.next();
} while(!this.canLoadFileExtension(loader, location));
this.load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
}
// 针对"application"值,使用相应的属性资源加载器(前面构造Loader的时候已经初始化)进行处理
// 其中,属性资源加载器有两种
// 1. PropertiesPropertySourceLoader类 支持properties和xml文件,解析成Properties,然后封装成PropertiesPropertySource
// 2. YamlPropertySourceLoader类 支持yml和yaml文件,解析成Map,然后封装成MapPropertySource
else {
Set<String> processed = new HashSet();
// 获取迭代器
Iterator var7 = this.propertySourceLoaders.iterator();
while(var7.hasNext()) {
// 获取属性资源加载器
PropertySourceLoader loaderx = (PropertySourceLoader)var7.next();
// 返回属性资源加载器可以支持的扩展名
// PropertiesPropertySourceLoader加载器支持以"properties"、"xml"为后缀的配置文件
// YamlPropertySourceLoader加载器支持以"yml"、"ymal"为后缀的配置文件
String[] var9 = loaderx.getFileExtensions();
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String fileExtension = var9[var11];
if (processed.add(fileExtension)) {
this.loadForFileExtension(loaderx, location + name, "." + fileExtension, profile, filterFactory, consumer);
}
}
}
}
}
private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
该方法的作用是根据配置文件的绝对路径名(上面已经添加文件扩展名),来加载配置文件
ConfigFileApplicationListener.DocumentFilter defaultFilter = filterFactory.getDocumentFilter((ConfigFileApplicationListener.Profile)null);
ConfigFileApplicationListener.DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
// 如果profile不为null的话,配置文件名是 {name}-{profile}.fileExtension
// 比如遍历到第一个location,使用PropertiesPropertySourceLoader加载器加载时
// 默认情况是 location:file:./config/,name:application,profile:default,fileExtension:properties
// 此时的 prefix=file:./config/application,profile=default,fileExtension=properties
// profileSpecificFile=file:./config/application-default.properties
if (profile != null) {
// 确定配置文件的具体文件名(包括路径和完整的文件名)
String profileSpecificFile = prefix + "-" + profile + fileExtension;
this.load(loader, profileSpecificFile, profile, defaultFilter, consumer);
this.load(loader, profileSpecificFile, profile, profileFilter, consumer);
Iterator var10 = this.processedProfiles.iterator();
while(var10.hasNext()) {
ConfigFileApplicationListener.Profile processedProfile = (ConfigFileApplicationListener.Profile)var10.next();
if (processedProfile != null) {
String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
this.load(loader, previouslyLoaded, profile, profileFilter, consumer);
}
}
}
this.load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}
private void load(PropertySourceLoader loader, String location, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilter filter, ConfigFileApplicationListener.DocumentConsumer consumer) {
该方法的作用是将获取配置文件的Resouce对象,解析后生成PropertySource对象,封装到Document对象中
try {
// 获取指定路径匹配的Resource实例
Resource resource = this.resourceLoader.getResource(location);
StringBuilder descriptionxx;
// 如果存在Resource实例,并且不为null
if (resource != null && resource.exists()) {
// getFilename()方法返回资源的文件名
// getFilenameExtension(String path)方法从给定的资源路径获取扩展文件名
// 一般就是属性资源加载器所支持的 fileExtension,比如"properties"、"xml"、"yml"、"yaml"
// 判断配置文件的后缀是否存在,如果不存在,会打印日志堆栈信息,方便追踪调试
if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
if (this.logger.isTraceEnabled()) {
descriptionxx = this.getDescription("Skipped empty config extension ", location, resource, profile);
this.logger.trace(descriptionxx); // 打印日志信息
}
} else {
// 此时name:applicationConfig:[profileSpecificFile]
// 比如applicationConfig[file:./config/application-default.properties]
String name = "applicationConfig: [" + location + "]";
List<ConfigFileApplicationListener.Document> documents = this.loadDocuments(loader, name, resource);
if (CollectionUtils.isEmpty(documents)) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = this.getDescription("Skipped unloaded config ", location, resource, profile);
this.logger.trace(description);
}
} else {
List<ConfigFileApplicationListener.Document> loaded = new ArrayList();
Iterator var10 = documents.iterator();
while(var10.hasNext()) {
ConfigFileApplicationListener.Document document = (ConfigFileApplicationListener.Document)var10.next();
if (filter.match(document)) {
this.addActiveProfiles(document.getActiveProfiles());
this.addIncludedProfiles(document.getIncludeProfiles());
loaded.add(document);
}
}
Collections.reverse(loaded);
if (!loaded.isEmpty()) {
loaded.forEach((documentx) -> {
consumer.accept(profile, documentx);
});
if (this.logger.isDebugEnabled()) {
StringBuilder descriptionx = this.getDescription("Loaded config file ", location, resource, profile);
this.logger.debug(descriptionx);
}
}
}
}
} else {
if (this.logger.isTraceEnabled()) {
descriptionxx = this.getDescription("Skipped missing config ", location, resource, profile);
this.logger.trace(descriptionxx);
}
}
} catch (Exception var12) {
throw new IllegalStateException("Failed to load property source from location '" + location + "'", var12);
}
}
- End -(转载请注明出处)
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。