zoukankan      html  css  js  c++  java
  • Spring Factories机制

    Spring Factories机制简述

    Spring Factories机制和Java SPI的扩展机制类似,Spring Boot采用了spring.factories的扩展机制,在很多spring的starter 包中都可以看到,通过读取 META-INF/spring.factories文件中的配置指定自动配置类入口,然后在程序中读取这些配置文件并实例化,从而让框架加载该类实现jar的动态加载。比如我们自定义的一些Spring Boot Starter公共组件就可以使用Spring Factories机制,通过极简的配置就可以在需要使用组件的地方引入依赖直接使用。

    什么是SPI机制

    SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。

    这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制。

    Spring Factories实现原理

    spring-core包(博主的是版本是5.2.8.RELEASE)里定义了SpringFactoriesLoader类,这个类实现了检索META-INF/spring.factories文件,并获取指定接口的配置的功能。

    以下为SpringFactoriesLoader类的注释说明:

    General purpose factory loading mechanism for internal use within the framework
    即该类是为框架内部使用工厂加载机制服务的。

    SpringFactoriesLoader loads and instantiates factories of a given type from FACTORIES_RESOURCE_LOCATION files which may be present in multiple JAR files in the classpath.
    SpringFactoriesLoader类通过读取spring.factories文档(该文档会存在多个classpath的多个jar包中),对给定的type及factoryClass加载和实例化其工厂类。

    The spring.factories file must be in java.util.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.
    spring.factories格式:key为接口或抽象类全称,value为具体实现类全称的列表

    举例如下:

    # PropertySource Loaders
    org.springframework.boot.env.PropertySourceLoader=
    org.springframework.boot.env.PropertiesPropertySourceLoader,
    org.springframework.boot.env.YamlPropertySourceLoader
    
    # Run Listeners
    org.springframework.boot.SpringApplicationRunListener=
    org.springframework.boot.context.event.EventPublishingRunListener
    

    通过FACTORIES_RESOURCE_LOCATION指定扫描的配置文件路径:

    /**
     * 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";
    

    类中定义了两个公共的方法:

    • loadFactories 根据接口类获取其实现类的实例,这个方法返回的是对象列表。
    • loadFactoryNames 根据接口获取其接口类的名称,这个方法返回的是类名的列表。

    其中还包含了私有方法:

    • instantiateFactory 根据类名创建实例对象。
    • loadSpringFactories 通过扫描配置文件获取接口名称和接口实现类名称列表。

    loadFactories

    loadFactories方法首先调用loadFactoryNames方法获取待实例化的具体实现类的全称,然后调用instantiateFactory方法实例化每一个具体实现类,最终返回一个具体实现类的实例列表。

    public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
    	Assert.notNull(factoryType, "'factoryType' must not be null");
    	ClassLoader classLoaderToUse = classLoader;
    	if (classLoaderToUse == null) {
    		classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    	}
    	//调用loadFactoryNames获取接口的实现类
    	List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
    	if (logger.isTraceEnabled()) {
    		logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
    	}
    	//遍历 factoryNames 数组,创建实现类的对象
    	List<T> result = new ArrayList<>(factoryImplementationNames.size());
    	for (String factoryImplementationName : factoryImplementationNames) {
    		//调用instantiateFactory根据类创建实例对象
    		result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
    	}
    	//排序
    	AnnotationAwareOrderComparator.sort(result);
    	return result;
    }
    

    instantiateFactory

    根据实现类名实例化每一个具体实现类返回

    @SuppressWarnings("unchecked")
    private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) {
    	try {
    		Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader);
    		if (!factoryType.isAssignableFrom(factoryImplementationClass)) {
    			throw new IllegalArgumentException(
    					"Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]");
    		}
    		return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance();
    	}
    	catch (Throwable ex) {
    		throw new IllegalArgumentException(
    			"Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]",
    			ex);
    	}
    }
    

    loadFactoryNames

    在这个方法中会遍历整个ClassLoader中所有jar包下的spring.factories文件。通过Properties解析所有接口的实现类名称。

    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    	String factoryTypeName = factoryType.getName();
    	return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }
    

    loadSpringFactories

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    	MultiValueMap<String, String> result = cache.get(classLoader);
    	if (result != null) {
    		return result;
    	}
    
    	try {
    		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();
    				//StringUtils.commaDelimitedListToStringArray:将配置文件中的value(接口实现类名称)通过","分隔成String数组
    				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);
    	}
    }
    
  • 相关阅读:
    常见的灰度发布系统规则
    golang中的路由分组
    艾森豪威尔矩阵
    列文定理
    吃狗粮定理
    mysql事务 锁
    mysql中explain优化分析
    mysql hash索引优化
    各种浏览器内核介绍
    浏览器 兼容性问题总结
  • 原文地址:https://www.cnblogs.com/curtinliu/p/14178342.html
Copyright © 2011-2022 走看看