本文分类:
Java

Java SPI(Service Provider Interface,服务提供者接口)是一种服务发现机制,用于为某个接口寻找并实例化具体的实现。本文介绍 Java SPI 机制的用法并探究了其实现原理,本站曾在 2016 年写过 3 篇文章来介绍 SPI 并被多个网站转载,本文重新整理了这些内容。

SPI 示例

首先说明一下概念,SPI 的服务(Service)指的是一个熟知的接口或类(通常是一个抽象类)。服务提供者(Service Provider)是服务的具体实现,通过SPI机制,服务提供者能够以扩展的方式安装到 Java 平台。ServiceLoader 是 Java 为支持SPI提供的一个简单的服务提供者加载工具,本节通过一个简单的示例演示了 Java SPI 的用法,代码在github上。

定义服务

服务由单个类型表示,即一个接口或抽象类(具体的类也可以使用,但不推荐这么做),下面先定义一个示例服务,代码如下:

package com.hiwzc.java.spi;

public interface DemoService {
    void dosomething();
}

实现服务

服务提供者实现了指定的服务,下面的代码提供了一个示例服务的实现。

package com.hiwzc.java.spi;

public class DomainDemoServiceImpl implements DemoService {
    @Override
    public void dosomething() {
        System.out.println("www.hiwzc.com");
    }
}

配置服务提供者

服务提供者由 jar 包中资源目录 META-INF/services 下的配置文件来标识。配置文件的名称是服务类型的完全限定名,配置文件的内容是具体的服务提供者类的完全限定名称列表,每行一个服务提供者。例如,DomainDemoServiceImpl 的 jar 包中应该包含一个名为META-INF/services/com.hiwzc.java.spi.DemoService的文件,该文件内容为:

com.hiwzc.java.spi.DomainDemoServiceImpl

注释字符为井号“#”,每行里以 # 开始的所有字符将被忽略。空行和每个名称的首尾空白会被忽略。配置文件必须以 UTF-8 编码。

获取服务实例

服务实例通过 ServiceLoader 的 load 静态方法创建和返回,并能够通过返回的迭代器遍历所有已经发现的服务。示例代码如下,采用了 for-each 和迭代器的形式遍历了服务提供者:

package com.hiwzc.java.spi;

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

public class TestDemoService {
    public static void main(String[] args) {
        ServiceLoader<DemoService> providers = ServiceLoader.load(DemoService.class);

        // Use for-each
        for (DemoService service : providers) {
            service.dosomething();
        }

        // Use iterator
        Iterator<DemoService> it = providers.iterator();
        while (it.hasNext()) {
            DemoService service = it.next();
            service.dosomething();
        }
    }
}

运行测试程序,输出

www.hiwzc.com
www.hiwzc.com

ServiceLoader 以懒加载的方式定位和实例化服务提供者,ServiceLoader 缓存了所有已加载的服务提供者实例。每次调用 iterator 方法都返回一个迭代器,它首先以实例化顺序缓存所有已知的提供者,然后延迟定位和实例化其他剩余的提供者,可以通过 reload 方法清除缓存。

补充说明

下面对ServiceLoader类进行必要的补充说明,这些内容取自ServiceLoader的类注释:

ServiceLoader 源码分析

本节通过分析 ServiceLoader 类的源码来探究 Java SPI 机制的实现,代码取自 JDK 1.8.0_111。

类定义

ServiceLoader 类实现了 Iterable 接口,以便通过调用 iterator 方法获取遍历元素的迭代器。

