Spring Factories知多少?
一柱独擎梁栋重,十年整顿乾坤了。
种春风、桃李满人间,知多少。
文章目录
Spring Factories使用场景
spring.factories文件主要是帮助把spring-boot项目包以外的bean(即在pom文件中添加依赖中的bean)注册到spring-boot项目的spring容器。由于@ComponentScan注解只能扫描spring-boot项目包内的bean并注册到spring容器中,因此需要@EnableAutoConfiguration注解来注册项目包外的bean。而spring.factories文件,则是用来记录项目包外需要注册的bean类名。
Spring Factories是什么
在Spring中有一种类似与Java SPI的加载机制。它在META-INF/spring.factories文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。这种自定义的SPI机制是Spring Boot Starter实现的基础。简而言之Spring Factories就是Spring Boot中一种解耦的扩展机制。
META-INF/spring.factories文件是springboot 框架识别并解析starter的核心文件,了解springboot加载META-INF/spring.factories文件原理至关重要。
Spring Factories原理
run方法源码
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
//创建程序计时器
StopWatch stopWatch=new StopWatch();
//启动程序计时器
stopWatch.start();
ConfigurableApplicationContext context=null;
Collection<SpringBootExceptionReporter> exceptionReporters=new ArrayList<>();
//设置java.awt.headless系统属性值
configureHeadlessProperty();
//从缓存的META-INF/spring.factories Map中获取
//SpringApplicationRunListeners接口的所有子类
SpringApplicationRunListeners listeners=getRunListeners(args);
//回调通知应用开始启动事件监听器
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
getRunListeners(args)源码
private SpringApplicationRunListeners getRunListeners(String[] args) {
//SpringApplicationRunListener实现类构造器参数类型数组
//默认实现类为EventPublishingRunListener
Class<?>[] types = new Class<?>[]{SpringApplication.class, String[].class};
//将SpringApplicationRunListener实现类封装到SpringApplicationRunListeners
//SpringApplicationRunListeners支持通过for所有事件list批量执行功能
//后面分析starting方法时可以看到for代码
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
//需要查找的接口名称字符串
String factoryClassName = factoryClass.getName();
//读取磁盘文件进行加载,返回找到封装结果的Map中key为需要查找的接口名称的values
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//先从缓存查找当前classloader是否加载过
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
//如果之前加载过就返回,这里体验了读取文件资源实际做了缓存的
//后面都是从缓存读取
return result;
}
try {
//从当前classloader的classpath下读取META-INF/spring.factories
//封装返回Enumeration迭代器
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
//迭代所有META-INF/spring.factories文件
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
//将META-INF/spring.factories文件转为UrlResource对象
UrlResource resource = new UrlResource(url);
//将META-INF/spring.factories文件从UrlResource对象转为Properties映射表
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
//遍历META-INF/spring.factories文件entry
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
//遍历value逗号分隔返回的String[]
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
//放到LinkedMultiValueMap,key为接口全名称字符串,
//value为接口实现类的全名称字符串
result.add(factoryClassName, factoryName.trim());
}
}
}
//保存到ConcurrentReferenceHashMap,key为当前classloader
//value为所有META-INF/spring.factories文件k,v Map
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
//获取Class对象
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
//类型判断
Assert.isAssignable(type, instanceClass);
//通过传入的构造器参数信息获取构造器
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
//实例化
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
} catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
大致展示了springboot读取META-INF/spring.factories过程。通过classloader从classpath下读取所有的META-INF/spring.factories文件,然后通过Map键值对数据结构保存在spring core模块下的SpringFactoriesLoader抽象类的静态属性cache中。后续所有需要从META-INF/spring.factories文件获取都是尝试先从这个缓存的Map中获取。
JAVA的SPI机制
SPI的全名为Service Provider Interface.大多数开发人员可能不熟悉,因为这个是针对厂商或者插件的。在java.util.ServiceLoader的文档里有比较详细的介绍。
简单的总结下java SPI机制的思想。我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块、jdbc模块的方案等。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。
java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。
Spring Factories使用
封装好的sdk,recourse中添加对应配置文件,将maven坐标配置到指定项目,spring boot自动配置会加载pom中的额外bean,简单方便。
有一些封装的案例可以参考下。
//META-INF spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.nom.mds.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration