spring.factories
在Spring Boot中有⼀种⾮常解耦的扩展机制:Spring Factories。这种扩展机制实际上是仿照Java中的SPI扩展机制来实现的。
Java SPI机制SPI的全名为Service Provider Interface.⼤多数开发⼈员可能不熟悉,因为这个是针对⼚商或者插件的。在
java.util.ServiceLoader的⽂档⾥有⽐较详细的介绍。简单的总结下java spi机制的思想。我们系统⾥抽象的各个模块,往往有很多不同的实现⽅案,⽐如⽇志模块的⽅案,xml解析模块、jdbc模块的⽅案等。⾯向的对象的设计⾥,我们⼀般推荐模块之间基于接⼝编程,模块之间不对实现类进⾏硬编码。⼀旦代码⾥涉及具体的实现类,就违反了可拔插的原则,如果需要替换⼀种实现,就需要修改代码。为了实现在模块装配的时候能不在程序⾥动态指明,这就需要⼀种服务发现机制。 java spi就是提供这样的⼀个机制:为某个接⼝寻服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。
Java SPI约定
java spi的具体约定为:当服务的提供者,提供了服务接⼝的⼀种实现之后,在jar包的META-INF/services/
⽬录⾥同时创建⼀个以服务接⼝命名的⽂件。该⽂件⾥就是实现该服务接⼝的具体实现类。⽽当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/⾥的配置⽂件到具体的实现类名,并装载实例化,完成模块的注⼊。基于这样⼀个约定就能很好的到服务接⼝的实现类,⽽不需要再代码⾥制定。jdk提供服务实现查的⼀个⼯具类:java.util.ServiceLoader
Spring Boot中的SPI机制
在Spring中也有⼀种类似与Java SPI的加载机制。它在META-INF/spring.factories⽂件中配置接⼝的实现类名称,然后在程序中读取这些配置⽂件并实例化。这种⾃定义的SPI机制是Spring Boot Starter实现的基础。
spring.factories 部分配置信息
# Initializers
t.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
t.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
Spring Factories实现原理
spring-core包⾥定义了SpringFactoriesLoader类,这个类实现了检索META-INF/spring.factories⽂件,并获取指定接⼝的配置的功能。在这个类中定义了两个对外的⽅法:
loadFactories:根据接⼝类获取其实现类的实例,这个⽅法返回的是对象列表。
loadFactoryNames:根据接⼝获取其接⼝类的名称,这个⽅法返回的是类名的列表。
上⾯的两个⽅法的关键都是从指定的ClassLoader中获取spring.factories⽂件,并解析得到类名列表,具体代码如下
/**
* 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 = Log(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) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = ClassLoader();
}
List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
if (logger.isTraceEnabled()) {
}
List<T> result = new ArrayList<>(factoryNames.size());
for (String factoryName : factoryNames) {
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
spring怎么读取propertiesreturn 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 = Name();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, ptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = Element();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : Set()) {
String factoryClassName = ((String) Key()).trim();
for (String factoryName : StringUtilsmaDelimitedListToStringArray((String) Value())) {
result.add(factoryClassName, im());
}
}
}
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 [" + Name() + "]");
}
return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance();
}
catch (Throwable ex) {
throw new IllegalArgumentException("Unable to instantiate factory class: " + Name(), ex);
}
}
}
加载factories⽂件
从代码中我们可以知道,在这个⽅法中会遍历整个ClassLoader中所有jar包下的spring.factories⽂件。也就是说我们可以在⾃⼰的jar中配置spring.factories⽂件,不会影响到其它地⽅的配置,也不会被别⼈的配置覆盖。
spring.factories的是通过Properties解析得到的,所以我们在写⽂件中的内容都是安装下⾯这种⽅式配置的:
如果⼀个接⼝希望配置多个实现类,可以使⽤','进⾏分割。
对于loadFactories⽅法⽽⾔,在获取类列表的基础上,还有进⾏实例化的过程。
实例化过程
从这段代码中我们可以知道,它只⽀持没有参数的构造函数。
Spring Factories在Spring Boot中的应⽤
在Spring Boot的很多包中都能够到spring.factories⽂件
spring.factories⽂件内容
⽇常⼯作中如何使⽤Spring Factories
在⽇常⼯作中,我们可能需要实现⼀些SDK或者Spring Boot Starter给被⼈使⽤,这个使⽤我们就可以使⽤Factories机制。Factories机制可以让SDK或者Starter的使⽤只需要很少或者不需要进⾏配置,只需要在服务中引⼊我们的jar包。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论