public final class ServiceLoader<S> implements Iterable<S> {

成员变量

ServiceLoader 的成员变量如下:

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;

重新加载

reload 方法用于清空所有已实例化的服务提供者缓存,并重新加载所有的服务提供者。在调用此方法后,迭代器方法将以懒加载的方式从头重新查找和实例化服务提供者,就像新创建的 ServiceLoader 所做的那样,该方法用于新的服务提供者可以安装到正在运行的 Java 虚拟机中的情况。

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

配置解析失败处理方法

定义了三个用于失败时抛异常的重载方法,用于输出不同的信息。

private static void fail(Class<?> service, String msg, Throwable cause) throws ServiceConfigurationError {
    throw new ServiceConfigurationError(service.getName() + ": " + msg, cause);
}

private static void fail(Class<?> service, String msg) throws ServiceConfigurationError {
    throw new ServiceConfigurationError(service.getName() + ": " + msg);
}

private static void fail(Class<?> service, URL u, int line, String msg) throws ServiceConfigurationError {
    fail(service, u + ":" + line + ": " + msg);
}

解析配置

parseLine方法用于解析服务提供者配置文件中的一行,首先去掉注释,然后校验语法,最后保存合法的配置项,并返回下一行的行号,重复的配置项不会被保存,已经被实例化的服务提供者也不会保存。

private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names)
    throws IOException, ServiceConfigurationError {
    String ln = r.readLine();
    if (ln == null) {
        return -1;
    }
    int ci = ln.indexOf('#');
    if (ci >= 0) ln = ln.substring(0, ci);
    ln = ln.trim();
    int n = ln.length();
    if (n != 0) {
        if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
            fail(service, u, lc, "Illegal configuration-file syntax");
        int cp = ln.codePointAt(0);
        if (!Character.isJavaIdentifierStart(cp))
            fail(service, u, lc, "Illegal provider-class name: " + ln);
        for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
            cp = ln.codePointAt(i);
            if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                fail(service, u, lc, "Illegal provider-class name: " + ln);
        }
        if (!providers.containsKey(ln) && !names.contains(ln))
            names.add(ln);
    }
    return lc + 1;
}

解析配置文件

parse 方法调用上面的 parseLine 方法解析指定 URL 的配置文件,解析的结果是,配置文件中未被实例化的服务提供者名被缓存到 names 链表返回。

private Iterator<String> parse(Class<?> service, URL u)
    throws ServiceConfigurationError
{
    InputStream in = null;
    BufferedReader r = null;
    ArrayList<String> names = new ArrayList<>();
    try {
        in = u.openStream();
        r = new BufferedReader(new InputStreamReader(in, "utf-8"));
        int lc = 1;
        while ((lc = parseLine(service, u, r, lc, names)) >= 0);
    } catch (IOException x) {
        fail(service, "Error reading configuration file", x);
    } finally {
        try {
            if (r != null) r.close();
            if (in != null) in.close();
        } catch (IOException y) {
            fail(service, "Error closing configuration file", y);
        }
    }
    return names.iterator();
}

获取迭代器

iterator 方法返回遍历服务提供者的迭代器。该迭代器以懒加载的方式加载可用的服务提供者,该方法返回的迭代器首先按实例化顺序返回已经缓存的服务提供者实例(通过使用 knownProviders 迭代器),然后以懒加载的方式实例化其余的提供者并提交到缓存。此方法返回的迭代器不支持删除。调用其 remove 方法将导致抛出 UnsupportedOperationException。

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

    };
}

为了实现懒加载,解析配置文件和实例化服务提供者的工作必须由迭代器本身完成。因此,如果配置文件不符合指定的格式,或者配置了无法找到或实例化的提供者,或者实例化类的结果不能赋值给服务(指定的提供者不是服务的子类),或者是在查找或实例化下一个提供者时(指调用next方法)抛出任何其他种类的异常或错误,那么它的 hasNext 和 next 方法可能会抛出 ServiceConfigurationError 异常。

在这些情况下抛出异常似乎有点极端,但这种做法也有其合理之处,格式不正确的配置文件(就像格式不正确的类文件)通常意味着 Java 虚拟机配置或使用方式有严重的问题,因此,最好抛出一个错误,而不是试图恢复,更不是悄无声息地失败。要编写健壮的代码,只需要在使用迭代器时捕获 ServiceConfigurationError 异常。如果抛出这样的错误,则迭代器的后续调用将尽最大努力来定位和实例化下一个可用的提供者,但是一般来说,无法保证这种恢复。

LazyIterator 负责具体的实例化,实现如下:

private class LazyIterator implements Iterator<S>
{

    Class<S> service;
    ClassLoader loader;
    Enumeration<URL> configs = null;
    Iterator<String> pending = null;
    String nextName = null;

    private LazyIterator(Class<S> service, ClassLoader loader) {
        this.service = service;
        this.loader = loader;
    }

    private boolean hasNextService() {
        if (nextName != null) {
            return true;
        }
        if (configs == null) {
            try {
                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);
            }
        }
        while ((pending == null) || !pending.hasNext()) {
            if (!configs.hasMoreElements()) {
                return false;
            }
            pending = parse(service, configs.nextElement());
        }
        nextName = pending.next();
        return true;
    }

    private S nextService() {
        if (!hasNextService())
            throw new NoSuchElementException();
        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();          // This cannot happen
    }

    public boolean hasNext() {
        if (acc == null) {
            return hasNextService();
        } else {
            PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                public Boolean run() { return hasNextService(); }
            };
            return AccessController.doPrivileged(action, acc);
        }
    }

    public S next() {
        if (acc == null) {
            return nextService();
        } else {
            PrivilegedAction<S> action = new PrivilegedAction<S>() {
                public S run() { return nextService(); }
            };
            return AccessController.doPrivileged(action, acc);
        }
    }

    public void remove() {
        throw new UnsupportedOperationException();
    }

}

