首页 后端 正文

Dubbo的SPI机制简介

简介

SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的,它可以用来启用框架扩展和替换组件。

我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块、xml解析模块、jdbc模块等方案。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

Java的SPI机制: ServiceLoader

用法

// java.sql.Driver
// 1.new一个ServiceLoader
ServiceLoader<Driver> drivers = ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = drivers.iterator();
// 2.调用延迟加载器的hashNext
while (iterator.hasNext()) {
  // 3.调用延迟加载器的next
  Driver next = iterator.next();
  System.out.println(next.getClass());
}

// result:
class com.mysql.jdbc.Driver
class com.mysql.fabric.jdbc.FabricMySQLDriver
class com.alibaba.druid.proxy.DruidDriver
class com.alibaba.druid.mock.MockDriver

原理

ServiceLoader并不会在load的时候去加载所有 接口对应实现类的文件。而是在执行接口的时候去遍历加载。

ServiceLoader的核心属性

// 接口全限定名的文件的路径前缀
private static final String PREFIX = "META-INF/services/";

// 被加载的接口
private final Class<S> service;

// 类的加载器
private final ClassLoader loader;

// 创建serviceLoader时 访问控制上下文
private final AccessControlContext acc;

// 服务缓存池(k-> 类全报名  v-> 实现类信息)
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// 延迟加载的迭代器
private LazyIterator lookupIterator;

LazyIterator的核心属性

// 接口类型
Class<S> service;
// 接的加载器
ClassLoader loader;
// META-INF/services/内配置文件的URL集合
Enumeration<URL> configs = null;
// 需加载的实现类的全包名集合
Iterator<String> pending = null;
// 下一个实现类的全报名,用于迭代器延迟加载的下一个类
String nextName = null;

源码解析

1、load方法

// 1、load方法
public static <S> ServiceLoader<S> load(Class<S> service) {
  // 获取当前线程的类加载器
  ClassLoader cl = Thread.currentThread().getContextClassLoader();
  // 继续调用重载的方法
  return ServiceLoader.load(service, cl);
}

// 2、调用重载方法
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
  // new 一个ServiceLoader对象
  return new ServiceLoader<>(service, loader);
}

// 3、new 一个serviceLoader对象
private ServiceLoader(Class<S> svc, ClassLoader cl) {
  // PS:服务接口不能为空
  service = Objects.requireNonNull(svc, "Service interface cannot be null");
  // 指定加载器
  loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
  // 指定下上容器(默认为null)
  acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
  // 刷新:1.重置服务缓冲池 2.new一个延迟加载的迭代器
  reload();
}

// 4、刷新  
public void reload() {
  // 重置服务缓存池
  providers.clear();
  // 构造一个延迟加载的迭代器
  lookupIterator = new LazyIterator(service, loader);
}

// 5、new LazyIterator
private LazyIterator(Class<S> service, ClassLoader loader) {
  // 指定接口的类信息
  this.service = service;
  // 指定类加载器
  this.loader = loader;
}

2、获取延迟加载的迭代器

3、调用延迟加载迭代器的hasNext()方法

// 1、调用hasNext()方法
public boolean hasNext() {
  // 默认 acc为null
  if (acc == null) {
    // 执行hasNextService()方法
    return hasNextService();
  } else {
    PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
      public Boolean run() { return hasNextService(); }
    };
    return AccessController.doPrivileged(action, acc);
  }
}

// 2、调用hasNextService()方法
private boolean hasNextService() {
  // 默认第一次执行 nextName为null
  if (nextName != null) {
    return true;
  }
  // 默认第一次执行 configs为null, 通过类加载器加载类的全报名,然后获取URL。
  if (configs == null) {
    try {
      // "META-INF/services/" + java.sql.Driver
      // 1.file:/Users/zhouzeng/.m2/repository/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar!/META-INF/services/java.sql.Driver
      // 2.file:/Users/zhouzeng/.m2/repository/com/alibaba/druid/1.0.28/druid-1.0.28.jar!/META-INF/services/java.sql.Driver
      String fullName = PREFIX + service.getName();
      if (loader == null) {
        configs = ClassLoader.getSystemResources(fullName);
      } else {
        configs = loader.getResources(fullName);
      }
    } catch (IOException x) {
      fail(service, "Error locating configuration files", x);
    }
  }
  // 默认第一次执行 pending为null
  while ((pending == null) || !pending.hasNext()) {
    if (!configs.hasMoreElements()) {
      return false;
    }
    // 根据 接口+ URL 解析 
    /**
     * 1.0 = "com.mysql.jdbc.Driver" 1 = "com.mysql.fabric.jdbc.FabricMySQLDriver"
     * 2.0 = "com.alibaba.druid.proxy.DruidDriver" 1 = "com.alibaba.druid.mock.MockDriver"
     **/
    pending = parse(service, configs.nextElement());
  }
  // com.mysql.jdbc.Driver
  nextName = pending.next();
  return true;
}

