扩展点开发指南
1. 简介
SPI 全称 Service Provider Interface,是一种服务发现机制。SPI 的本质是在文件中配置接口实现类的全限定名,服务加载器读取配置文件并加载实现类。这样,就可以在运行时为接口动态替换实现类。正因为此特性,我们可以通过 SPI 机制轻松地为程序提供扩展功能。SPI 机制也被用在第三方框架中。例如,Dubbo 就是通过 SPI 机制来加载所有的组件的。不过,Dubbo 并没有使用 Java 原生的 SPI 机制,而是对其进行了增强,以更好地满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行扩展。在 Dubbo 中,SPI 主要有两种用法,一种是加载固定的扩展类,另一种是加载自适应的扩展类。这两种方式会在下文详细介绍。特别注意:在 Dubbo 中,基于 SPI 扩展加载的类是单例的。
1.1 加载固定的扩展类
如果让你设计,加载固定的扩展类,你会怎么做?一种常见的思路是读取指定目录下的配置文件,然后解析出全类名,通过反射机制实例化类,然后将类存储到集合中。需要的时候,直接从集合中获取即可。Dubbo 中的实现也是这样的思路。不过,在 Dubbo 中,实现的更加完善,它实现了 IOC 和 AOP 的功能。IOC 指的是,如果这个扩展类依赖其他的属性,Dubbo 会自动的将这个属性注入进来。这个功能是如何实现的?一种常见的思路是,获取这个扩展类方法的 setter,调用 setter 方法进行属性注入。那 AOP 指的是什么呢?指的是 Dubbo 可以将自己的 Wrapper 类,注入到扩展类中。比如,DubboProtocol 是 Protocol 的扩展类,ProtocolListenerWrapper 是 DubboProtocol 的 Wrapper 类。
1.2 加载自适应的扩展类
先说明一下,自适应扩展类的使用场景。比如,我们有一个需求,在调用某个方法的时候,可以根据参数选择调用不同的实现类。有点类似工厂方法,根据不同的参数构造不同的实例对象。Dubbo 中的实现思路也和这个类似,不过 Dubbo 的实现更加灵活,它的实现有点类似策略模式。每一个扩展类就相当于一个策略。基于 URL 消息总线,将参数传递给 ExtensionLoader,ExtensionLoader 根据参数加载对应的扩展类,从而实现运行时动态调用目标实例。
2. Dubbo SPI 源码分析
2.1 加载固定的扩展类
在 Dubbo 中,SPI 加载固定的扩展类的入口是 ExtensionLoader 的 getExtension 方法。接下来,我们就来详细分析获取扩展类对象的流程。
public T getExtension(String name) {
if (name == null || name. length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
// Get the default extension implementation class
return getDefaultExtension();
}
// Holder, as the name suggests, is used to hold the target object
Holder<Object> holder = cachedInstances. get(name);
// This logic ensures that only one thread can create a Holder object
if (holder == null) {
cachedInstances. putIfAbsent(name, new Holder<Object>());
holder = cachedInstances. get(name);
}
Object instance = holder. get();
// double check
if (instance == null) {
synchronized (holder) {
instance = holder. get();
if (instance == null) {
// create extension instance
instance = createExtension(name);
// set the instance to the holder
holder.set(instance);
}
}
}
return (T) instance;
}
上面代码的逻辑比较简单,先检查缓存,如果缓存未命中,则创建扩展对象。我们再来看一下创建扩展对象的流程。
private T createExtension(String name, boolean wrap) {
// Load all extension classes from the configuration file to get the mapping relationship table from "configuration item name" to "configuration class"
Class<?> clazz = getExtensionClasses().get(name);
// If there is no extension of the interface, or the implementation class of the interface does not allow repetition but actually repeats, an exception is thrown directly
if (clazz == null || unacceptableExceptions. contains(name)) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES. get(clazz);
// This code ensures that the extended class will only be constructed once, which is a singleton.
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance());
instance = (T) EXTENSION_INSTANCES. get(clazz);
}
// Inject dependencies into the instance
injectExtension(instance);
// Automatically wrap if wrapping is enabled.
// For example, I defined the extension of DubboProtocol based on Protocol, but in fact, DubboProtocol is not directly used in Dubbo, but its wrapper class
// ProtocolListenerWrapper
if (wrap) {
List<Class<?>> wrapperClassesList = new ArrayList<>();
if (cachedWrapperClasses != null) {
wrapperClassesList.addAll(cachedWrapperClasses);
wrapperClassesList.sort(WrapperComparator. COMPARATOR);
Collections. reverse(wrapperClassesList);
}
// Loop to create Wrapper instances
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))) {
// Pass the current instance as a parameter to the constructor of Wrapper, and create a Wrapper instance through reflection.
// Then inject dependencies into the Wrapper instance, and finally assign the Wrapper instance to the instance variable again
instance = injectExtension((T) wrapperClass. getConstructor(type). newInstance(instance));
}
}
}
}
// initialization
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t. getMessage(), t);
}
}
createExtension 方法的逻辑稍微复杂一点,主要包含以下几个步骤
- 通过 getExtensionClasses 获取所有的扩展类
- 通过反射创建扩展对象
- 向扩展对象中注入依赖
- 将扩展对象封装到对应的 Wrapper 对象中
- 初始化扩展对象
在上面的步骤中,第一步是加载扩展类的关键,第三、四步是 Dubbo IOC 和 AOP 的具体实现。在接下来的章节中,我们会重点分析 getExtensionClasses 方法的逻辑,并简单介绍 Dubbo IOC 的具体实现。
2.1.1 获取所有的扩展类
在我们根据名称获取扩展类之前,首先需要根据配置文件解析出扩展项名称到扩展类的映射关系表(Map<name, extension class>),然后根据扩展项名称从映射关系表中提取出对应的扩展类即可。相关流程的代码分析如下
private Map<String, Class<?>> getExtensionClasses() {
// Get the loaded extension class from the cache
Map<String, Class<?>> classes = cachedClasses. get();
// double check
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses. get();
if (classes == null) {
// load extension class
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
这里也是先检查缓存,如果缓存未命中,则通过 synchronized 加锁。加锁之后,再次检查缓存,发现为空。此时,如果 classes 仍然为空,则通过 loadExtensionClasses 加载扩展类。我们来分析一下 loadExtensionClasses 方法的逻辑。
private Map<String, Class<?>> loadExtensionClasses() {
// Cache the default SPI extension
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// Load the files in the specified folder based on the policy
// Currently there are four strategies to read the configuration files in META-INF/services/ META-INF/dubbo/ META-INF/dubbo/internal/ META-INF/dubbo/external/ respectively
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;
}
loadExtensionClasses 方法总共做了两件事,一件是解析 SPI 注解,另一件是调用 loadDirectory 方法加载指定文件夹下的配置文件。SPI 注解解析的过程比较简单,这里就不再赘述,我们重点看一下 loadDirectory 做了什么。
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
// fileName = folder path + type fully qualified name
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);
}
}
// Load all files with the same name according to the file name
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();
// load resources
loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
loadDirectory 方法首先通过 classLoader 获取所有资源链接,然后通过 loadResource 方法加载资源。我们继续往下看,看一下 loadResource 方法的实现。
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
try {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL. openStream(), StandardCharsets. UTF_8))) {
String line;
String clazz = null;
// Read configuration content line by line
while ((line = reader. readLine()) != null) {
// locate # characters
final int ci = line. indexOf('#');
if (ci >= 0) {
// Intercept the string before #, the content after # is a comment, which needs to be ignored
line = line. substring(0, ci);
}
line = line. trim();
if (line. length() > 0) {
try {
String name = null;
// Use the equal sign = as the boundary to intercept the key and value
int i = line. indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
clazz = line.substring(i + 1).trim();
} else {
clazz = line;
}
// Load the class and cache the class through the loadClass method
if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages)) {
loadClass(extensionClasses, resourceURL, Class. forName(clazz, true, classLoader), name, overridden);
}
} 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);
}
}
loadResource 方法用于读取和解析配置文件,通过反射加载类,最后调用 loadClass 方法进行其他操作。loadClass 方法主要用于操作缓存,该方法的逻辑如下
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
boolean overridden) 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.");
}
// Check if there are Adaptive annotations on the target class
if (clazz. isAnnotationPresent(Adaptive. class)) {
cacheAdaptiveClass(clazz, overridden);
} else if (isWrapperClass(clazz)) {
// cache wrapper class
cacheWrapperClass(clazz);
} else {
// Enter here, indicating that this class is just an ordinary extension class
// Check if clazz has a default constructor, if not, throw an exception
clazz. getConstructor();
if (StringUtils. isEmpty(name)) {
// If name is empty, try to get name from Extension annotation, or use lowercase class name as 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)) {
// If the class has the Activate annotation, use the first element of the names array as the key,
// Store the mapping relationship between name and Activate annotation object
cacheActivateClass(clazz, names[0]);
for (String n : names) {
// Store the mapping relationship from Class to name
cacheName(clazz, n);
// Store the mapping relationship from name to Class.
// If there are multiple implementation classes corresponding to the same extension, whether overriding is allowed based on the override parameter, if not, an exception is thrown.
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}
}
如上所示,loadClass 方法操作不同的缓存,比如 cachedAdaptiveClass、cachedWrapperClasses 和 cachedNames 等,除此之外,该方法中就没有其他逻辑了。
至此,缓存类加载的流程分析完毕。整个流程中并没有什么特别复杂的地方,大家可以一步步分析,实在不理解的地方可以断点调试一下。接下来,我们聊聊 Dubbo IOC。
2.1.2 Dubbo IOC
Dubbo IOC 是通过 setter 方法注入依赖的。Dubbo 首先会通过反射获取实例的所有方法,然后遍历方法列表,检测方法名是否具有 setter 方法的特征,如果是,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。整个过程对应的代码如下
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try {
// traverse all methods of the target class
for (Method method : instance. getClass(). getMethods()) {
// Check whether the method starts with set, and the method has only one parameter, and the method access level is public
if (!isSetter(method)) {
continue;
}
/**
* Detect whether there is DisableInject annotation modification.
*/
if (method. getAnnotation(DisableInject. class) != null) {
continue;
}
/**
* Detect whether the ScopeModelAware and ExtensionAccessorAware classes are implemented, and if implemented, do not inject
*/
if (method. getDeclaringClass() == ScopeModelAware. class) {
continue;
}
if (instance instanceof ScopeModelAware || instance instanceof ExtensionAccessorAware) {
if (ignoredInjectMethodsDesc. contains(ReflectUtils. getDesc(method))) {
continue;
}
}
// Primitive types are not injected
Class<?> pt = method. getParameterTypes()[0];
if (ReflectUtils. isPrimitives(pt)) {
continue;
}
try {
// Get the attribute name, for example, the setName method corresponds to the attribute name name
String property = getSetterProperty(method);
// Get dependent objects from ObjectFactory
Object object = objectFactory. getExtension(pt, property);
if (object != null) {
// inject
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 容器中获取所需的扩展。这两个类的代码也不是很复杂,这里就不再一一分析了。
Dubbo IOC 目前只支持 setter 注入,总体来说,逻辑比较简单,容易理解。
2.2 加载自适应扩展类
自适应扩展类的意思是,基于参数,在运行时动态的选择某个具体的的目标类,然后执行。在 Dubbo 中,很多扩展都是通过 SPI 机制进行加载的,比如 Protocol、Cluster、LoadBalance 等。有时候,有些扩展不想在框架启动阶段就加载,而是希望在调用扩展方法的时候,根据运行时参数进行加载。这听起来有点矛盾,扩展不加载,扩展方法就无法调用(静态方法除外),扩展方法不调用,扩展就无法加载。对于这个矛盾的问题,Dubbo 通过自适应扩展机制很好的解决了。自适应扩展机制的实现逻辑比较复杂,首先 Dubbo 会为扩展接口生成具有代理功能的代码,然后通过 javassist 或 jdk 编译这段代码,得到 Class 类,最后通过反射创建代理类,整个过程比较复杂。
加载自适应扩展类的入口方法为 ExtensionLoader 的 getAdaptiveExtension 方法。
public T getAdaptiveExtension() {
// Get the adaptive extension from the cache
Object instance = cachedAdaptiveInstance. get();
if (instance == null) {
// If there is an exception, throw it directly
if (createAdaptiveInstanceError != null) {
throw new IllegalStateException("Failed to create adaptive instance: " +
createAdaptiveInstanceError.toString(),
createAdaptiveInstanceError);
}
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance. get();
// double check
if (instance == null) {
try {
// create adaptive extension
// There are two cases here: one is that there is an Adaptive class, and the other is that an Adaptive class needs to be generated
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
}
return (T) instance;
}
getAdaptiveExtension 方法首先会检查缓存,如果缓存未命中,则调用 createAdaptiveExtension 方法创建自适应扩展。接下来,我们来看一下 createAdaptiveExtension 方法的代码。
private T createAdaptiveExtension() {
try {
// Get the adaptive extension class and instantiate it through reflection
return injectExtension((T) getAdaptiveExtensionClass(). newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extension ...");
}
}
createAdaptiveExtension 方法的代码比较少,但是包含了三个逻辑,分别是
- 调用 getAdaptiveExtensionClass 方法获取自适应扩展 Class 对象
- 通过反射进行实例化
- 调用 injectExtension 方法向扩展实例中注入依赖
前两个逻辑比较容易理解,第三个逻辑是为自适应扩展对象注入依赖。这个逻辑看起来有点多余,但是它是必须存在的,这里简单解释一下。前面提到,Dubbo 中的自适应扩展有两种,一种是手动编码实现的,另一种是自动生成的。手动编码实现的 Adaptive 扩展可能会有一些依赖,而自动生成的 Adaptive 扩展则不会依赖其他类。这里调用 injectExtension 方法的目的就是为手动编码实现的 Adaptive 扩展注入依赖,这一点需要大家注意。关于 injectExtension 方法,在前面的文章中已经分析过了,这里就不再赘述了。接下来,分析 getAdaptiveExtensionClass 方法的逻辑。
private Class<?> getAdaptiveExtensionClass() {
// Get all extension classes through SPI
getExtensionClasses();
// Check the cache, if the cache is not empty, return the cache directly
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// Create an adaptive extension class
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
getAdaptiveExtensionClass 方法也包含了三个逻辑,分别是
- 调用 getExtensionClasses 获取所有的扩展类
- 检查缓存,如果缓存不为空,则返回缓存
- 如果缓存为空,则调用 createAdaptiveExtensionClass 创建自适应扩展类
这三个逻辑看起来平淡无奇,似乎没有什么好说的。但是,在这段平淡的代码中,隐藏了一些细节需要说明一下。首先,我们从第一个逻辑说起,getExtensionClasses 方法用于获取一个接口的所有实现类,比如该方法可以获取到 Protocol 接口的 DubboProtocol、HttpProtocol、InjvmProtocol 等实现类。在获取实现类的过程中,如果某个实现类被 Adaptive 注解修饰了,那么则会将该类赋值给 cachedAdaptiveClass 变量。此时,上面步骤中的第二步的条件就满足了(缓存不为空),直接返回 cachedAdaptiveClass 即可。如果所有的实现类都没有被 Adaptive 注解修饰,那么就执行第三步的逻辑,创建自适应扩展类。相关的代码如下所示
private Class<?> createAdaptiveExtensionClass() {
// Build adaptive extension code
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
ClassLoader classLoader = findClassLoader();
// Get the compiler implementation class
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
// Compile the code and generate Class
return compiler.compile(code, classLoader);
}
createAdaptiveExtensionClass 方法用于生成自适应扩展类,该方法首先会生成自适应扩展类的源码,然后通过 Compiler 实例(Dubbo 默认使用 javassist 作为编译器)编译源码以获得代理类 Class 实例。接下来,我们重点关注代理类代码生成的逻辑,其他逻辑大家自行分析。
2.2.1 自适应扩展类代码生成
AdaptiveClassCodeGenerator#generate 方法生成扩展类代码
public String generate() {
// If there is no method in the interface modified by the @Adaptive annotation, an exception is thrown directly
if (!hasAdaptiveMethod()) {
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
}
StringBuilder code = new StringBuilder();
// Generate package name, import, method, etc.
code.append(generatePackageInfo());
code.append(generateImports());
code.append(generateClassDeclaration());
Method[] methods = type. getMethods();
for (Method method : methods) {
code.append(generateMethod(method));
}
code.append("}");
if (logger. isDebugEnabled()) {
logger. debug(code. toString());
}
return code. toString();
}
2.2.2 生成方法
在上面的代码中,生成方法的逻辑最为关键,我们来详细分析一下。
private String generateMethod(Method method) {
String methodReturnType = method. getReturnType(). getCanonicalName();
String methodName = method. getName();
// generate method content
String methodContent = generateMethodContent(method);
String methodArgs = generateMethodArguments(method);
String methodThrows = generateMethodThrows(method);
return String. format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
}
generateMethodContent 分析
private String generateMethodContent(Method method) {
// This method must be decorated with @Adaptive annotation
Adaptive adaptiveAnnotation = method. getAnnotation(Adaptive. class);
StringBuilder code = new StringBuilder(512);
if (adaptiveAnnotation == null) {
// Without @Adaptive annotation modification, exception information is generated
return generateUnsupported(method);
} else {
// Get the index of the URL on the parameter list
int urlTypeIndex = getUrlTypeIndex(method);
if (urlTypeIndex != -1) {
// Generate a null check for the URL if it exists on the parameter list
code.append(generateUrlNullCheck(urlTypeIndex));
} else {
// If there is no parameter of URL type in the parameter list, then it depends on whether the parameter object on the parameter list contains the getUrl method
// If there is, generate a URL null check
code.append(generateUrlAssignmentIndirectly(method));
}
// parse the value attribute on the Adaptive annotation
String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
// If there is a parameter of type Invocation on the parameter list, generate a null check and get the methodName.
boolean hasInvocation = hasInvocationArgument(method);
code.append(generateInvocationArgumentNullCheck(method));
// This logic is mainly to generate extName (that is, the extension)
// Divided into multiple situations:
// 1. Does defaultExtName exist?
// 2. Whether there is an invocation type parameter in the parameter
// 3. Whether to generate a proxy for the protocol
// Why should the protocol be considered separately? Because there is a method to get the protocol value in the URL
code.append(generateExtNameAssignment(value, hasInvocation));
// check extName == null?
code.append(generateExtNameNullCheck(value));
// generate get extension (using ExtensionLoader.getExtension method)
code.append(generateExtensionAssignment());
// generate return statement
code.append(generateReturnAndInvocation(method));
}
return code. toString();
}
上面的逻辑主要做了以下几件事
- 检查方法上 Adaptive 注解是否修饰
- 方法生成代码时,参数列表中必须要有 URL(或者参数对象中有 URL)
- 使用 ExtensionLoader.getExtension 获取扩展
- 执行对应的方法
2.2.3 附上一段动态生成的代码示例
package org.apache.dubbo.common.extension.adaptive;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class HasAdaptiveExt$Adaptive implements org.apache.dubbo.common.extension.adaptive.HasAdaptiveExt {
public java.lang.String echo(org.apache.dubbo.common.URL arg0,
java. lang. String arg1) {
// URL null check
if (arg0 == null) {
throw new IllegalArgumentException("url == null");
}
org.apache.dubbo.common.URL url = arg0;
// get the extension
String extName = url. getParameter("has. adaptive. ext", "adaptive");
// extension null check
if (extName == null) {
throw new IllegalStateException(
"Failed to get extension (org.apache.dubbo.common.extension.adaptive.HasAdaptiveExt) name from url (" +
url.toString() + ") use keys([has.adaptive.ext])");
}
// get extension
org.apache.dubbo.common.extension.adaptive.HasAdaptiveExt extension = (org.apache.dubbo.common.extension.adaptive.HasAdaptiveExt) ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.adaptive.HasAdaptiveExt.class)
.getExtension(extName);
// Execute the corresponding method
return extension.echo(arg0, arg1);
}
}
3. SPI 扩展示例
3.1 加载固定扩展类
3.1.1 编写 SPI 接口和实现类
无论是 Java SPI 还是 Dubbo 中实现的 SPI,都需要编写接口。不过,Dubbo 中的接口需要使用 @SPI 注解修饰。
@SPI
public interface DemoSpi {
void say();
}
public class DemoSpiImpl implements DemoSpi {
public void say() {
}
}
3.1.2 将实现类放到特定目录下
从上面的代码中,我们可以看到,dubbo 在加载扩展类的时候,会从四个目录下读取。我们在 META-INF/dubbo 目录下新建一个以 DemoSpi 接口命名的文件,内容如下
demoSpiImpl = com.xxx.xxx.DemoSpiImpl (full class name for the implementation class of the DemoSpi interface)
3.1.3 使用
public class DubboSPITest {
@Test
public void sayHello() throws Exception {
ExtensionLoader<DemoSpi> extensionLoader =
ExtensionLoader. getExtensionLoader(DemoSpi. class);
DemoSpi dmeoSpi = extensionLoader. getExtension("demoSpiImpl");
optimusPrime. sayHello();
}
}
3.2 加载自适应扩展类
这里以 Protocol 为例进行说明
3.2.1 Protocol 接口(摘取部分核心方法)
@SPI("dubbo")
public interface Protocol {
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
}
public class DubboProtocol extends AbstractProtocol {
…
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
return protocolBindingRefer(type, url);
}
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
…
return exporter;
}
}
3.2.2 将实现类放到特定目录下
在 dubbo 中,配置路径为 META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
需要注意的是,在 dubbo 中,并没有直接使用 DubboProtocol,而是使用了它的包装类。
3.2.3 使用
public class DubboAdaptiveTest {
@Test
public void sayHello() throws Exception {
URL url = URL.valueOf("dubbo://localhost/test");
Protocol adaptiveProtocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
adaptiveProtocol. refer(type, url);
}
}