Spring中的@PropertySource注解源码详细解析
作者:mumubili
前言
通常,我们在开发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引入的配置,后面单独对这块进行分析;
到此这篇关于Spring中的@PropertySource注解源码详细解析的文章就介绍到这了,更多相关@PropertySource注解源码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!