Spring Factories知多少

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值