概述
本文主要讲述 Dubbo 中的 SPI 机制, 首先给大家带来一个简单的 Spring-Boot-Dubbo 案例,然后通过 Java 中的 SPI 的机制和讲解来引出 SPI 解决的问题,最后再通过一下几个方面讲述 Dubbo 中的 SPI 的设。
- Dubbo 中的 SPI 实现
- Dubbo 中的依赖注入
- Dubbo 中的 AOP
- Dubbo 中的 Adaptive 机制
版本说明:
dubbo 2.7.8
Spring-Boot-Dubbo 简单案例
Spring Boot 整合 Dubbo 版本说明:
spring-boot 2.3.0.RELEASE
dubbo 2.7.8
Dubbo 更多功能的 资料地址
什么是 SPI 机制 ?
SPI 机制
在双亲委托模型下,类加载是由下而上的,即下层的类加载器会委托上层进行加载。Java 为了实现可拓展性在核心库中只定义了接口信息,而这些接口的实现可以由不同的三方厂家实现。但是 Java 的启动类加载器不会加载其它来源的 jar 包,这样就无法满足 Java 的拓展性需求。
针对这一个问题 Java 提供了线程上下文类加载器来解决这个问题, 他可以让父 ClassLoader 可以使用当前线程Thread.currentThread().getContextLoader(). 所指定的 ClassLoader 加载的类。这就改变了父 ClassLoader 不能使用 ClassLoader 或是没有直接父子关系的 ClassLoader 加载的类的情况,即改变了双亲委托模型。
这个整个打破双亲委派模型的类加载机制,就是 Java 的 SPI(Service Provider Interface) 机制。它有很多我们熟知的案例 JDBC、Spring-Boot 、Dubbo
ServiceLoader 核心原理
这个整个过程
Java 中 SPI 的使用
- 定义接口和实现类
// 接口类
public interface Car {
void start();
}
// 实现类
public class RedCar implements Car {
private String color = "red";
@Override
public void start() {
System.out.println(this.color + " car start!");
}
}
- 定义 META-INF/services/ cn.spi.Car 文件文件内容
cn.spi.RedCar
- 通过 SPI 获取实例对象
ServiceLoader<Car> cars = ServiceLoader.load(Car.class);
for (Car car : cars) {
car.start();
}
- 获取代理类
Dubbo 中的 SPI
在 Dubbo 中 SPI 的流程是这样的首先 ExtensionLoader 是 Dubbo SPI 实现的核心类,它下面有连个核心方法 getExtension 和 createExtension 。在 createExtension 中完成对象的创建,在这个创建的过程中一共有以下几个步骤:
- 读取配置文件通过 name 参数找到实现类;
- 对实现类进行实例化;
- 依赖注入;
- AOP;
- 返回对象实例。
一. Dubbo 中的 SPI 实现
使用步骤
还是使用我们之前 Car 接口,下面是咱们有变化的部分
- 定义配置文件 META-INF/dubbo/ cn.spi.Car 文件内容
red=cn.spi.RedCar
- 获取对象实例
ExtensionLoader<Car> extensionLoader = ExtensionLoader
.getExtensionLoader(Car.class);
Car car = extensionLoader.getExtension("red");
- 依赖注入实现代理类
// 定义
@SPI
public interface Car {
void start();
/**
* 方法被代理需要两个步骤:
* 1. 增加 @Adaptive 注解
* 2. URL 参数,
*
* @param url 服务提供者的 URL
* @return
*/
@Adaptive
String color(URL url);
}
// 使用
ExtensionLoader<Person> extensionLoader2 = ExtensionLoader.getExtensionLoader(Person.class);
Person person = extensionLoader2.getExtension("black");
URL url = new URL("http", "localhost", 8080); //代理逻辑
url = url.addParameter("car", "red");
person.getCar().color(url);
源码分析
getExtension 方法源码分析
// 获取实例
public T getExtension(String name, boolean wrap) {
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) { // DCL
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 创建对象
instance = createExtension(name, wrap);
holder.set(instance);
}
}
}
return (T) instance;
}
// 创建实例
private T createExtension(String name, boolean wrap) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// .... ioc, aop
return instance;
}
// 读取类
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;
}
// 加载类
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// LoadingStrategy 策略接口,读取 SPI 目录下的类
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
}
return extensionClasses;
}
// loadDirectory 读取目录查找文件
// loadResource 读取文件
// loadClass 加载类
// saveInExtensionClass 保存类信息到 Map
二. Dubbo 中的依赖注入
Dubbo 依赖注入和核心逻辑是通过 Set 方法进行依赖注入,会在 Dubbo SPI 或者 Spring SPI 中查找对象。
// 依赖注入
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try {
for (Method method : instance.getClass().getMethods()) {
if (!isSetter(method)) {
continue;
}
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
// 获取 set 方法参数类型
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
// 获取 setXXX 中的 xxx
String property = getSetterProperty(method);
Object object = objectFactory.getExtension(pt, property); // Car.class, car
if (object != null) {
// 根据参数类型或属性名,从 objectFactory 中获取到对象,然后调用 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;
}
// ExtensionLoader
private ExtensionLoader(Class<?> type) {
this.type = type;
// ExtensionLoader 表示拓展类实例工程,可以利用 ExtensionFactory 得到某个拓展的对象实例
// 或者得到 ExtensionFactory 接口的 AdaptiveExtensionFactory 实例, 利用 AdaptiveExtensionFactory 来获取某个类的实例
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
// AdaptiveExtensionFactory 构造方法
public AdaptiveExtensionFactory() {
// 支持那些 ExtensionFactory (Spring, SPI)
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}
// SpringExtensionFactory#getExtension 在 Spring 容器中去查找Bean
public <T> T getExtension(Class<T> type, String name) {
// 如果有 @SPI 修饰就不管
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
return null;
}
// 首先 byName, 然后 byType
for (ApplicationContext context : CONTEXTS) {
T bean = BeanFactoryUtils.getOptionalBean(context, name, type);
if (bean != null) {
return bean;
}
}
return null;
}
// SpiExtensionFactory#getExtension
public <T> T getExtension(Class<T> type, String name) {
// 接口上的 SPI 注解
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension(); // 接口的 Adaptive 对象
}
}
return null;
}
// ExtensionLoader
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError != null) {
throw new IllegalStateException("Failed to create adaptive instance: " +
createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
// 如果不存在就创建
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
}
return (T) instance;
}
// 获取 AdaptiveExtensionClass
private Class<?> getAdaptiveExtensionClass() {
// 获取当前接口的所有拓展类
getExtensionClasses();
// 缓存了 @Adaptive 注解标记类
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 如果某个接口没有手动指定 Adaptive, 那么就生成一个 Adaptive 类
return cachedAdaptiveClass = 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);
}
三. Dubbo 中的 AOP
dubbo中也实现了一套非常简单的 AOP,就是利用 Wrapper.class ,如果一个接口的扩展点中包含了多个 Wrapper 类,那么在实例化完某个扩展点后,就会利用这些 Wrapper 类对这个实例进行包裹,比如:现在有一个 DubboProtocol 的实例,同时对于 Protocol 这个接口还有很多的 Wrapper,比如 ProtocolFilterWrapper、ProtocolListenerWrapper,那么,当对 DubboProtocol 的实例完成了IOC之后,就会先调用 new ProtocolFilterWrapper(DubboProtocol实例) 生成一个新的Protocol的实例,再对此实例进行IOC,完了之后,会再调用new ProtocolListenerWrapper(ProtocolFilterWrapper实例)生成一个新的Protocol的实例,然后进行IOC,从而完成DubboProtocol实例的AOP。
// ExtensionLoader#createExtension
if (wrap) {
List<Class<?>> wrapperClassesList = new ArrayList<>();
if (cachedWrapperClasses != null) {
wrapperClassesList.addAll(cachedWrapperClasses);
wrapperClassesList.sort(WrapperComparator.COMPARATOR);
Collections.reverse(wrapperClassesList);
}
if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
if (wrapper == null
|| (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
}
四. Dubbo 中的 Adaptive 机制
每个扩展点都有一个name,通过这个name可以获得该name对应的扩展点实例,但是有的场景下,希望一次性获得多个扩展点实例,可以通过传入多个name来获取,可以通过识别URL上的信息来获取:
extensionLoader.getActivateExtension(url, new String[]{"car1", "car2"}); 这个可以拿到name为car1和car2的扩展类实例,同时还会通过传入的url寻找可用的扩展类, 怎么找的呢?
在一个扩展点类上,可以添加 @Activate 注解,这个注解的属性有:
- String[] group():表示这个扩展点是属于拿组的,这里组通常分为
PROVIDER和CONSUMER,表示该扩展点能在服务提供者端,或者消费端使用 - String[] value():指示的是URL中的某个参数 key,当利用
getActivateExtension方法来寻找扩展点时,如果传入的url中包含的参数的所有 key 中,包括了当前扩展点中的 value 值,那么则表示当前 url 可以使用该扩展。