4、next()方法

// 1.执行next方法
public S next() {
  // 默认 acc为null
  if (acc == null) {
    // 调用nexrService()方法
    return nextService();
  } else {
    PrivilegedAction<S> action = new PrivilegedAction<S>() {
      public S run() { return nextService(); }
    };
    return AccessController.doPrivileged(action, acc);
  }
}

// 2.执行nextService()方法
private S nextService() {
  // 判断是否还有下一个类需要加载
  if (!hasNextService()) {
    throw new NoSuchElementException();  
  }
  // 1.com.mysql.jdbc.Driver
  // 2.com.mysql.fabric.jdbc.FabricMySQLDriver
  // 3.com.alibaba.druid.proxy.DruidDriver
  // 4.com.alibaba.druid.mock.MockDriver
  String cn = nextName;
  nextName = null;
  Class<?> c = null;
  try {
    // 生成接口的实现类
    c = Class.forName(cn, false, loader);
  } catch (ClassNotFoundException x) {
    fail(service, "Provider " + cn + " not found");
  }
  // 校验实现类 是不是接口的实现类
  if (!service.isAssignableFrom(c)) {
    fail(service,  "Provider " + cn  + " not a subtype");
  }
  try {
    // 将实现类的实例,转化为接口类型(类型转化)
    S p = service.cast(c.newInstance());
    // 放到缓存池中
    providers.put(cn, p);
    return p;
  } catch (Throwable x) {
    fail(service, "Provider " + cn + " could not be instantiated", x);
  }
  throw new Error();
}

优缺点

优点:将业务代码和具体实现类解耦,方便扩展。如需增加新逻辑,无需修改主流程,直接在PI配置文件增加实现类的全限定名即可。

缺点:颗粒度不够细,无法准确定位某一个实现类。要执行就执行所有的实现类。

SpringBoot 之SPI机制:SpringFactoriesLoader

应用

1.创建一个enableConfiguration

@Configuration
@EnableConfigurationProperties({MybatisProperties.class})
public class MybatisAutoConfiguration {
    
}

2.在resources下定义META-INF/spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

3.用SpringBoot启动

@SpringBootApplication
public class Application extends SpringBootServletInitializer {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    } 
}

在springbot启动时回去加载MybatisAutoConfiguration这个类。

原理

1、在SpringBoot启动时,在refreshContext()时,回去调用@Import的selector。

2、然后执行 AutoConfigurationImportSelector的process()方法 获取对应的configuration的List。

3、由Spring 去加载实例化configuration的配置类。

// 1.SpringBootApplication中的SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};

    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};


    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

}

// 2.SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

// 3.查看 AutoConfigurationImportSelector的selectImports()方法
public String[] selectImports(AnnotationMetadata annotationMetadata) {
  if (!this.isEnabled(annotationMetadata)) {
    return NO_IMPORTS;
  } else {
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
    AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
    // 获取enableautoconfiguration相关的类
    List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
    configurations = this.removeDuplicates(configurations);
    Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
    this.checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = this.filter(configurations, autoConfigurationMetadata);
    this.fireAutoConfigurationImportEvents(configurations, exclusions);
    return StringUtils.toStringArray(configurations);
  }
}
// 4. 执行getCandidateConfigurations()方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, 
                                                  AnnotationAttributes attributes) {
  // 获取configurations
  List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
                                                                this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
  Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
  return configurations;
}

// 5. 执行loadFactoryNames()方法
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
  // org.springframework.boot.autoconfigure.EnableAutoConfiguration
  String factoryClassName = factoryClass.getName();
  // 加载spring.fatories的配置项,然后获取key为EnableAutoConfiguration的value
  return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

SpringFactoriesLoader的核心属性

// 接口全限定名的文件的路径前缀
private static final String PREFIX = "META-INF/services/";

// 被加载的接口
private final Class<S> service;

// 类的加载器
private final ClassLoader loader;

// 创建serviceLoader时 访问控制上下文
private final AccessControlContext acc;

// 服务缓存池(k-> 类全报名  v-> 实现类信息)
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// 延迟加载的迭代器
private LazyIterator lookupIterator;0

// 接口全限定名的文件的路径前缀
private static final String PREFIX = "META-INF/services/";

// 被加载的接口
private final Class<S> service;

// 类的加载器
private final ClassLoader loader;

// 创建serviceLoader时 访问控制上下文
private final AccessControlContext acc;

// 服务缓存池(k-> 类全报名  v-> 实现类信息)
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// 延迟加载的迭代器
private LazyIterator lookupIterator;1

Dubbo的SPI机制

用法

代码

// 接口全限定名的文件的路径前缀
private static final String PREFIX = "META-INF/services/";

// 被加载的接口
private final Class<S> service;

// 类的加载器
private final ClassLoader loader;

// 创建serviceLoader时 访问控制上下文
private final AccessControlContext acc;

