• 深入理解SPI机制


    一、什么是SPI
    SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
    SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。

    这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制。我们先通过一个很简单的例子来看下它是怎么用的。

    JAVA SPI:
    sun.misc.Service
    java.util.ServiceLoader

    public final class ServiceLoader<S>
        implements Iterable<S>
    {
    
        private static final String PREFIX = "META-INF/services/";
    
        // The class or interface representing the service being loaded
        private final Class<S> service;
    
        // The class loader used to locate, load, and instantiate providers
        private final ClassLoader loader;
    
        // The access control context taken when the ServiceLoader is created
        private final AccessControlContext acc;
    
        // Cached providers, in instantiation order
        private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    
        // The current lazy-lookup iterator
        private LazyIterator lookupIterator;
    
        /**
         * Clear this loader's provider cache so that all providers will be
         * reloaded.
         *
         * <p> After invoking this method, subsequent invocations of the {@link
         * #iterator() iterator} method will lazily look up and instantiate
         * providers from scratch, just as is done by a newly-created loader.
         *
         * <p> This method is intended for use in situations in which new providers
         * can be installed into a running Java virtual machine.
         */
        public void reload() {
            providers.clear();
            lookupIterator = new LazyIterator(service, loader);
        }
    
        private ServiceLoader(Class<S> svc, ClassLoader cl) {
            service = Objects.requireNonNull(svc, "Service interface cannot be null");
            loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
            acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
            reload();
        }
    ...........
    

    Dubbo SPI:
    com.alibaba.dubbo.common.extension.ExtensionLoader
    https://mp.weixin.qq.com/s/gwWOsdQGEN0t2GJVMQQexw

    /**
     * Load dubbo extensions
     * <ul>
     * <li>auto inject dependency extension </li>
     * <li>auto wrap extension in wrapper </li>
     * <li>default extension is an adaptive instance</li>
     * </ul>
     *
     * @see <a href="http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Service%20Provider">Service Provider in Java 5</a>
     * @see com.alibaba.dubbo.common.extension.SPI
     * @see com.alibaba.dubbo.common.extension.Adaptive
     * @see com.alibaba.dubbo.common.extension.Activate
     */
    public class ExtensionLoader<T> {
    
        private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
    
        private static final String SERVICES_DIRECTORY = "META-INF/services/";
    
        private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
    
        private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
    
        private static final Pattern NAME_SEPARATOR = Pattern.compile("\s*[,]+\s*");
    
        private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
    
        private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
    
        // ==============================
    
        private final Class<?> type;
    
        private final ExtensionFactory objectFactory;
    
        private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();
    
        private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();
    
        private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();
        private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
        private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
        private volatile Class<?> cachedAdaptiveClass = null;
        private String cachedDefaultName;
        private volatile Throwable createAdaptiveInstanceError;
    
        private Set<Class<?>> cachedWrapperClasses;
    
        private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();
    
        private ExtensionLoader(Class<?> type) {
            this.type = type;
            objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
        }
    ...........
    

    Spring SPI:
    org.springframework.core.io.support.SpringFactoriesLoader

    /**
     * General purpose factory loading mechanism for internal use within the framework.
     *
     * <p>{@code SpringFactoriesLoader} {@linkplain #loadFactories loads} and instantiates
     * factories of a given type from {@value #FACTORIES_RESOURCE_LOCATION} files which
     * may be present in multiple JAR files in the classpath. The {@code spring.factories}
     * file must be in {@link 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. For example:
     *
     * <pre class="code">example.MyService=example.MyServiceImpl1,example.MyServiceImpl2</pre>
     *
     * where {@code example.MyService} is the name of the interface, and {@code MyServiceImpl1}
     * and {@code MyServiceImpl2} are two implementations.
     *
     * @author Arjen Poutsma
     * @author Juergen Hoeller
     * @author Sam Brannen
     * @since 3.2
     */
    public final class SpringFactoriesLoader {
    
    	/**
    	 * 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";
    
    
    	private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
    
    	private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
    
    
    	private SpringFactoriesLoader() {
    	}
    
    
    	/**
    	 * Load and instantiate the factory implementations of the given type from
    	 * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader.
    	 * <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
    	 * <p>If a custom instantiation strategy is required, use {@link #loadFactoryNames}
    	 * to obtain all registered factory names.
    	 * @param factoryClass the interface or abstract class representing the factory
    	 * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
    	 * @throws IllegalArgumentException if any factory implementation class cannot
    	 * be loaded or if an error occurs while instantiating any factory
    	 * @see #loadFactoryNames
    	 */
    	public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
    		Assert.notNull(factoryClass, "'factoryClass' must not be null");
    		ClassLoader classLoaderToUse = classLoader;
    		if (classLoaderToUse == null) {
    			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    		}
    		List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
    		if (logger.isTraceEnabled()) {
    			logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
    		}
    		List<T> result = new ArrayList<>(factoryNames.size());
    		for (String factoryName : factoryNames) {
    			result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
    		}
    		AnnotationAwareOrderComparator.sort(result);
    		return result;
    	}
    
    	/**
    	 * Load the fully qualified class names of factory implementations of the
    	 * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
    	 * class loader.
    	 * @param factoryClass 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<?> factoryClass, @Nullable ClassLoader classLoader) {
    		String factoryClassName = factoryClass.getName();
    		return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    	}
    .......
    
    

    https://www.jianshu.com/p/3a3edbcd8f24
    https://blog.csdn.net/x_iya/article/details/79954618
    http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html
    https://docs.oracle.com/javase/1.5.0/docs/guide/jar/jar.html#Service Provider

    注意:如果使用DataSource并指定了DriverClassName,则实验不生效

    @see
    com.zaxxer.hikari.util.DriverDataSource#DriverDataSource 
    java.sql.DriverManager#getDrivers
    
  • 相关阅读:
    Linux内核邮件列表发送和回复格式研究
    FastCopy包含和排除文件夹处理
    Linux解压命令(tar)
    Linux下的删除命令
    分区还原工具(DiskGenius)
    树莓派利用PuTTY进行远程登录
    树莓派下载地址及一些常用工具
    树莓派开机黑屏问题解决
    Jenkins从2.x新建Job时多了一个文件夹的功能(注意事项)
    Jenkins的Publish Over FTP Plugin插件参数使用
  • 原文地址:https://www.cnblogs.com/zhangww/p/12024759.html
Copyright © 2020-2023  润新知