SOFA RPC(Scalable Open Financial Architecture Remote Procedure Call)是一个高可扩展性、高性能、生产级的 Java RPC 框架。其 SPI(Service Provider Interface)机制为框架提供了强大的扩展能力,允许开发者在不修改框架核心代码的情况下,对框架的各个功能组件进行定制和扩展。以下将详细介绍 SOFA RPC 的 SPI 机制原理。
1. Java SPI 机制回顾
在了解 SOFA RPC 的 SPI 机制之前,有必要先回顾一下 Java 原生的 SPI 机制。Java SPI 是一种服务发现机制,它允许第三方为某个接口提供实现。其核心思想是将接口的定义和实现分离,通过在 META-INF/services
目录下创建以接口全限定名命名的文件,并在文件中列出实现类的全限定名,JVM 可以在运行时动态加载这些实现类。
例如,假设有一个接口 com.example.MyService
,其实现类为 com.example.impl.MyServiceImpl
,则需要在 META-INF/services/com.example.MyService
文件中添加以下内容:
com.example.impl.MyServiceImpl
Java 代码中可以通过 ServiceLoader
来加载这些实现类:
import java.util.ServiceLoader;
public class Main {
public static void main(String[] args) {
ServiceLoader<com.example.MyService> serviceLoader = ServiceLoader.load(com.example.MyService.class);
for (com.example.MyService service : serviceLoader) {
service.doSomething();
}
}
}
Java SPI 机制虽然提供了一定的扩展性,但也存在一些缺点,如无法指定加载特定的实现类、加载效率较低等。
2. SOFA RPC 对 SPI 的改进
SOFARPC 的 SPI 机制可以借助上下文类加载器(Context ClassLoader)来实现。上下文类加载器是 Java 里的一个特性,它允许线程在运行时指定一个类加载器,以此来打破双亲委派机制的限制,达成动态加载类的目的。
在 Java 里,能够通过 Thread.currentThread().getContextClassLoader() 方法获取当前线程的上下文类加载器。SOFARPC 里有专门的工具类 ClassLoaderUtils 来获取当前的类加载器:
public class ClassLoaderUtils {
public static ClassLoader getCurrentClassLoader() {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) {
cl = ClassLoaderUtils.class.getClassLoader();
}
return cl == null ? ClassLoader.getSystemClassLoader() : cl;
}
}
上述代码中,getCurrentClassLoader 方法先尝试获取当前线程的上下文类加载器,若为空,则使用 ClassLoaderUtils 类的类加载器,若还为空,则使用系统类加载器。
SOFA RPC 在 Java SPI 的基础上进行了改进和扩展,主要体现在以下几个方面:
2.1 拓展文件管理和加载优化
SOFA RPC 引入了自定义的拓展文件管理机制,不再局限于 META-INF/services
目录。它使用 META-INF/sofa-rpc
目录来存放拓展文件,通过自定义的加载器来提高加载效率。同时,SOFA RPC 支持在拓展文件中为每个实现类指定一个唯一的名称,方便在代码中通过名称来获取特定的实现类。
例如,在 META-INF/sofa-rpc/com.alipay.sofa.rpc.protocol.Protocol
文件中,可以定义多个 Protocol
接口的实现类:
bolt=com.alipay.sofa.rpc.protocol.bolt.BoltProtocol
rest=com.alipay.sofa.rpc.protocol.rest.RestProtocol
2.2 扩展点的编码和排序机制
SOFA RPC 为每个扩展点提供了编码和排序机制。通过 @Extensible
注解可以为扩展点指定一个唯一的编码,用于在配置文件中引用。同时,可以通过 @Extension
注解为实现类指定一个排序值,SOFA RPC 会根据排序值对实现类进行排序,确保在加载时按照指定的顺序进行。
以下是 Protocol
接口的定义,使用了 @Extensible
注解:
@Extensible(coded = true)
@Unstable
public interface Protocol {
// 接口方法定义
ProtocolInfo protocolInfo();
ProtocolEncoder encoder();
ProtocolDecoder decoder();
ProtocolNegotiator negotiator();
}
以下是 BoltProtocol
实现类的定义,使用了 @Extension
注解:
@Extension("bolt")
public class BoltProtocol implements Protocol {
// 实现接口方法
@Override
public ProtocolInfo protocolInfo() {
// 实现逻辑
return null;
}
@Override
public ProtocolEncoder encoder() {
// 实现逻辑
return null;
}
@Override
public ProtocolDecoder decoder() {
// 实现逻辑
return null;
}
@Override
public ProtocolNegotiator negotiator() {
// 实现逻辑
return null;
}
}
2.3 覆盖和排斥策略
SOFA RPC 支持覆盖和排斥策略,允许开发者在不同的模块中对同一个扩展点的实现进行覆盖或排斥。例如,在某些情况下,开发者可能希望使用自定义的实现类来替换框架默认的实现类,或者禁止某些实现类的加载。
3. 核心源码解析
SOFA RPC 的 SPI 机制中使用了两个核心注解:@Extensible
和 @Extension
。
3.1 @Extensible
注解
@Extensible
注解用于标记一个接口为可扩展的接口,即该接口可以有多个实现类。该注解有以下几个重要的参数:
singleton
:指定该扩展点的实现类是否为单例模式,默认为true
。file
:指定自定义扩展文件名称,默认是接口的全类名。coded
:指定该扩展点是否支持编码,即是否可以通过编码来引用实现类,默认为false
。
例如,Cluster
接口使用了 @Extensible
注解:
@Extensible(singleton = false)
@ThreadSafe
public abstract class Cluster implements Invoker, ProviderInfoListener, Initializable, Destroyable {
// 类的定义
}
3.2 @Extension
注解
@Extension
注解用于标记一个类为某个扩展点的实现类。该注解有以下几个重要的参数:
value
:扩展点的名称。code
:扩展点的编码,默认 -1,当接口需要编码时使用。order
:优先级排序,默认 0。override
:是否覆盖其他低order
的同名扩展,默认false
。rejection
:排斥其他扩展,可以排斥掉其他低order
的扩展。
例如,SofaTracerModule
类使用了 @Extension
注解:
@Extension("sofaTracer")
public class SofaTracerModule implements Module {
// 类的定义
}
3.3 扩展加载器工厂 ExtensionLoaderFactory
ExtensionLoaderFactory
是一个工厂类,用于获取 ExtensionLoader
实例。它使用一个 ConcurrentMap
来缓存 ExtensionLoader
实例,确保每个扩展接口只有一个 ExtensionLoader
实例。
示例代码如下:
public class ExtensionLoaderFactory {
private static final ConcurrentMap<Class, ExtensionLoader> LOADER_MAP = new ConcurrentHashMap<>();
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> clazz) {
return getExtensionLoader(clazz, null);
}
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> clazz, ExtensionLoaderListener<T> listener) {
ExtensionLoader<T> loader = LOADER_MAP.get(clazz);
if (loader == null) {
synchronized (ExtensionLoaderFactory.class) {
loader = LOADER_MAP.get(clazz);
if (loader == null) {
loader = new ExtensionLoader<>(clazz, listener);
LOADER_MAP.put(clazz, loader);
}
}
}
return loader;
}
}
3. 5扩展加载器 ExtensionLoader
ExtensionLoader
是核心类,负责加载扩展点的实现类。以下是其主要功能的详细解读:
3.5.1构造函数
构造函数负责初始化扩展加载器的各种属性,并根据 autoLoad
参数决定是否自动加载扩展。
public ExtensionLoader(Class<T> interfaceClass, boolean autoLoad, ExtensionLoaderListener<T> listener) {
if (RpcRunningState.isShuttingDown()) {
// 省略部分代码
return;
}
if (interfaceClass == null || !(interfaceClass.isInterface() || Modifier.isAbstract(interfaceClass.getModifiers()))) {
throw new IllegalArgumentException("Extensible class must be interface or abstract class!");
}
this.interfaceClass = interfaceClass;
this.interfaceName = ClassTypeUtils.getTypeStr(interfaceClass);
this.listeners = new ArrayList<>();
if (listener != null) {
listeners.add(listener);
}
Extensible extensible = interfaceClass.getAnnotation(Extensible.class);
if (extensible == null) {
throw new IllegalArgumentException(
"Error when load extensible interface " + interfaceName + ", must add annotation @Extensible.");
} else {
this.extensible = extensible;
}
this.factory = extensible.singleton() ? new ConcurrentHashMap<String, T>() : null;
this.all = new ConcurrentHashMap<String, ExtensionClass<T>>();
if (autoLoad) {
List<String> paths = RpcConfigs.getListValue(RpcOptions.EXTENSION_LOAD_PATH);
for (String path : paths) {
loadFromFile(path);
}
}
}
3.5.2 从文件加载扩展
loadFromFile
方法负责从指定路径加载扩展文件。
protected synchronized void loadFromFile(String path) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Loading extension of extensible {} from path: {}", interfaceName, path);
}
String file = StringUtils.isBlank(extensible.file()) ? interfaceName : extensible.file().trim();
String fullFileName = path + file;
try {
ClassLoader classLoader = ClassLoaderUtils.getClassLoader(getClass());
loadFromClassLoader(classLoader, fullFileName);
} catch (Throwable t) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Failed to load extension of extensible " + interfaceName + " from path:" + fullFileName, t);
}
}
}
3.3 从类加载器加载扩展
loadFromClassLoader
方法负责从类加载器中读取扩展文件的内容,并逐行解析。
protected void loadFromClassLoader(ClassLoader classLoader, String fullFileName) throws Throwable {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources(fullFileName)
: ClassLoader.getSystemResources(fullFileName);
if (urls != null) {
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Loading extension of extensible {} from classloader: {} and file: {}",
interfaceName, classLoader, url);
}
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8));
String line;
while ((line = reader.readLine()) != null) {
readLine(url, line);
}
} catch (Throwable t) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Failed to load extension of extensible " + interfaceName
+ " from classloader: " + classLoader + " and file:" + url, t);
}
} finally {
if (reader != null) {
reader.close();
}
}
}
}
}
3.5.3 解析扩展行
readLine
方法负责解析扩展文件中的每一行,提取别名和类名,并加载扩展。
protected void readLine(URL url, String line) {
String[] aliasAndClassName = parseAliasAndClassName(line);
if (aliasAndClassName == null || aliasAndClassName.length != 2) {
return;
}
String alias = aliasAndClassName[0];
String className = aliasAndClassName[1];
Class tmp;
try {
tmp = ClassUtils.forName(className, false);
} catch (Throwable e) {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("Extension {} of extensible {} is disabled, cause by: {}",
className, interfaceName, ExceptionUtils.toShortString(e, 2));
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Extension " + className + " of extensible " + interfaceName + " is disabled.", e);
}
return;
}
loadExtension(alias, tmp, StringUtils.toString(url), className);
}
3.5.4加载扩展
loadExtension
方法负责检查扩展类是否符合要求,并将其添加到 all
映射中。
private void loadExtension(String alias, Class loadedClazz, String location, String className) {
if (!interfaceClass.isAssignableFrom(loadedClazz)) {
throw new IllegalArgumentException("Error when load extension of extensible " + interfaceName +
" from file:" + location + ", " + className + " is not subtype of interface.");
}
Class<? extends T> implClass = (Class<? extends T>) loadedClazz;
Extension extension = implClass.getAnnotation(Extension.class);
if (extension == null) {
throw new IllegalArgumentException("Error when load extension of extensible " + interfaceName +
" from file:" + location + ", " + className + " must add annotation @Extension.");
}
// 省略部分代码
ExtensionClass old = all.get(alias);
ExtensionClass<T> extensionClass = null;
if (old != null) {
if (extension.override()) {
if (extension.order() < old.getOrder()) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Extension of extensible {} with alias {} override from {} to {} failure, ",
interfaceName, alias, old.getClazz().getName(), implClass.getName());
}
} else {
all.put(alias, extensionClass);
if (factory != null) {
factory.remove(alias);
}
}
}
} else {
all.put(alias, extensionClass);
}
for (ExtensionLoaderListener<T> listener : listeners) {
listener.onLoad(extensionClass);
}
}
3.6 扩展类封装 ExtensionClass
ExtensionClass
用于封装扩展类的信息,包括类名、别名、编码、是否单例、排序等。它还提供了获取扩展实例的方法。
public class ExtensionClass<T> implements Sortable {
protected final Class<? extends T> clazz;
protected final String alias;
protected byte code;
protected boolean singleton;
protected int order;
protected boolean override;
protected String[] rejection;
private volatile transient T instance;
public ExtensionClass(Class<? extends T> clazz, String alias) {
this.clazz = clazz;
this.alias = alias;
}
public T getExtInstance() {
return getExtInstance(null, null);
}
// 该方法通过单例模式或非单例模式创建并返回扩展点对象的实例。在单例模式下,使用双重检查锁定确保只有一个实例被创建。
public T getExtInstance(Class[] argTypes, Object[] args) {
if (clazz != null) {
try {
if (singleton) { // 如果是单例
if (instance == null) {
synchronized (this) {
if (instance == null) {
instance = ClassUtils.newInstanceWithArgs(clazz, argTypes, args);
}
}
}
return instance; // 保留单例
} else {
return ClassUtils.newInstanceWithArgs(clazz, argTypes, args);
}
} catch (SofaRpcRuntimeException e) {
throw e;
} catch (Exception e) {
throw new SofaRpcRuntimeException(LogCodes.getLog(LogCodes.ERROR_CREATE_EXT_INSTANCE,
clazz.getCanonicalName()), e);
}
}
throw new SofaRpcRuntimeException(LogCodes.getLog(LogCodes.ERROR_EXTENSION_CLASS_NULL));
}
// 省略 getter 和 setter 方法
}
4 缓存和管理机制
SOFARPC 定义了多个缓存类,用于存储不同类型的数据,这些缓存类主要使用 ConcurrentMap
来实现线程安全的存储。以下是几个主要的缓存类及其用途:
ReflectCache
:用于缓存反射相关的信息,如类加载器、类、方法等。
public final class ReflectCache {
// 应用对应的ClassLoader
@VisibleForTesting
static final ConcurrentMap<String, ClassLoader> APPNAME_CLASSLOADER_MAP = new ConcurrentHashMap<String, ClassLoader>();
// 服务对应的ClassLoader
@VisibleForTesting
static final ConcurrentMap<String, ClassLoader> SERVICE_CLASSLOADER_MAP = new ConcurrentHashMap<String, ClassLoader>();
// String-->Class 缓存
@VisibleForTesting
static final ConcurrentMap<String, WeakHashMap<ClassLoader, Class>> CLASS_CACHE = new ConcurrentHashMap<String, WeakHashMap<ClassLoader, Class>>();
// 其他缓存...
}
RpcCacheManager
:提供清理所有缓存的静态方法。
public class RpcCacheManager {
/**
* 清理缓存
*/
public static void clearAll() {
ReflectCache.clearAll();
}
}
4.1. 缓存的注册和获取
在 ReflectCache
类中,提供了一系列方法用于注册和获取缓存数据。
- 注册类加载器缓存:
// 注册应用级别的ClassLoader
public static void registerAppClassLoader(String appName, ClassLoader classloader) {
APPNAME_CLASSLOADER_MAP.put(appName, classloader);
}
// 注册服务级别的ClassLoader
public static void registerServiceClassLoader(String serviceUniqueName, ClassLoader classloader) {
SERVICE_CLASSLOADER_MAP.put(serviceUniqueName, classloader);
}
- 获取类加载器缓存:
// 获取应用级别的ClassLoader
public static ClassLoader getAppClassLoader(String appName) {
ClassLoader appClassLoader = APPNAME_CLASSLOADER_MAP.get(appName);
if (appClassLoader == null) {
return ClassLoaderUtils.getCurrentClassLoader();
} else {
return appClassLoader;
}
}
// 获取服务级别的ClassLoader
public static ClassLoader getServiceClassLoader(String serviceUniqueName) {
ClassLoader appClassLoader = SERVICE_CLASSLOADER_MAP.get(serviceUniqueName);
if (appClassLoader == null) {
return ClassLoaderUtils.getCurrentClassLoader();
} else {
return appClassLoader;
}
}
- 注册和获取类缓存:
// 放入Class缓存
public static void putClassCache(String typeStr, Class clazz) {
CLASS_CACHE.putIfAbsent(typeStr, new WeakHashMap<ClassLoader, Class>());
CLASS_CACHE.get(typeStr).put(clazz.getClassLoader(), clazz);
}
// 得到Class缓存
public static Class getClassCache(String typeStr) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader == null) {
return null;
} else {
Map<ClassLoader, Class> temp = CLASS_CACHE.get(typeStr);
return temp == null ? null : temp.get(classLoader);
}
}
4.2. 缓存的清理
ReflectCache
类提供了 clearAll
方法,用于清理所有的缓存数据。
static void clearAll() {
CLASS_CACHE.clear();
TYPE_STR_CACHE.clear();
APPNAME_CLASSLOADER_MAP.clear();
SERVICE_CLASSLOADER_MAP.clear();
NOT_OVERLOAD_METHOD_CACHE.clear();
// 其他缓存清理...
}
4.3 SPI 机制与缓存管理的结合
在 SOFARPC 中,SPI 机制用于动态加载不同的服务实现,而缓存管理则用于提高这些服务实现的访问效率。例如,在 Cluster
类中,通过 SPI 机制可以动态加载不同的集群实现,而在实际调用过程中,可以使用缓存来存储一些常用的信息,如方法签名、类加载器等,以减少反射调用的开销。
@Extensible(singleton = false)
@ThreadSafe
public abstract class Cluster implements Invoker, ProviderInfoListener, Initializable, Destroyable {
// 服务端消费者启动器
protected final ConsumerBootstrap consumerBootstrap;
// 配置
protected final ConsumerConfig consumerConfig;
// 其他方法...
}
5. 总结
SOFA RPC 的 SPI 机制通过对 Java SPI 的改进和扩展,提供了强大的扩展性和灵活性。它允许开发者在不修改框架核心代码的情况下,对框架的各个功能组件进行定制和扩展,如服务注册与发现、远程调用、负载均衡、过滤器等。通过核心注解和拓展点加载流程,SOFA RPC 能够高效地管理和加载扩展点的实现类,为分布式系统的开发提供了便利。