// 服务缓存池(k-> 类全报名  v-> 实现类信息)
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// 延迟加载的迭代器
private LazyIterator lookupIterator;2

配置

在META-INF/dubbo目录下添加配置文件(com.example.spidemo.dubbo.base.Order)

// 接口全限定名的文件的路径前缀
private static final String PREFIX = "META-INF/services/";

// 被加载的接口
private final Class<S> service;

// 类的加载器
private final ClassLoader loader;

// 创建serviceLoader时 访问控制上下文
private final AccessControlContext acc;

// 服务缓存池(k-> 类全报名  v-> 实现类信息)
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// 延迟加载的迭代器
private LazyIterator lookupIterator;3

测试类

// 接口全限定名的文件的路径前缀
private static final String PREFIX = "META-INF/services/";

// 被加载的接口
private final Class<S> service;

// 类的加载器
private final ClassLoader loader;

// 创建serviceLoader时 访问控制上下文
private final AccessControlContext acc;

// 服务缓存池(k-> 类全报名  v-> 实现类信息)
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// 延迟加载的迭代器
private LazyIterator lookupIterator;4

流程

// 接口全限定名的文件的路径前缀
private static final String PREFIX = "META-INF/services/";

// 被加载的接口
private final Class<S> service;

// 类的加载器
private final ClassLoader loader;

// 创建serviceLoader时 访问控制上下文
private final AccessControlContext acc;

// 服务缓存池(k-> 类全报名  v-> 实现类信息)
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// 延迟加载的迭代器
private LazyIterator lookupIterator;5

ExtensionLoader的核心属性

// 接口全限定名的文件的路径前缀
private static final String PREFIX = "META-INF/services/";

// 被加载的接口
private final Class<S> service;

// 类的加载器
private final ClassLoader loader;

// 创建serviceLoader时 访问控制上下文
private final AccessControlContext acc;

// 服务缓存池(k-> 类全报名  v-> 实现类信息)
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// 延迟加载的迭代器
private LazyIterator lookupIterator;6

源码解析

ExtensionLoader.getExtensionLoader(Order.class);

流程

Dubbo的SPI机制简介  第1张

代码解析
// 接口全限定名的文件的路径前缀
private static final String PREFIX = "META-INF/services/";

// 被加载的接口
private final Class<S> service;

// 类的加载器
private final ClassLoader loader;

// 创建serviceLoader时 访问控制上下文
private final AccessControlContext acc;

// 服务缓存池(k-> 类全报名  v-> 实现类信息)
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// 延迟加载的迭代器
private LazyIterator lookupIterator;7

其实我们不难发现上述只是加载的我们SPI对应ExtensionLoader的ExtensionFactory的getAdaptiveExtension()

--> AdaptiveExtensionFactory

1.常用方法extensionLoader.getExtension("alipay");

流程

Dubbo的SPI机制简介  第2张

代码解析
// 接口全限定名的文件的路径前缀
private static final String PREFIX = "META-INF/services/";

// 被加载的接口
private final Class<S> service;

// 类的加载器
private final ClassLoader loader;

// 创建serviceLoader时 访问控制上下文
private final AccessControlContext acc;

// 服务缓存池(k-> 类全报名  v-> 实现类信息)
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// 延迟加载的迭代器
private LazyIterator lookupIterator;8

2.常用方法loader.getAdaptiveExtension();

流程

Dubbo的SPI机制简介  第3张

代码解析
// 接口全限定名的文件的路径前缀
private static final String PREFIX = "META-INF/services/";

// 被加载的接口
private final Class<S> service;

// 类的加载器
private final ClassLoader loader;

// 创建serviceLoader时 访问控制上下文
private final AccessControlContext acc;

// 服务缓存池(k-> 类全报名  v-> 实现类信息)
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// 延迟加载的迭代器
private LazyIterator lookupIterator;9

3.常用方法loader.getActivateExtension(url, "", "online");课后讨论:

// 接口类型
Class<S> service;
// 接的加载器
ClassLoader loader;
// META-INF/services/内配置文件的URL集合
Enumeration<URL> configs = null;
// 需加载的实现类的全包名集合
Iterator<String> pending = null;
// 下一个实现类的全报名,用于迭代器延迟加载的下一个类
String nextName = null;0

总结

Dubbo 的SPI的优势:

  1. 有缓存(都有缓存)

  2. 可以结合Spring容器实现属性注入。

  3. 通过wrapper 装饰器实现类似AOP的功能。

  4. 通过@Adaptive 配置适配器的类,就支持一个场景使用多种实现类。

打赏
海报

本文转载自互联网,旨在分享有价值的内容,文章如有侵权请联系删除,部分文章如未署名作者来源请联系我们及时备注,感谢您的支持。

转载请注明本文地址:https://www.shouxicto.com/article/6302.html

-->

相关推荐

什么是前端什么是后端?

什么是前端什么是后端?

SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的,它可以用来启用框架扩展和...

后端 2023.07.03 0 732

支付宝
微信
赞助本站