文章目录
初探java spi
java spi,dubbo spi
Java SPI的缺点:
- 不支持按需加载,迭代器遍历会实例化所有的实现类,即使它没有被用到,太浪费资源了。
- 获取实现类的方式不灵活,只能通过迭代器遍历。
- 没有缓存,实现类会被多次创建。
- 扩展加载失败,失败原因丢失,不方便排查。
- 不支持AOP和IOC。
Dubbo SPI定义了一套自己的规范,同时对Java SPI存在的问题进行了改进,优点如下:
- 扩展类按需加载,节约资源。
- SPI文件采用Key=Value形式,可以根据扩展名灵活获取实现类。
- 扩展类对象做了缓存,避免重复创建。
- 扩展类加载失败有详细日志,方便排查。
- 支持AOP和IOC。
Dubbo SPI使用规范:
- 编写接口,接口必须加@SPI注解,代表它是一个可扩展的接口。
编写实现类。 - 在ClassPath下的META-INF/dubbo目录创建以接口全限定名命名的文件,文件内容为Key=Value格式,Key是扩展点的名称,Value是扩展点实现类的全限定名。
- 通过ExtensionLoader类获取扩展点实现。
测试
spi简单测试,aop测试(wrapper类)
首先创建个spi测试接口Car,和对应的两个实现RedCar和BlackCar;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
@SPI
public interface Car {
@Adaptive(value = "carType")
void getColor(URL url);
}
实现:
import org.apache.dubbo.common.URL;
import org.example.api.Car;
public class RedCar implements Car {
@Override
public void getColor(URL url) {
System.out.println("red");
}
}
// BlackCar:
import org.example.api.Car;
import org.apache.dubbo.common.URL;
public class BlackCar implements Car {
@Override
public void getColor(URL url) {
System.out.println("black");
}
}
aop测试类CarCheckWrapper类:
import org.apache.dubbo.common.URL;
import org.example.api.Car;
public class CarCheckWrapper implements Car {
private Car car;
public CarCheckWrapper(Car car) {
this.car = car;
}
@Override
public void getColor(URL url) {
System.out.println("before...");
car.getColor(url);
System.out.println("after....");
}
}
在Resource下建立META-INF.services文件夹,创建org.example.api.Car的文件,文件配置上key,value的实现,和wrapper实现;
black=org.example.BlackCar
red=org.example.RedCar
org.example.CarCheckWrapper
测试方法:
public class DubboSPITest {
@Test
public void car() throws Exception {
ExtensionLoader<Car> extensionLoader =
ExtensionLoader.getExtensionLoader(Car.class);
Car red = extensionLoader.getExtension("red");
red.getColor(null);
}
}
执行结果:
spi的aop测试
新建接口Driver类,在Driver类里注入Car类,然后执行对应的Car方法.对应传入的Car通过入参URL里的Map来获取.Map的值配置在Car接口getColor方法上的注解@Adaptive中:
import org.apache.dubbo.common.extension.SPI;
import org.apache.dubbo.common.URL;
@SPI
public interface Driver {
public void driverCar(URL url);
}
实现类:
import org.example.api.Car;
import org.example.api.Driver;
import org.apache.dubbo.common.URL;
public class Trucker implements Driver {
private Car car;
public void setCar(Car car) {
this.car = car;
}
@Override
public void driverCar(URL url) {
System.out.println("Trucker driverCar...");
car.getColor(url);
}
}
然后同样需要将Driver接口类配置在META-INF.services文件夹中,然后创建文件:org.example.api.Driver文件:
trucker=org.example.Trucker
测试类,通过构建URL的map里key为carType来获取哪个类型的car.
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.example.api.Car;
import org.example.api.Driver;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
public class DubboSPITest {
@Test
public void car() throws Exception {
ExtensionLoader<Car> extensionLoader =
ExtensionLoader.getExtensionLoader(Car.class);
Car red = extensionLoader.getExtension("red");
red.getColor(null);
}
@Test
public void driverCar() throws Exception {
ExtensionLoader<Driver> extensionLoader =
ExtensionLoader.getExtensionLoader(Driver.class);
Driver trucker = extensionLoader.getExtension("trucker");
HashMap<String, String> map = new HashMap<>();
map.put("carType", "black");
URL url = new URL("","",0,map);
trucker.driverCar(url);
}
}
执行结果:
源码分析
ExtensionLoader入口
我们首先通过 ExtensionLoader 的 getExtensionLoader 方法获取一个 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。
获取ExtensionLoader实例比较简单,没有就new一个,主要是添加了缓存.
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 an interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
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;
}
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
// 获取默认的拓展实现类
return getDefaultExtension();
}
// 持有者,持有目标对象
Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
// 双重校验锁
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 创建拓展实例
instance = createExtension(name);
// 设置到holder中
holder.set(instance);
}
}
}
return (T) instance;
}
createExtension创建拓展对象的过程
createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:
- 通过 getExtensionClasses 获取所有的拓展类
- 通过反射创建拓展对象
- 向拓展对象中注入依赖
- 将拓展对象包裹在相应的 Wrapper 对象中
injectExtension方法是ioc实现主要方法,
wrapperClasses的遍历主要是aop实现的主要方式.
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);
}
// 向实例中注入依赖 ioc实现
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
// 循环创建 Wrapper 实例 aop实现
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
// 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
// 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
// wapper通过构造方法进行包装
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
getExtensionClasses获取所有的拓展类
基本都是缓存,双重校验锁
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;
}
loadExtensionClasses 方法总共做了两件事情,一是对 SPI 注解进行解析,二是调用 loadDirectory 方法加载指定文件夹配置文件。loadExtensionClasses,加载对应配置文件里面的配置,key=value这样
然后通过value获取对应的class类.
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// 加载指定文件夹下的配置文件
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
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;
}
private void cacheDefaultExtensionName() {
// 获取 SPI 注解,这里的 type 变量是在调用 getExtensionLoader 方法时传入的
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
// 对 SPI 注解内容进行切分
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));
}
// 设置默认名称,参考 getDefaultExtension 方法
if (names.length == 1) {
cachedDefaultName = names[0];
}
}
}
}
loadDirectory 方法先通过 classLoader 获取所有资源链接,然后再通过 loadResource 方法加载资源。
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
// fileName = 文件夹路径 + type 全限定名
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls;
ClassLoader classLoader = findClassLoader();
// 根据文件名加载所有的同名文件
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);
}
}
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
try {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
String line;
// 按行读取配置内容
while ((line = reader.readLine()) != null) {
// 定位 # 字符
final int ci = line.indexOf('#');
if (ci >= 0) {
// 截取 # 之前的字符串,# 之后的内容为注释,需要忽略
line = line.substring(0, ci);
}
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
int i = line.indexOf('=');
if (i > 0) {
// 以等于号 = 为界,截取键与值
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
// 加载类,并通过 loadClass 方法对类进行缓存
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}
loadClass 方法操作了不同的缓存,比如 cachedAdaptiveClass、cachedWrapperClasses 和 cachedNames 等等。除此之外,该方法没有其他什么逻辑了。
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
// 检测目标类上是否有 Adaptive 注解
if (clazz.isAnnotationPresent(Adaptive.class)) {
// 缓存
cacheAdaptiveClass(clazz);
// 检测 clazz 是否是 Wrapper 类型
} else if (isWrapperClass(clazz)) {
// 缓存
cacheWrapperClass(clazz);
} else {
// 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
clazz.getConstructor();
// 可以通过类上面注解Extension来获取name,不过已经过时了
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);
}
}
// 切分name,比如支持red,red1,red2=org.example.RedCar
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
// 存储名称到Class的映射关系
cacheActivateClass(clazz, names[0]);
for (String n : names) {
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, name);
}
}
}
}
到这类的加载缓存都完成了.
dubbo spi 的ioc
injectExtension方法就是对类的ioc,
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
// 遍历目标类的所有方法
for (Method method : instance.getClass().getMethods()) {
// 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public
if (isSetter(method)) {
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
// 获取 setter 方法参数类型
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
// 获取属性名,比如 setName 方法对应属性名 name
String property = getSetterProperty(method);
// 从 ObjectFactory 中获取依赖对象
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
// 通过反射调用 setter 方法设置依赖
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;
}
objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展。
SpiExtensionFactory
SpiExtensionFactory就是调用ExtensionLoader的getExtensionLoader方法来获取注入类.
public class SpiExtensionFactory implements ExtensionFactory {
@Override
public <T> T getExtension(Class<T> type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension();
}
}
return null;
}
}
SpringExtensionFactory
SpringExtensionFactory就是通过拿到spring容器,塞入对应bean的.
SpringExtensionFactory的容器是在加载ServiceBean跟ReferenceBean的添加的.
@Override
@SuppressWarnings("unchecked")
public <T> T getExtension(Class<T> type, String name) {
//SPI should be get from SpiExtensionFactory
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
return null;
}
for (ApplicationContext context : contexts) {
if (context.containsBean(name)) {
Object bean = context.getBean(name);
if (type.isInstance(bean)) {
return (T) bean;
}
}
}
logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());
if (Object.class == type) {
return null;
}
for (ApplicationContext context : contexts) {
try {
return context.getBean(type);
} catch (NoUniqueBeanDefinitionException multiBeanExe) {
logger.warn("Find more than 1 spring extensions (beans) of type " + type.getName() + ", will stop auto injection. Please make sure you have specified the concrete parameter type and there's only one extension of that type.");
} catch (NoSuchBeanDefinitionException noBeanExe) {
if (logger.isDebugEnabled()) {
logger.debug("Error when get spring extension(bean) for type:" + type.getName(), noBeanExe);
}
}
}
logger.warn("No spring extension (bean) named:" + name + ", type:" + type.getName() + " found, stop get bean.");
return null;
}
dubbo spi的aop
dubbo spi的aop就是通过将执行类包装一层返回wapper类实现的
将执行类通过wapper类的构造方法,塞到wapper类里,如果有多个wapper类就循环塞进去.
// 循环创建 Wrapper 实例 aop实现
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
// 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
// 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
// wapper通过构造方法进行包装
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
wapper类demo: