1. 什么是SPI
SPI 是英文Service Provider Interface的缩写。中文意思是服务提供商接口。满足某种服务标准的供应商提供的符合该标准的应用程序接口,SPI应该和该服务的API标准是兼容的,应用程序一般应该是基于API编写,除非是SPI中包含API中没有提供的功能而又必须使用。
2. Java中的SPI机制
2.1 定义
在jdk6里面引进的一个新的特性ServiceLoader,从官方的文档来说,它主要是用来装载一系列的service provider。而且ServiceLoader可以通过service provider的配置文件来装载指定的service provider。当服务的提供者,提供了服务接口的一种实现之后,我们只需要在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
2.2 Demo示例
接口以及实现类
package spi;
public interface Animal {
void hello();
}
package spi;
public class Dog implements Animal {
@Override
public void hello() {
System.out.println("This is Dog");
}
}
package spi;
public class Cat implements Animal {
@Override
public void hello() {
System.out.println("This is Cat");
}
}
测试类
package spi;
import java.util.Iterator;
import java.util.ServiceLoader;
public class TestSPI {
public static void main(String[] args) {
ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class);
Iterator<Animal> iterator = serviceLoader.iterator();
while (iterator.hasNext()){
Animal animal = iterator.next();
animal.hello();
}
}
}
resources目录下新建目录META-INF.services,在该目录下新建文件spi.Animal
spi.Cat
spi.Dog
启动测试类,最终输出
This is Cat
This is Dog
2.3 源码分析
整体过程:
- 根据接口名Animal从META-INF.services中找到文件spi.Animal
- 解析该文件,得到所有的实现类的全限定名
- 循环加载实现类
- 根据名称通过反射创建实现类的实例
- 放入缓存
2.3.1 ServiceLoader.load() 方法
public static <S> ServiceLoader<S> load(Class<S> service) {
//获取当前线程类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
2.3.2 ServiceLoader构造方法
最终调用构造方法
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();
}
2.3.3 reload()方法
public void reload() {
providers.clear();
//初始化LazyIterator
lookupIterator = new LazyIterator(service, loader);
}
2.3.4 serviceLoader.iterator()方法
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();
}
};
}
2.3.5 hasNext()方法
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);
}
}
2.3.6 hasNextService()方法
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//从META-INF.services中获取名称
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;
}
2.3.7 lookupIterator.next()方法
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);
}
}
2.3.8 nextService()方法
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
}
至此所有源码分析完毕。
2.4 数据库驱动
DriverManager是jdbc里管理和注册不同数据库driver的工具类。从它设计的初衷来看,和我们前面讨论的场景有相似之处。针对一个数据库,可能会存在着不同的数据库驱动实现。我们在使用特定的驱动实现时,不希望修改现有的代码,而希望通过一个简单的配置就可以达到效果。
我们在运用Class.forName("com.mysql.jdbc.Driver")加载mysql驱动后,就会执行其中的静态代码把driver注册到DriverManager中,以便后续的使用。
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
查看DriverManager的静态代码块
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
loadInitialDrivers()方法中有这么一行
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
可见,DriverManager初始化时也运用了spi的思想,使用ServiceLoader把写到配置文件里的Driver都加载了进来。
3. Dubbo中的SPI机制
3.1 为什么Dubbo不用Java写好的SPI
见过上边Java的SPI例子,我们需要思考一个问题,Java的实现有什么不足之处?
Java的SPI机制是获取文件中的所有内容,太死板,不支持指定获取,如果我们只想获取某一个实现类,Java中的SPI就不适用了,那么思考一下,如果需要指定获取实现类需要怎么做呢?
最简单的做法就是这样定义内容
dog = spi.Dog
cat = spi.Cat
然后把这些内容加载到一个Map中,根据名称获取,那么Dubbo是怎么做的呢?让我们深入源码分析一下。
3.2 Demo
先看一个Dubbo SPI简单Demo
创建一个项目,依赖如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>DubboDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.5</version>
</dependency>
</dependencies>
</project>
新建接口及实现类
package com.dubbo.spi;
import org.apache.dubbo.common.extension.SPI;
@SPI
public interface Animal {
void hello();
}
package com.dubbo.spi;
public class Cat implements Animal {
public void hello() {
System.out.println("This is Cat");
}
}
package com.dubbo.spi;
public class Dog implements Animal {
public void hello() {
System.out.println("This is Dog");
}
}
在resources目录下,新建META-INF目录,在该目录下创建dubbo目录,新建文件 com.dubbo.spi.Animal
文件内容为:
dog = com.dubbo.spi.Dog
cat = com.dubbo.spi.Cat
测试类
package com.dubbo.spi;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class TestDubboSPI {
public static void main(String[] args) {
ExtensionLoader<Animal> extensionLoader =
ExtensionLoader.getExtensionLoader(Animal.class);
Animal dog = extensionLoader.getExtension("dog");
dog.hello();
}
}
测试结果
This is Dog
4. 源码解析
4.1 Dubbo中关于目录的定义
我们先来看一下 Dubbo 对配置文件目录的约定,不同于 Java SPI ,Dubbo 分为了三类目录。
- META-INF/services/ 目录:该目录下的 SPI 配置文件是为了用来兼容 Java SPI 。
- META-INF/dubbo/ 目录:该目录存放用户自定义的 SPI 配置文件。
- META-INF/dubbo/internal/ 目录:该目录存放 Dubbo 内部使用的 SPI 配置文件。
4.2 源码入口
来看上面的Demo
ExtensionLoader<Animal> extensionLoader =
ExtensionLoader.getExtensionLoader(Animal.class);
Animal dog = extensionLoader.getExtension("dog");
按照之前Java SPI的源码套路,这两行的作用就是,先根据接口名生成一个ExtensionLoader对象,在根据实现类的名字,实例化实现类。
4.3 ExtensionLoader.getExtensionLoader()方法
//缓存存放接口名和ExtensionLoader类的映射,Animal -> ExtensionLoader<Animal>
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
//省略一些校验逻辑
//从缓存中获取ExtensionLoader<T>对象
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
//如果缓存为空,就创建ExtensionLoader<T>,并放入缓存
if (loader == null) {
//创建并放入缓存
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
//从缓存中获取ExtensionLoader<T>
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
//返回ExtensionLoader<T>
return loader;
}
4.4 ExtensionLoader.getExtension()方法
由于是第一次创建实例,所以会走createExtension逻辑
public T getExtension(String name) {
//校验Name
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
//返回默认实例
if ("true".equals(name)) {
return getDefaultExtension();
}
//从缓存中获取目标对象
final Holder<Object> holder = getOrCreateHolder(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;
}
4.5 createExtension()方法
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, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
截止到目前的流程如下:
那么getExtensionClasses()方法是怎么生成class的呢?
4.5.1 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;
}
4.5.2 loadExtensionClasses()方法
这里就从我们上面提到的那三个目录中加载文件。
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// internal extension load from ExtensionLoader's ClassLoader first
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName(), true);
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"), true);
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
4.5.3 loadDirectory()方法
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst) {
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls = null;
ClassLoader classLoader = findClassLoader();
// try to load from ExtensionLoader's ClassLoader first
if (extensionLoaderClassLoaderFirst) {
ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
urls = extensionLoaderClassLoader.getResources(fileName);
}
}
if(urls == null || !urls.hasMoreElements()) {
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
//具体加载逻辑
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
4.5.4 loadResource()方法
这个方法最终会调用loadClass方法。
4.5.6 loadClass()方法
这里去除一些不必要逻辑
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
//如果有Adaptive注解就存入对应缓存里
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz);
} else if (isWrapperClass(clazz)) { //如果是WrapperClass就记录对应缓存里
cacheWrapperClass(clazz);
} else { //普通类逻辑
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
//如果有Activate.class注解,就存入对应缓存里
cacheActivateClass(clazz, names[0]);
for (String n : names) {
//放到一个缓存里
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, n);
}
}
}
}
完善流程图如下:
那么什么是Adaptive注解呢?
4.6 Adaptive注解
还记得上边的SPI例子吧,这个例子有一个问题,就是在启动的时候所有的调用都已经固定了,假设现在有这么一个需求,接口类是Pay,有两个实现类,一个AliPay,一个WeChatPay,我们要通过传进来的参数,动态的决定要访问哪个实现类,那么以前的SPI实现就不能满足了,而Adaptive注解可以实现这个功能。
这个注解就是自适应扩展相关的注解,可以修饰类和方法上,在修饰类的时候不会生成代理类,因为这个类就是代理类,修饰在方法上的时候会生成代理类。
4.6.1 使用
这里举一个注解在方法上的例子,如下所示:
package com.dubbo.spi.auto;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
import org.apache.dubbo.common.URL;
//默认使用aliPay
@SPI("ali")
public interface Pay {
//根据payType参数,动态决定调用哪个实现类
@Adaptive({"payType"})
void pay(URL url);
}
package com.dubbo.spi.auto;
import org.apache.dubbo.common.URL;
public class AliPay implements Pay {
public void pay(URL url) {
System.out.println("User AliPay");
}
}
package com.dubbo.spi.auto;
import org.apache.dubbo.common.URL;
public class WeChatPay implements Pay {
public void pay(URL url) {
System.out.println("Use WeChat");
}
}
在META-INF.dubbo中添加这个文件com.dubbo.spi.auto.Pay,内容为:
wechat = com.dubbo.spi.auto.WeChatPay
ali = com.dubbo.spi.auto.AliPay
测试类
package com.dubbo.spi.auto;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class TestAutoSPI {
public static void main(String[] args) {
ExtensionLoader<Pay> extensionLoader = ExtensionLoader.getExtensionLoader(Pay.class);
Pay pay = extensionLoader.getAdaptiveExtension();
pay.pay(URL.valueOf("http://localhost:8080/"));
pay.pay(URL.valueOf("http://localhost:8080?payType=wechat"));
}
}
最终输出
User AliPay
Use WeChat
成功!!!!
4.6.2 源码分析
4.6.2.1 注解在类上
注解在类上的调用逻辑比较简单 createAdaptiveExtension() -> createAdaptiveExtension() -> getAdaptiveExtensionClass() -> getExtensionClasses(),到这里跟上边逻辑一样了。
4.6.2.2 注解在方法上
注解在方法上有一点不同,就是最后不会调用getExtensionClasses(),而是调用createAdaptiveExtensionClass()生成代理类
private Class<?> createAdaptiveExtensionClass() {
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
ClassLoader classLoader = findClassLoader();
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
上面的例子中,最终会生成这样一个代理类,根据请求的参数,即 URL 得到具体要调用的实现类名,然后再调用 getExtension 获取。
package com.dubbo.spi.auto;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Pay$Adaptive implements com.dubbo.spi.auto.Pay {
public void pay(org.apache.dubbo.common.URL arg0) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("payType", "ali");
if(extName == null)
throw new IllegalStateException("Failed to get extension (com.dubbo.spi.auto.Pay) name from url (" + url.toString() + ") use keys([payType])");
com.dubbo.spi.auto.Pay extension = (com.dubbo.spi.auto.Pay)ExtensionLoader.getExtensionLoader(
com.dubbo.spi.auto.Pay.class).getExtension(extName);
extension.pay(arg0);
}
}
4.7 WrapperClass - AOP
看源码的时候,注意到有一个判断是该类是不是Wrapper,这个功能是Dubbo中的AOP功能,下边举一个简单的例子来看如何使用。
4.7.1 使用
还是之前那个Animal的例子,新建一个AnimalWrapper类,实现Animal接口
package com.dubbo.spi.wrapper;
import com.dubbo.spi.Animal;
public class AnimalWrapper implements Animal {
private final Animal animal;
public AnimalWrapper(Animal animal) {
this.animal = animal;
}
public void hello() {
System.out.println("before");
animal.hello();
System.out.println("after");
}
}
在com.dubbo.spi.Animal文件中新加一行:
wrapper = com.dubbo.spi.wrapper.AnimalWrapper
启动类:
package com.dubbo.spi.wrapper;
import com.dubbo.spi.Animal;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class TestWrapper {
public static void main(String[] args) {
ExtensionLoader<Animal> extensionLoader = ExtensionLoader.getExtensionLoader(Animal.class);
Animal wrapper = extensionLoader.getExtension("cat");
wrapper.hello();
}
}
输出为:
before
This is Dog
after
看到这个结果,不妨猜一下,Dubbo是怎么实现的。
- 从文件中获取AnimalWrapper类
- 在创建实例的时候,通过AnimalWrapper的构造方法进行创建,并把Cat类作为参数传入构造方法中,这样AnimalWrapper中的Animal实例就是Cat,接着创建AnimalWrapper实例
- 最终调用AnimalWrapper.hello()方法
4.7.2 源码解析
4.7.2.1 isWrapperClass()方法
该方法用来判断这个类是不是Wrapper类,如果一个类的构造方法的参数是给定type,就是Wrapper类,对于本例来说,就是看AnimalWrapper类的构造方法的参数是不是Animal接口。
private boolean isWrapperClass(Class<?> clazz) {
try {
clazz.getConstructor(type);
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
4.7.2.2 cacheWrapperClass()方法
先从缓存里拿Wrapper类,缓存里没有就重新创建,这里是把AnimalWrapper类加入到缓存里。
private void cacheWrapperClass(Class<?> clazz) {
if (cachedWrapperClasses == null) {
cachedWrapperClasses = new ConcurrentHashSet<>();
}
cachedWrapperClasses.add(clazz);
}
4.7.2.3 创建实例
这几行代码就是最终创建AnimalWrapper类的实例,并返回,至此Wrapper类的逻辑也分析完了。
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
4.8 injectExtension - IOC
就差最后这部分代码了。
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try { //找到Setter方法
for (Method method : instance.getClass().getMethods()) {
if (!isSetter(method)) {
continue;
}
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
//找到Setter方法
String property = getSetterProperty(method);
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
//执行set方法注入依赖
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
至此所有代码分析完毕。