前言:我们在创建一个springboot工程的时候,经常会依赖很多starter包,比如: [spring-boot-starter-actuator]、[spring-boot-starter-data-redis]、[spring-boot-starter-jdbc]等等。那么大家有没有想过别人开发的工程,我们通过自动注入的方式怎么会拿到他的对象?SpringBoot又是什么时候把这些对象以代理对象的形式存放进spring容器中的呢?下边我们就来聊聊如何创建一个我们自己的starter工程,并让其他工程通过maven依赖进来使用。
1.spring.factories是什么
如果我们打开一个starter工程的autoconfigure模块,会在他的类路径(classpath)的
META-INF目录下看到一个 spring.factories文件
这个文件其实就是给springboot去识别哪些类需要注入spring容器管理的,文件格式为
#key=value,key:spring特征类路径,value:我们的特征类路径
#比如需要创建一个自动配置的类MyAutoConfiguration.class
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.la.autoconfiguration.MyAutoConfiguration
#如果有多个类,使用","隔开,使用"\"进行换行,例如:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
xxx.A,\
xxx.B,\
xxx.C
常见的类型有以下几种:
- Initializers
org.springframework.context.ApplicationContextInitializer= - Application Listeners
org.springframework.context.ApplicationListener= - Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener= - Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter= - Auto Configure()
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
2.spring.factories什么时候被加载
springboot在启动后,会遍历所有jar的类路径的META-INF目录,寻找spring.factories文件并解析。具体过程如下:
执行SpringApplication.run(Class<?> primarySource, String... args)方法中调用了SpringFactoriesLoader去加载并解析spring.factories文件
/**
* Load the fully qualified class names of factory implementations of the
* given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
* class loader.
* @param factoryType 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<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, 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 {
//---- 这里的FACTORIES_RESOURCE_LOCATION就是是字符串常量"META-INF/spring.factories" ----
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 factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
SpringBoot启动后查找spring.factories文件的调用栈如下图:
3.作用
简化第三方组件的配置使用,只要通过maven引入相关的依赖,springboot启动后就能自动找到并使用该组件的功能。