Spring源碼:@PropertySource源碼解析

目錄

1. @PropertySource處理入口

2. @PropertySource註解解析

3. 構造ResourcePropertySource對象

4. PropertySource配置加載到environment當中


通常,我們在開發java spring項目時,會包含多套環境(profile),並且分別提供了不同環境下的屬性文件(.properties),在引用屬性文件時,都會用到@PropertySource註解,標註在配置類@Configuration上面,下面主要分析一下@PropertySource註解的處理過程,也就是怎麼把配置信息從.properies文件放到environment中的;

1. @PropertySource處理入口

@PropertySource使用時都會和@Configuration放在一起,對@PropertySource的處理也是放在@Configuration解析處理過程中的(對@Configuration的處理過程後面再單獨進行分析),參見源碼如下:

/**
	 * Apply processing and build a complete {@link ConfigurationClass} by reading the
	 * annotations, members and methods from the source class. This method can be called
	 * multiple times as relevant sources are discovered.
	 * @param configClass the configuration class being build
	 * @param sourceClass a source class
	 * @return the superclass, or {@code null} if none found or previously processed
	 */
	protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			throws IOException {

		// Recursively process any member (nested) classes first
		processMemberClasses(configClass, sourceClass);

		// Process any @PropertySource annotations
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
				processPropertySource(propertySource);
			}
			else {
				logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}

    //......其餘部分省略
}

上面for循環處理的過程,實質上主要包含了兩步:

  • 解析@PropertySource註解,包括單獨聲明的@PropertySource註解,以及在容器註解@PropertySources value屬性中指定的註解;
  • 將解析的@PropertySource註解放到environment中;

下面分別對這兩個過程進行分析;

2. @PropertySource註解解析

解析過程主要封裝到了AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)當中,根據進去源碼如下:

static Set<AnnotationAttributes> attributesForRepeatable(
			AnnotationMetadata metadata, String containerClassName, String annotationClassName) {

		Set<AnnotationAttributes> result = new LinkedHashSet<AnnotationAttributes>();

		// Direct annotation present?
		addAttributesIfNotNull(result, metadata.getAnnotationAttributes(annotationClassName, false));

		// Container annotation present?
		Map<String, Object> container = metadata.getAnnotationAttributes(containerClassName, false);
		if (container != null && container.containsKey("value")) {
			for (Map<String, Object> containedAttributes : (Map<String, Object>[]) container.get("value")) {
				addAttributesIfNotNull(result, containedAttributes);
			}
		}

		// Return merged result
		return Collections.unmodifiableSet(result);
	}

private static void addAttributesIfNotNull(Set<AnnotationAttributes> result, Map<String, Object> attributes) {
		if (attributes != null) {
			result.add(AnnotationAttributes.fromMap(attributes));
		}
	}

這裏,首先獲取配置類上@PropertySource註解,解析成AnnotationAttributes map對象,放到result中;

然後解析容器註解@PropertySources value屬性值,並將解析的@PropertySource列表放到result中;

這樣@PropertySource註解和@PropertySources容器註解解析完畢;

3. 構造ResourcePropertySource對象

這裏主要分析一下processPropertySource(propertySource)的過程,源碼如下:

/**
	 * Process the given <code>@PropertySource</code> annotation metadata.
	 * @param propertySource metadata for the <code>@PropertySource</code> annotation found
	 * @throws IOException if loading a property source failed
	 */
	private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
		String name = propertySource.getString("name");
		if (!StringUtils.hasLength(name)) {
			name = null;
		}
		String encoding = propertySource.getString("encoding");
		if (!StringUtils.hasLength(encoding)) {
			encoding = null;
		}
		String[] locations = propertySource.getStringArray("value");
		Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
		boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");

		Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
		PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
				DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));

		for (String location : locations) {
			try {
				String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
				Resource resource = this.resourceLoader.getResource(resolvedLocation);
				addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
			}
			catch (IllegalArgumentException ex) {
				// Placeholders not resolvable
				if (ignoreResourceNotFound) {
					if (logger.isInfoEnabled()) {
						logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
					}
				}
				else {
					throw ex;
				}
			}
			catch (IOException ex) {
				// Resource not found when trying to open it
				if (ignoreResourceNotFound &&
						(ex instanceof FileNotFoundException || ex instanceof UnknownHostException)) {
					if (logger.isInfoEnabled()) {
						logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
					}
				}
				else {
					throw ex;
				}
			}
		}
	}

其中@PropertySource的主要屬性value(這裏放到了locations中)保存了屬性文件的存放位置,對每一個location的解析主要分爲如下3步:

  • 解析location中包含的佔位符
  • 加載Resource對象
  • 構造ResourcePropertySource對象
  • PropertySource加載到environment當中

其中第三步構造ResourcePropertySource主要用到了PropertySourceFactory,這裏默認實現是DefaultPropertySourceFactory,內部實現源碼如下:

/**
 * The default implementation for {@link PropertySourceFactory},
 * wrapping every resource in a {@link ResourcePropertySource}.
 *
 * @author Juergen Hoeller
 * @since 4.3
 * @see PropertySourceFactory
 * @see ResourcePropertySource
 */
public class DefaultPropertySourceFactory implements PropertySourceFactory {

	@Override
	public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
		return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
	}

}

上面主要通過resource構造了ResourcePropertySource對象,其構造函數如下:

/**
	 * Create a PropertySource based on Properties loaded from the given resource.
	 * The name of the PropertySource will be generated based on the
	 * {@link Resource#getDescription() description} of the given resource.
	 */
	public ResourcePropertySource(EncodedResource resource) throws IOException {
		super(getNameForResource(resource.getResource()), PropertiesLoaderUtils.loadProperties(resource));
		this.resourceName = null;
	}

如上,可見先是由resource構造了Peoperties對象,然後構造了PropertiesPropertySource父類.....

如下是ResourcePropertySource的繼承結構,最終加載的屬性值放入到了PropertySource的成員變量source中;

4. PropertySource配置加載到environment當中

構造完ResourcePropertySource對象之後,下面將該對象放入到environment中,源碼如下:

private void addPropertySource(PropertySource<?> propertySource) {
		String name = propertySource.getName();
		MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();
		if (propertySources.contains(name) && this.propertySourceNames.contains(name)) {
			// We've already added a version, we need to extend it
			PropertySource<?> existing = propertySources.get(name);
			PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ?
					((ResourcePropertySource) propertySource).withResourceName() : propertySource);
			if (existing instanceof CompositePropertySource) {
				((CompositePropertySource) existing).addFirstPropertySource(newSource);
			}
			else {
				if (existing instanceof ResourcePropertySource) {
					existing = ((ResourcePropertySource) existing).withResourceName();
				}
				CompositePropertySource composite = new CompositePropertySource(name);
				composite.addPropertySource(newSource);
				composite.addPropertySource(existing);
				propertySources.replace(name, composite);
			}
		}
		else {
			if (this.propertySourceNames.isEmpty()) {
				propertySources.addLast(propertySource);
			}
			else {
				String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
				propertySources.addBefore(firstProcessed, propertySource);
			}
		}
		this.propertySourceNames.add(name);
	}

注意,這裏對於@PropertySource註解獲取的配置屬性放入到了environment的後面,實際在application.properties後面,也即application.properties的優先級高於@PropertySource引入的配置,後面單獨對這塊進行分析;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章