spring.factories配置⽂件的⼯⼚模式
在springboot的各个依赖包下,我们经常看到META-INF/spring.factories这个⽂件。spring.factories⽂件的内容基本上都是这样的格式:
1 # Initializers
2 t.ApplicationContextInitializer=\
3 org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
4 org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
我们看到,这个⽂件配置了⼀个key:value格式的数据
1)key是:t.ApplicationContextInitializer
2)value是:
t.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,t.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener key声明的是⼀个接⼝,value则是这个接⼝对应的实现类,如果有多个则以","符号分割。
简单来说,spring.factories⽂件包含了⼀些接⼝相对应的实现类的配置,我们通过这些配置就可以知道接⼝有哪些可选的实现类,并通过反射获取对应的实例对象。就像是简单
⼯⼚模式⼀样,也因此spring将这个⽂件定义为spring.factories这个名字。
代码实例
下⾯以ApplicationContextInitializer接⼝为⽰例,我们看看springboot是怎么使⽤spring.factories的。
⾸先会⽤classLoader加载类路径下的所有spring.factories的配置内容,loadSpringFactories⽅法将返回⼀个key=接⼝名,value=实现类集合的Map结构
1private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
2// 先试着取缓存
3 MultiValueMap<String, String> result = (classLoader);
4if (result != null) {
5return result;
6 }
7
8try {
9// 获取所有spring.factories的URL
10 Enumeration<URL> urls = (classLoader != null ? Resources(FACTORIES_RESOURCE_LOCATION) : SystemResources(FACTORIES_RESOURCE_LOCATION));
11 result = new LinkedMultiValueMap<>();
12// 遍历URL
13while (urls.hasMoreElements()) {
14 URL url = Element();
15 UrlResource resource = new UrlResource(url);
16// 加载每个URL中的properties配置
17 Properties properties = PropertiesLoaderUtils.loadProperties(resource);
18// 遍历每个配置
19for (Map.Entry<?, ?> entry : Set()) {
20 String factoryClassName = ((String) Key()).trim();
21// 将实现类的配置按照","符号分割开
22for (String factoryName : StringUtilsmaDelimitedListToStringArray((String) Value())) {
23// 逐个添加到接⼝对应的集合当中
24 result.add(factoryClassName, im());
25 }
26 }
27 }
28// 加⼊缓存
29 cache.put(classLoader, result);
30return result;
31 } catch (IOException ex) {
32// ...
spring framework版本33 }
34 }
有了以上这个Map结构,就可以轻松拿到对应接⼝的实现类集合了,如:
1public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
2 String factoryClassName = Name();
3return loadSpringFactories(classLoader).getOrDefault(factoryClassName, ptyList());
4 }
这⾥的factoryClass是接⼝,通过getName()⽅法获取全限定名,然后根据该全限定名从Map结构中get出对应的实现类全限定名的集合。
到这⾥我们得到了⼀个实现类的集合,要获取实现类具体的实例对象只需要通过反射得到实例对象即可,如:
1private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
2 ClassLoader classLoader, Object[] args, Set<String> names) {
3 List<T> instances = new ArrayList<>(names.size());
4// 遍历实例对象的全限定名
5for (String name : names) {
6try {
7// 加载该类
8 Class<?> instanceClass = ClassUtils.forName(name, classLoader);
9// 断⾔是否为该接⼝的实现类
10 Assert.isAssignable(type, instanceClass);
11// 获取构造⽅法
12 Constructor<?> constructor = DeclaredConstructor(parameterTypes);
13// 实例化该类
14 T instance = (T) BeanUtils.instantiateClass(constructor, args);
15// 添加到结果集当中
16 instances.add(instance);
17 }
18catch (Throwable ex) {
19throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
20 }
21 }
22return instances;
23 }
总结
spring.factories就像是⼯⼚⼀样配置了⼤量的接⼝对应的实现类,我们通过这些配置 + 反射处理就可以拿到相应的实现类。这种类似于插件式的设计⽅式,只要引⼊对应的jar
包,那么对应的spring.factories就会被扫描到,对应的实现类也就会被实例化,如果不需要的时候,直接把jar包移除即可。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论