上一篇内容:https://blog.csdn.net/qq_22596931/article/details/86606903
上一篇内容介绍的是如何读取我们自定义的yml文件,这一篇内容承接上一篇,主要介绍为什么我们能够利用自定义一个类来实现读取我们自定义的yml文件。文章内容中对于涉及源码的部分,只截取了部分相关的,读者最好在自己电脑上观看更佳,此文仅作为引导作用。
自定义YamlPropertySourceFactory读取yml文件
基于对自定义YamlPropertySourceFactory的分析。
比较自定义YamlPropertySourceFactory和默认的DefaultPropertySourceFactory的不同。两者实现的机制是不一样。
无论是默认的还是自定义的,都是实现了PropertySourceFactory接口。
首先分析默认的DefaultPropertySourceFactory方式。
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.io.support;
import java.io.IOException;
import org.springframework.core.env.PropertySource;
import org.springframework.lang.Nullable;
/**
* 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(@Nullable String name, EncodedResource resource) throws IOException {
return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
}
}
如上述代码所示,实现PropertySourceFactory接口主要是重写PropertySourceFactory这个方法,获取到一个PropertySourceFactory。
对应的源码信息在ResourcePropertySource中,相关源码如下:
/**
* Create a PropertySource having the given name based on Properties
* loaded from the given encoded resource.
*/
public ResourcePropertySource(String name, EncodedResource resource) throws IOException {
super(name, PropertiesLoaderUtils.loadProperties(resource));
this.resourceName = getNameForResource(resource.getResource());
}
/**
* 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;
}
/**
* Return the description for the given Resource; if the description is
* empty, return the class name of the resource plus its identity hash code.
* @see org.springframework.core.io.Resource#getDescription()
*/
private static String getNameForResource(Resource resource) {
String name = resource.getDescription();
if (!StringUtils.hasText(name)) {
name = resource.getClass().getSimpleName() + "@" + System.identityHashCode(resource);
}
return name;
}
上面涉及到的源码PropertiesLoaderUtils:
/**
* Load properties from the given EncodedResource,
* potentially defining a specific encoding for the properties file.
* @see #fillProperties(java.util.Properties, EncodedResource)
*/
public static Properties loadProperties(EncodedResource resource) throws IOException {
Properties props = new Properties();
fillProperties(props, resource);
return props;
}
自定义YamlPropertySourceFactory的实现机制:
package com.wch.SpringBootTestDemo.defineTest;
import java.io.IOException;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import org.springframework.boot.env.PropertySourcesLoader;
public class YamlPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
@SuppressWarnings("rawtypes")
PropertySource test = name != null ? new PropertySourcesLoader().load(resource.getResource(), name, null) : new PropertySourcesLoader().load(
resource.getResource(), getNameForResource(resource.getResource()), null);
return test;
}
private static String getNameForResource(Resource resource) {
String name = resource.getDescription();
if (!org.springframework.util.StringUtils.hasText(name)) {
name = resource.getClass().getSimpleName() + "@" + System.identityHashCode(resource);
}
return name;
}
}
上述代码中getNameForResource方法其实和ResourcePropertySource中的getNameForResource方法是一样的,都是为了获取资源的名称。
PropertySourcesLoader中相关源码:
private final MutablePropertySources propertySources;
private final List loaders;
public PropertySourcesLoader() {
this(new MutablePropertySources());
}
public PropertySourcesLoader(MutablePropertySources propertySources) {
Assert.notNull(propertySources, "PropertySources must not be null");
this.propertySources = propertySources;
loaders = SpringFactoriesLoader.loadFactories(org / springframework / boot / env / PropertySourceLoader,
getClass().getClassLoader());
}
public PropertySource load(Resource resource, String name, String profile) throws IOException {
return load(resource, null, name, profile);
}
public PropertySource load(Resource resource, String group, String name, String profile) throws IOException {
label0: {
if (!isFile(resource))
break label0;
String sourceName = generatePropertySourceName(name, profile);
Iterator iterator = loaders.iterator();
PropertySourceLoader loader;
do {
if (!iterator.hasNext())
break label0;
loader = (PropertySourceLoader) iterator.next();
} while (!canLoadFileExtension(loader, resource));
PropertySource specific = loader.load(sourceName, resource, profile);
addPropertySource(group, specific, profile);
return specific;
}
return null;
}
上述源码中的leaders(加载器),是通过SpringFactoriesLoader.loadFactories方法实现获取到的,下面看下里面的具体内容,在SpringFactoriesLoader对应源码为:
/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.io.support;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.UrlResource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* General purpose factory loading mechanism for internal use within the framework.
*
* <p>{@code SpringFactoriesLoader} {@linkplain #loadFactories loads} and instantiates
* factories of a given type from {@value #FACTORIES_RESOURCE_LOCATION} files which
* may be present in multiple JAR files in the classpath. The {@code spring.factories}
* file must be in {@link Properties} format, where the key is the fully qualified
* name of the interface or abstract class, and the value is a comma-separated list of
* implementation class names. For example:
*
* <pre class="code">example.MyService=example.MyServiceImpl1,example.MyServiceImpl2</pre>
*
* where {@code example.MyService} is the name of the interface, and {@code MyServiceImpl1}
* and {@code MyServiceImpl2} are two implementations.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.2
*/
public final class SpringFactoriesLoader {
/**
* The location to look for factories.
* <p>Can be present in multiple JAR files.
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
private SpringFactoriesLoader() {
}
/**
* Load and instantiate the factory implementations of the given type from
* {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader.
* <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
* <p>If a custom instantiation strategy is required, use {@link #loadFactoryNames}
* to obtain all registered factory names.
* @param factoryClass the interface or abstract class representing the factory
* @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
* @throws IllegalArgumentException if any factory implementation class cannot
* be loaded or if an error occurs while instantiating any factory
* @see #loadFactoryNames
*/
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryClass, "'factoryClass' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
}
List<T> result = new ArrayList<>(factoryNames.size());
for (String factoryName : factoryNames) {
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
/**
* Load the fully qualified class names of factory implementations of the
* given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
* class loader.
* @param factoryClass the interface or abstract class representing the factory
* @param classLoader the ClassLoader to use for loading resources; can be
* {@code null} to use the default
* @throws IllegalArgumentException if an error occurs while loading factory names
* @see #loadFactories
*/
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
@SuppressWarnings("unchecked")
private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
try {
Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
if (!factoryClass.isAssignableFrom(instanceClass)) {
throw new IllegalArgumentException(
"Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
}
return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance();
}
catch (Throwable ex) {
throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);
}
}
}
上述代码中很明显,那些leaders(加载器)的获取是通过在文件 META-INF/spring.factories中获取到的,
对应文件为:
很明显,能够在这个文件中获取对应的加载器,其中也就有我们需要的yml文件的加载器:YamlPropertySourceLoader。
所以在我们使用的时候使用注解,指定对应的文件已经对应的工程类,就能够正确读取到yml文件。