JDK中「SPI」原理分析

基于【JDK1.8】

一、SPI简介

1、概念

SPI即service-provider-interface的简写;

JDK内置的服务提供加载机制,可以为服务接口加载实现类,解耦是其核心思想,也是很多框架和组件的常用手段;

2、入门案例

JDK中「SPI」原理分析

2.1 定义接口

就是普通的接口,在SPI的机制中称为【service】,即服务;

public interface Animal {     String animalName () ; } 

2.2 两个实现类

提供两个模拟用来测试,就是普通的接口实现类,在SPI的机制中称为【service-provider】即服务提供方;

CatAnimal实现类;

public class CatAnimal implements Animal {     @Override     public String animalName() {         System.out.println("Cat-Animal:布偶猫");         return "Ragdoll";     } } 

DogAnimal实现类;

public class DogAnimal implements Animal {     @Override     public String animalName() {         System.out.println("Dog-Animal:哈士奇");         return "husky";     } } 

2.3 配置文件

文件目录:在代码工程中创建META-INF.services文件夹;

文件命名:butte.program.basics.spi.inf.Animal,即全限定接口名称;

文件内容:添加相应实现类的全限定命名;

butte.program.basics.spi.impl.CatAnimal butte.program.basics.spi.impl.DogAnimal 

2.4 测试代码

通过ServiceLoader加载配置文件中指定的服务实现类,然后遍历并调用Animal接口方法,从而执行不同服务提供方的具体逻辑;

public class SpiAnaly {     public static void main(String[] args) {         ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class);         Iterator<Animal> animalIterator = serviceLoader.iterator();         while(animalIterator.hasNext()) {             Animal animal = animalIterator.next();             System.out.println("animal-name:" + animal.animalName());         }     } } 

结果输出

Cat-Animal:布偶猫 n animal-name:ragdoll Dog-Animal:哈士奇 n animal-name:husky 

二、原理分析

1、ServiceLoader结构

很显然,分析SPI机制的原理,从ServiceLoader源码中load方法切入即可,但是需要先从核心类的结构开始分析;

public final class ServiceLoader<S> implements Iterable<S> {     // 配置文件目录     private static final String PREFIX = "META-INF/services/";     // 表示正在加载的服务的类或接口     private final Class<S> service;     // 类加载器用来定位,加载,实例化服务提供方     private final ClassLoader loader;     // 创建ServiceLoader时采用的访问控制上下文     private final AccessControlContext acc;     // 按实例化的顺序缓存服务提供方     private LinkedHashMap<String,S> providers = new LinkedHashMap<>();     // 惰性查找迭代器     private LazyIterator lookupIterator;     /**      * service:表示服务的接口或抽象类      * loader: 类加载器      */     public static <S> ServiceLoader<S> load(Class<S> service) {         ClassLoader cl = Thread.currentThread().getContextClassLoader();         return ServiceLoader.load(service, cl);     }     /**      * ServiceLoader构造方法      */     private ServiceLoader(Class<S> svc, ClassLoader cl) {         loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;         acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;         reload();     }     public void reload() {         providers.clear();         // 实例化迭代器         lookupIterator = new LazyIterator(service, loader);     }     public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader) {         return new ServiceLoader<>(service, loader);     }      private class LazyIterator implements Iterator<S> {         // 服务接口         Class<S> service;         // 类加载器         ClassLoader loader;         // 实现类URL         Enumeration<URL> configs = null;         // 实现类全名         Iterator<String> pending = null;         // 下个实现类全名         String nextName = null;     } } 

断点截图:

JDK中「SPI」原理分析

2、iterator迭代方法

ServiceLoader类的迭代器方法中,实际使用的是LazyIterator内部类的方法;

public Iterator<S> iterator() {     return new Iterator<S>() {         Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();         public boolean hasNext() {             if (knownProviders.hasNext())                 return true;             return lookupIterator.hasNext();         }         public S next() {             if (knownProviders.hasNext())                 return knownProviders.next().getValue();             return lookupIterator.next();         }         public void remove() {             throw new UnsupportedOperationException();         }     }; } 

3、hasNextService方法

从上面迭代方法的源码中可知,最终执行的是LazyIterator#hasNextService判断方法,该方法通过解析最终会得到实现类的全限定名称;

private class LazyIterator implements Iterator<S> {     private boolean hasNextService() {         // 1、拼接名称         String fullName = PREFIX + service.getName();         // 2、加载资源文件         configs = loader.getResources(fullName);         // 3、解析文件内容         pending = parse(service, configs.nextElement());         nextName = pending.next();         return true;     } } 

断点截图:

JDK中「SPI」原理分析

4、nextService方法

迭代器的next方法最终执行的是LazyIterator#nextService获取方法,会基于上面hasNextService方法获取的实现类全限定名称,获取其Class对象,进而得到实例化对象,缓存并返回;

private class LazyIterator implements Iterator<S> {     private S nextService() {         // 1、通过全限定命名获取Class对象         String cn = nextName;         Class<?> c = Class.forName(cn, false, loader);         // 2、实例化对象         S p = service.cast(c.newInstance());         // 3、放入缓存并返回该对象         providers.put(cn, p);         return p;     } } 

断点截图:

JDK中「SPI」原理分析

三、SPI实践

1、Driver驱动接口

在JDK中提供了数据库驱动接口java.sql.Driver,无论是MySQL驱动包还是Druid连接池,都提供了该接口的实现类,通过SPI机制可以加载到这些驱动实现类;

public class DriverManager {     private static void loadInitialDrivers() {         AccessController.doPrivileged(new PrivilegedAction<Void>() {             public Void run() {                 ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(java.sql.Driver.class);                 Iterator<Driver> driversIterator = loadedDrivers.iterator();             }         });     } } 

断点截图:

JDK中「SPI」原理分析

2、Slf4j日志接口

SLF4J是门面模式的日志组件,提供了标准的日志服务SLF4JServiceProvider接口,在LogFactory日志工厂类中,负责加载具体的日志实现类,比如常用的Log4j或Logback日志组件;

public final class LoggerFactory {     static List<SLF4JServiceProvider> findServiceProviders() {         // 服务加载         ClassLoader classLoaderOfLoggerFactory = org.slf4j.LoggerFactory.class.getClassLoader();         // 重点看该方法:【getServiceLoader()】         ServiceLoader<SLF4JServiceProvider> serviceLoader = getServiceLoader(classLoaderOfLoggerFactory);         // 迭代方法         List<SLF4JServiceProvider> providerList = new ArrayList();         Iterator<SLF4JServiceProvider> iterator = serviceLoader.iterator();         while(iterator.hasNext()) {             safelyInstantiate(providerList, iterator);         }         return providerList;     } } 

断点截图:

JDK中「SPI」原理分析

四、参考源码

文档仓库: https://gitee.com/cicadasmile/butte-java-note  应用仓库: https://gitee.com/cicadasmile/butte-flyer-parent 

发表评论

评论已关闭。

相关文章