工厂方法

为指定的服务使用指定的 classloader 创建 service loader。

public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader)
{
    return new ServiceLoader<>(service, loader);
}

默认的load方法使用线程上下文类加载器为指定的类创建服务加载器。

public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

loadInstalled方法使用扩展类加载器为指定的服务创建服务加载器。该方法先获取到扩展类加载器,并返回ServiceLoader.load(service, extClassLoader)。所以,该方法只能找到并加载已安装到当前Java虚拟机中的服务提供者,应用程序类路径中的服务提供者将被忽略。

public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
    ClassLoader cl = ClassLoader.getSystemClassLoader();
    ClassLoader prev = null;
    while (cl != null) {
        prev = cl;
        cl = cl.getParent();
    }
    return ServiceLoader.load(service, prev);
}

toString

toString方法,很简单,返回 ServiceLoader 的名称。

public String toString() {
    return "java.util.ServiceLoader[" + service.getName() + "]";
}

应用示例 Dubbo

Dubbo 是 alibaba 开源的一个分布式服务框架,其自身的功能也是一种 SPI 实现,Dubbo 的所有功能点都可被用户自定义的扩展替换。Dubbo 的扩展点机制实际上就是一种加强的 JDK 的 SPI,本节简要说明Dubbo的SPI机制。

Dubbo 中可加载的服务称为扩展点,本文只分析 Dubbo 的 SPI 实现,暂不深入其他实现细节,此外,本节将不加区分地使用扩展点和服务这两个概念。本节的代码取自 dubbo-2.4.11。

Dubbo 和 ServiceLoader 的区别

Dubbo 和 ServiceLoader的不同点主要体现在以下几个方面:

  1. ServiceLoader 是采用迭代器遍历的方式实现的,而 Dubbo 为每种实现指定一个名称,由名称和服务共同确定一个实现,这样做的好处是,可以为成套的服务接口指定相同的名称,比如指定使用 dubbo 协议后,协议使用的其他扩展点就自动加载名称为 dubbo 的实现。此外,指定服务名称可以根据名称来获取扩展点实现实例,不像 ServiceLoader 那样在遍历过程中创建永远不会使用的服务实例。例如 com.alibaba.dubbo.rpc.Protocol 配置的内容:

     dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
    
  2. Dubbo 提供了一种类似 IoC 的机制,即一个扩展点可以直接 setter 注入其它扩展点。

  3. 基于线程安全和性能的考虑,Dubbo 采用了 ConcurrentMap 来缓存实现类的实例。

  4. Dubbo要求服务必须是一个接口。

  5. ServiceLoader 在解析配置出错时会抛出异常,如果捕获了这种异常,而不进行额外的处理,那么后面需要这种实例时,由于没有成功实例化,又会抛出新的异常,而新抛出的异常不能指示真正的错误原因。Dubbo 的实现是将解析配置时发生的异常保存起来,当访问这种实例时,通过查找保存的异常,抛出真正的原因。

Holder辅助类

Holder类用于保存一个值,并通过给值添加 volatile 来保证线程可见性,代码如下:

public class Holder<T> {
    private volatile T value;
    
    public void set(T value) {
        this.value = value;
    }
    
    public T get() {
        return value;
    }
}

扩展点注解

SPI注解

Dubbo首先定义了一个 SPI 注解,只有标记了该注解的服务,Dubbo SPI 机制才能为其加载具体实现。value 属性用于配置该服务的默认实现名称。SPI的代码如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
    /**
     * 缺省扩展点名。
     */
    String value() default "";
}

Adaptive注解

Adaptive 注解标注一个扩展点的 Adaptive 实现,一个扩展点最多只能有一个 Adaptive 实现。Adaptive 标注的实现不提供具体的功能,而是作为一个适配器,根据不同的情况选择具体的实现。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    String[] value() default {};
}

Activate注解

Activate注解用于配置扩展点实现的激活条件和排列顺序,比如 com.alibaba.dubbo.rpc.filter 包下的 Filter 实现基本都标注了这个注解,部分指定了 order 值,这样就能够控制过滤器在过滤器列表中的顺序,并能够控制在具体的情况下哪些过滤器会被激活。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {

    String[] group() default {};

    String[] value() default {};

    String[] before() default {};

    String[] after() default {};

    int order() default 0;
}

