SpringBott之Profile的理解和使用

一、Profile 介绍

1.1 Profile 的作用

在日常工作中,项目会处于不同的环境下,如开发、测试、生产等环境。针对不同的环境,各种配置信息也会不同,针对这种情况 Spring 提供了 Profile 机制,实现环境的切换。

1.2激活 Profile 方式

Spring 激活 Profile 依赖两个属性:

  • spring.profiles.active
  • spring.profiles.default

active 优先级比 default 高。

二、Profile 实践

2.1 激活单个配置文件

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

fileName=dev

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

fileName=test

resources/application-test.properties 文件中启用当前环境为 dev:

spring.profiles.active=dev

新建启动加载器,获取 fileName 属性

@Component
public class GetAttributesApplicationRunner implements ApplicationRunner, EnvironmentAware {

    private Environment environment;
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(environment.getProperty("fileName"));
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

}

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

dev

2.2 激活多个配置文件

修改 resources /application-dev.properties 文件:

fileName=dev
age=18

修改resources /application-dev.properties 文件:

fileName=test
name=MarkLogZhu

resources/application-test.properties 文件中启用多个配置文件:

spring.profiles.include=test,dev

修改启动加载器,获取其他属性

@Override
public void run(ApplicationArguments args) throws Exception {
    System.out.println(environment.getProperty("fileName"));
    System.out.println(environment.getProperty("name"));
    System.out.println(environment.getProperty("age"));
}

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

dev
MarkLogZhu
18

可以看到已经将所有的属性都读取到了,但是同名的属性会被覆盖掉,这点要特别注意。

三、SpringBoot 中 Profile 加载原理

在之前我们讲解 SpringBoot之属性配置 的时候有稍微提到了 ConfigFileApplicationListener .load 方法,我们来继续看下它的实现:

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);
			});
}

3.1 initializeProfiles

初始化 Profiles,将所有的 profile 增加到 prifiles 集合中

private Deque<Profile> profiles;

private void initializeProfiles() {
	// The default profile for these purposes is represented as null. We add it
	// first so that it is processed first and has lowest priority.
	this.profiles.add(null);
	Set<Profile> activatedViaProperty = getProfilesFromProperty(ACTIVE_PROFILES_PROPERTY);
	Set<Profile> includedViaProperty = getProfilesFromProperty(INCLUDE_PROFILES_PROPERTY);
	List<Profile> otherActiveProfiles = getOtherActiveProfiles(activatedViaProperty, includedViaProperty);
	this.profiles.addAll(otherActiveProfiles);
	// Any pre-existing active profiles set via property sources (e.g.
	// System properties) take precedence over those added in config files.
	this.profiles.addAll(includedViaProperty);
	addActiveProfiles(activatedViaProperty);
	if (this.profiles.size() == 1) { // only has null profile
		for (String defaultProfileName : this.environment.getDefaultProfiles()) {
			Profile defaultProfile = new Profile(defaultProfileName, true);
			this.profiles.add(defaultProfile);
		}
	}
}

3.2 循环 load

private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
	getSearchLocations().forEach((location) -> {
		boolean isFolder = location.endsWith("/");
		Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
		names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
	});
}

进入 **getSearchLocations **方法:

// 配置文件属性名称,用来实现自定义配置文件名称
public static final String CONFIG_NAME_PROPERTY = "spring.config.name";
// 默认的配置文件名称
private static final String DEFAULT_NAMES = "application";
private Set<String> getSearchNames() {
    // 如果存在该属性,就是用用户自定义的配置文件,否则使用默认的名称
	if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
		String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
		return asResolvedSet(property, null);
	}
	return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}

进入 load(location, name, profile, filterFactory, consumer)) 方法:

private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
		DocumentConsumer consumer) {
	if (!StringUtils.hasText(name)) {
		for (PropertySourceLoader loader : this.propertySourceLoaders) {
			if (canLoadFileExtension(loader, location)) {
				load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
				return;
			}
		}
		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 '/'");
	}
	Set<String> processed = new HashSet<>();
	for (PropertySourceLoader loader : this.propertySourceLoaders) {
		for (String fileExtension : loader.getFileExtensions()) {
			if (processed.add(fileExtension)) {
				loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
						consumer);
			}
		}
	}
}

进入loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory, consumer) 方法:

private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
		Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
	DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
	DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
	if (profile != null) {
		// Try profile-specific file & profile section in profile file (gh-340)
		String profileSpecificFile = prefix + "-" + profile + fileExtension;
		load(loader, profileSpecificFile, profile, defaultFilter, consumer);
		load(loader, profileSpecificFile, profile, profileFilter, consumer);
		// Try profile specific sections in files we've already processed
		for (Profile processedProfile : this.processedProfiles) {
			if (processedProfile != null) {
				String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
				load(loader, previouslyLoaded, profile, profileFilter, consumer);
			}
		}
	}
	// Also try the profile-specific section (if any) of the normal file
	load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}

最后进入 load(loader, prefix + fileExtension, profile, profileFilter, consumer) 方法:

private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
		DocumentConsumer consumer) {
	try {
		Resource resource = this.resourceLoader.getResource(location);
		if (resource == null || !resource.exists()) {
			if (this.logger.isTraceEnabled()) {
				StringBuilder description = getDescription("Skipped missing config ", location, resource,
						profile);
				this.logger.trace(description);
			}
			return;
		}
		if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
			if (this.logger.isTraceEnabled()) {
				StringBuilder description = getDescription("Skipped empty config extension ", location,
						resource, profile);
				this.logger.trace(description);
			}
			return;
		}
		String name = "applicationConfig: [" + location + "]";
		List<Document> documents = loadDocuments(loader, name, resource);
		if (CollectionUtils.isEmpty(documents)) {
			if (this.logger.isTraceEnabled()) {
				StringBuilder description = getDescription("Skipped unloaded config ", location, resource,
						profile);
				this.logger.trace(description);
			}
			return;
		}
		List<Document> loaded = new ArrayList<>();
		for (Document document : documents) {
			if (filter.match(document)) {
				addActiveProfiles(document.getActiveProfiles());
				addIncludedProfiles(document.getIncludeProfiles());
				loaded.add(document);
			}
		}
		Collections.reverse(loaded);
		if (!loaded.isEmpty()) {
			loaded.forEach((document) -> consumer.accept(profile, document));
			if (this.logger.isDebugEnabled()) {
				StringBuilder description = getDescription("Loaded config file ", location, resource, profile);
				this.logger.debug(description);
			}
		}
	}
	catch (Exception ex) {
		throw new IllegalStateException("Failed to load property source from location '" + location + "'", ex);
	}
}

进入 List documents = loadDocuments(loader, name, resource); :

private List<Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource)
		throws IOException {
	DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
	List<Document> documents = this.loadDocumentsCache.get(cacheKey);
	if (documents == null) {
		List<PropertySource<?>> loaded = loader.load(name, resource);
		documents = asDocuments(loaded);
		this.loadDocumentsCache.put(cacheKey, documents);
	}
	return documents;
}

进入 List<PropertySource<?>> loaded = loader.load(name, resource);

@Override
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
	Map<String, ?> properties = loadProperties(resource);
	if (properties.isEmpty()) {
		return Collections.emptyList();
	}
	return Collections
			.singletonList(new OriginTrackedMapPropertySource(name, Collections.unmodifiableMap(properties), true));
}

循环 document 添加属性

List<Document> loaded = new ArrayList<>();
for (Document document : documents) {
   if (filter.match(document)) {
      addActiveProfiles(document.getActiveProfiles());
      addIncludedProfiles(document.getIncludeProfiles());
      loaded.add(document);
   }
}

3.3 addLoadedPropertySources

private void addLoadedPropertySources() {
   MutablePropertySources destination = this.environment.getPropertySources();
   List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values());
   Collections.reverse(loaded);
   String lastAdded = null;
   Set<String> added = new HashSet<>();
   for (MutablePropertySources sources : loaded) {
      for (PropertySource<?> source : sources) {
         if (added.add(source.getName())) {
            addLoadedPropertySource(destination, lastAdded, source);
            lastAdded = source.getName();
         }
      }
   }
}

3.4 applyActiveProfiles(defaultProperties)

// 应用活动配置文件
private void applyActiveProfiles(PropertySource<?> defaultProperties) {
	List<String> activeProfiles = new ArrayList<>();
	if (defaultProperties != null) {
		Binder binder = new Binder(ConfigurationPropertySources.from(defaultProperties),
				new PropertySourcesPlaceholdersResolver(this.environment));
		activeProfiles.addAll(getDefaultProfiles(binder, "spring.profiles.include"));
		if (!this.activatedProfiles) {
			activeProfiles.addAll(getDefaultProfiles(binder, "spring.profiles.active"));
		}
	}
	this.processedProfiles.stream().filter(this::isDefaultProfile).map(Profile::getName)
			.forEach(activeProfiles::add);
	this.environment.setActiveProfiles(activeProfiles.toArray(new String[0]));
}
posted @ 2020-03-18 13:41  MarkLogZhu  阅读(666)  评论(0编辑  收藏  举报