• 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]));
    }
    
  • 相关阅读:
    day10 基本数据类型介绍-整形的魔法
    python 快捷键
    F5负载均衡 简要原理解析以及命令行命令
    Python之路(2)
    Pycharm的安装
    集合框架(Collection和Collections的区别)
    集合框架(04)HashMap扩展知识
    集合框架(04)HashMap
    集合框架(03)泛型
    MVC
  • 原文地址:https://www.cnblogs.com/markLogZhu/p/12516948.html
Copyright © 2020-2023  润新知