ExtensionLoader的静态成员

Dubbo 通过 ExtensionLoader 类实现SPI机制,和ServiceLoader类似,ExtensionLoader 也是一个单例工厂类,构造方法都是私有的,对外提供静态工厂类获取服务加载器的实例,与 ServiceLoader 不同的是,ExtensionLoader 会缓存所有的服务的 ExtensionLoader 实例。ExtensionLoader 的静态成员如下:

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>();

logger 是日志器;

SERVICES_DIRECTORY、DUBBO_DIRECTORY 和 DUBBO_INTERNAL_DIRECTORY 定义了3个配置文件查询目录,即META-INF/dubbo、META-INF/dubbo/internal 和 META-INF/services,ExtensionLoader 支持从这三个地方加载扩展点配置。

NAME_SEPARATOR 是用于分隔配置项名称的正则表达式,其含义是带前后空白的逗号。

EXTENSION_LOADERS 用于缓存所有扩展点的ExtensionLoader实例;

EXTENSION_INSTANCES 用于缓存所有扩展点实现的实例。

ExtensionLoader的实例成员

每个ExtensionLoader实例都包含如下成员变量:

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 volatile Class<?> cachedAdaptiveClass = null;

private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();

private String cachedDefaultName;

private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
private volatile Throwable createAdaptiveInstanceError;

private Set<Class<?>> cachedWrapperClasses;

private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();

type 成员记录了该加载器要加载的扩展点类型,即标注了SPI注解的接口;

objectFactory 是获取对象的工厂,本文暂不介绍对象工厂;

cachedNames 缓存扩展点实现的名称;

cachedClasses 缓存扩展点实现的类;

cachedActivates 缓存扩展点实现的Activate注解;

cachedAdaptiveClass 缓存该扩展点的Adaptive实现;

cachedInstances 缓存创建的扩展点实例;

cachedDefaultName 缓存该扩展点的默认实现的名称;

cachedAdaptiveInstance 保存该扩展点的Adaptive实例;

createAdaptiveInstanceError 保存创建该扩展点Adaptive实例的异常,在cachedAdaptiveInstance为空时,该字段同时也标志了是否创建过Adaptive实例;

cachedWrapperClasses 缓存该扩展点所有的包装器(Wrapper)类;

exceptions 保存了解析配置时发生的异常信息,当创建扩展点实例时,如果找不到扩展点实例类,就从这里查找并抛出原始的异常信息。

工厂方法

ExtensionLoader 对外提供的工厂方法是 getExtensionLoader,源码如下:

private static <T> boolean withExtensionAnnotation(Class<T> type) {
    return type.isAnnotationPresent(SPI.class);
}

@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    if (type == null)
        throw new IllegalArgumentException("Extension type == null");
    if(!type.isInterface()) {
        throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
    }
    if(!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type(" + type + 
                ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
    }
    
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

代码首先判断扩展点是否为空,是否是接口,是否标注了SPI注解,如果都满足,则查看该扩展点是否已经创建过加载器实例,如果没有,则调用构造方法创建一个加载器实例并缓存起来。

构造器

构造器代码如下,主要是保存了该加载器实例要加载的扩展点类,并创建了获取对象的对象工厂。

private ExtensionLoader(Class<?> type) {
    this.type = type;
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

获取扩展点实现

获取了 ExtensionLoader 实例后,通过调用 getExtension 方法获取指定名称的实例。代码如下:

@SuppressWarnings("unchecked")
public T getExtension(String name) {
    if (name == null || name.length() == 0)
        throw new IllegalArgumentException("Extension name == null");
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    Holder<Object> holder = cachedInstances.get(name);
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<Object>());
        holder = cachedInstances.get(name);
    }
    Object instance = holder.get();
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                instance = createExtension(name);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

该方法首先判断是否指定了扩展点名称,如果没有指定,则抛出异常,如果指定的名称为true,则返回默认的扩展点。然后查看缓存的实例中有没有指定的实现,如果没有,则创建指定的实现。如果有则直接返回缓存的实例。

创建扩展点实现

createExtension 读取配置并创建指定的扩展点实例,下面看一下createExtension的代码:

@SuppressWarnings("unchecked")
private T createExtension(String name) {
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        injectExtension(instance);
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && wrapperClasses.size() > 0) {
            for (Class<?> wrapperClass : wrapperClasses) {
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                type + ")  could not be instantiated: " + t.getMessage(), t);
    }
}

