简介
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);
流程
代码解析
// 接口全限定名的文件的路径前缀 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");
流程
代码解析
// 接口全限定名的文件的路径前缀 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();
流程
代码解析
// 接口全限定名的文件的路径前缀 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的优势:
有缓存(都有缓存)
可以结合Spring容器实现属性注入。
通过wrapper 装饰器实现类似AOP的功能。
通过@Adaptive 配置适配器的类,就支持一个场景使用多种实现类。