深入理解SPI机制

深入理解SPI机制

一、什么是SPI
SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。

Java的SPI类似于IOC的功能,将装配的控制权移到了程序之外,实现在模块装配的时候不用在程序中动态指明。所以SPI的核心思想就是解耦,这在模块化设计中尤其重要。

正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。

在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。

二、使用示例

文件架构:

image-20201016112659182

interface

package com.mt.spitest.interf;

/**
 * @Auther: zhe.xiao
 * @Date: 2020/10/16
 * @Description
 */
public interface Address {
    void show();
}

service

package com.mt.spitest.impl;

import com.mt.spitest.interf.Address;

/**
 * @Auther: zhe.xiao
 * @Date: 2020/10/16
 * @Description
 */
public class CompanyAddress implements Address {
    @Override
    public void show() {
        System.out.println("公司地址");
    }
}

package com.mt.spitest.impl;

import com.mt.spitest.interf.Address;

/**
 * @Auther: zhe.xiao
 * @Date: 2020/10/16
 * @Description
 */
public class HomeAddress implements Address {

    @Override
    public void show() {
        System.out.println("家庭地址");
    }
}

resources/META-INF/services/com.mt.spitest.interf.Address

com.mt.spitest.impl.CompanyAddress
com.mt.spitest.impl.HomeAddress

main

package com.mt.spitest;

import com.mt.spitest.interf.Address;

import java.util.Iterator;
import java.util.ServiceLoader;

/**
 * @Auther: zhe.xiao
 * @Date: 2020/10/16
 * @Description
 */
public class T1 {
    public static void main(String[] args) {
        ServiceLoader<Address> addressServiceLoader = ServiceLoader.load(Address.class);
        Iterator<Address> addressIterator = addressServiceLoader.iterator();

        while (addressIterator.hasNext()){
            Address address = addressIterator.next();
            address.show();
        }
    }
}

打印

公司地址
家庭地址

三、spring加载spring.factories的源码

Spring SPI:

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() {
   }


   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;
   }


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

   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 factoryClassName = ((String) entry.getKey()).trim();
               for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                  result.add(factoryClassName, factoryName.trim());
               }
            }
         }
         cache.put(classLoader, result);
         return result;
      }
      catch (IOException ex) {
         throw new IllegalArgumentException("Unable to load factories from location [" +
               FACTORIES_RESOURCE_LOCATION + "]", ex);
      }
   }

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

}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值