该方法首先查看缓存的扩展点实现类中有没有包含这个扩展,如果没有则报错,因为 ExtensionLoader 只解析一次并缓存所有的扩展点实现类,此行为是 getExtensionClasses 实现的。如果找到了扩展点实现类,则先从缓存EXTENSION_INSTANCES 中查看是否已经存在该实现类的实例化对象,如果没有找到,则创建新的实例并缓存到EXTENSION_INSTANCES 中,否则使用找到的实例,然后调用 injectExtension 方法注入该扩展点依赖的其他扩展实现,并为该实例创建所有包装类。

getExtensionClasses 方法的代码如下,该方法使用懒汉式的单例模式加载一次扩展点配置,主要的操作调用loadExtensionClasses 方法完成。

private Map<String, Class<?>> getExtensionClasses() {
    Map<String, Class<?>> classes = cachedClasses.get();
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

解析扩展点配置

ExtensionLoader 解析扩展点配置操作由 loadExtensionClasses 方法实现,其代码如下:

private Map<String, Class<?>> loadExtensionClasses() {
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if(defaultAnnotation != null) {
        String value = defaultAnnotation.value();
        if(value != null && (value = value.trim()).length() > 0) {
            String[] names = NAME_SEPARATOR.split(value);
            if(names.length > 1) {
                throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                        + ": " + Arrays.toString(names));
            }
            if(names.length == 1) cachedDefaultName = names[0];
        }
    }
    
    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    loadFile(extensionClasses, DUBBO_DIRECTORY);
    loadFile(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}

该方法首先通过扩展点的SPI注解获取其默认实现的名称,默认实现只能配置一个,代码通过校验默认名称中是否有分隔符来确认。如果有合法的默认实现名,则将默认实现名称缓存到 cachedDefaultName 成员变量中。然后调用 loadFile 依次从三个配置路径解析该加载器要加载的扩展点的配置。代码稍微有点长,层次很深,不便于阅读,就不贴代码了。

loadFile 方法首先调用 findClassLoader 得到 ExtensionLoader 类本身的类加载器,如果该加载器为空,则获取系统类加载器作为加载扩展点实现的类加载器,findClassLoader 代码如下:

private static ClassLoader findClassLoader() {
    return  ExtensionLoader.class.getClassLoader();
}

获取到合适的类加载器后,进一步获取类加载器可访问路径下的指定目录中的该扩展点的所有配置。

然后仍然是去注释,解析扩展点实现名称和实现类的全限定名。然后尝试加载这个类并校验该类是不是要加载的扩展点的子类。

然后看该实现是否标注了 Adaptive 注解,如果是,则将该类缓存到 cachedAdaptiveClass 中。一个扩展点只能有一个Adaptive 实现。

如果没有标注 Adaptive 注解,将尝试获取以该扩展点为唯一参数的构造方法,如果有这样一个构造方法,则说明该实现是一个包装器(Wrapper),然后将其缓存到 cachedWrapperClasses 成员变量中。如果没有获取到这样的构造方法,则尝试获取默认构造方法,非 wrapper 的扩展点实现必须有默认构造方法,否则将抛出异常。

然后就是处理该实现的名称,如果配置文件中没有指定名称,则依次分析 Extension 注解、类的 SimpleName 来作为名称,由于 Extension 注解已被标记为废弃,所以最好还是显式指定名称。同一个实现支持多个别名,用逗号分隔。然后就是看类是否标记了 Activate 注解,如果将名称和注解缓存到 cachedActivates 中。指定了别名的情况下,只为第一个名称缓存 Activate。最后,把名称和别名都缓存到 cachedNames 中,将类保存到 extensionClasses 中,至此,配置文件解析完成。

扩展点注入

扩展点注入的代码如下,首先查找以 set 开头、带一个参数的 public 方法,如果 set 方法的名称大于 3,则根据名称获取参数扩展点的名称,然后获取扩展点实现的实例,这可能又是一个此配置的解析和实例化过程。最后调用 set 方法将实例设置进去。

private T injectExtension(T instance) {
    try {
        if (objectFactory != null) {
            for (Method method : instance.getClass().getMethods()) {
                if (method.getName().startsWith("set")
                        && method.getParameterTypes().length == 1
                        && Modifier.isPublic(method.getModifiers())) {
                    Class<?> pt = method.getParameterTypes()[0];
                    try {
                        String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) {
                        logger.error("fail to inject via method " + method.getName()
                                + " of interface " + type.getName() + ": " + e.getMessage(), e);
                    }
                }
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}
本文来自 [时光记 - 王智超的个人空间](www.hiwzc.com),转载请注明出处。