关于Java SPI
在了解Dubbo的SPI机制之前,我们先了解下Java提供的SPI (service provider interface) 机制,SPI是JDK内置的一种服务提供发现机制。目前市面上很多框架都用它来做服务的扩展发现。简单的说,它是一种动态替换发现的机制。
举个简单的例子,我们想在运行时动态给它添加实现,你只需要添加一个实现,然后把新的实现描述给JDK知道就行了。大家耳熟能详的如JDBC,日志框架都有用到。
实现 SPI 需要遵循的标准
我们如何去实现一个标准的 SPI 发现机制呢?其实很简单,只需要满足以下提交就行了 :
- 需要在 classpath 下创建一个目录,该目录命名必须是:META-INF/service
- 在该目录下创建一个 properties 文件,该文件需要满足以下几个条件 :
2.1 文件名必须是扩展的接口的全路径名称
2.2 文件内部描述的是该扩展接口的所有实现类
2.3 文件的编码格式是 UTF-8 - 通过 java.util.ServiceLoader 的加载机制来发现
SPI 的实际应用
SPI 在很多地方有应用,可能大家都没有关注,最常用的就是 JDBC 驱动,我们来看看是怎么应用的。
JDK 本身提供了数据访问的 api。在 java.sql 这个包里面 ,我们在连接数据库的时候,一定需要用到 java.sql.Driver 这个接口对吧。然后我好奇的去看了下 java.sql.Driver 的源码,发现 Driver 并没有实现,而是提供了一套标准的 api 接口。大家有兴趣可以去看看,因为我们在实际应用中用的比较多的是 mysql,所以我去 mysql 的包里面看到一个如下的目录结构
这个文件里面写的就是 mysql 的驱动实现。我恍然大悟,原来通过 SPI 机制把 java.sql.Driver 和 mysql 的驱动做了集成。这样 就达到了各个数据库厂商自己去实现数据库连接,jdk 本身不关心你怎么实现。
SPI 的缺点
- JDK 标准的 SPI 会一次性加载实例化扩展点的所有实现,什么意思呢?就是如果你在 META-INF/service 下的文件里面加了 N 个实现类,那么 JDK 启动的时候都会一次性全部加载。那么如果有的扩展点实现初始化很耗时或者如果有些实现类并没有用到, 那么会很浪费资源
- 如果扩展点加载失败,会导致调用方报错,而且这个错误很难定位到是这个原因
Dubbo中的SPI机制
Dubbo也用了SPI思想,不过没有用JDK的SPI机制,是自己实现的一套SPI机制。在Dubbo的源码中,很多地方会存在下面这样的三种代码,分别是自适应扩展点、指定名称的扩展点、激活扩展点。
ExtensionLoader.getExtensionLoader(xxx.class).getAdaptiveExtension();
ExtensionLoader.getExtensionLoader(xxx.class).getExtension(name);
ExtensionLoader.getExtensionLoader(xxx.class).getActivateExtension(url, key);
比如:
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
Protocol接口,在运行的时候dubbo会判断一下应该选用这个Protocol接口的哪个实现类来实例化对象。
它会去找你配置的Protocol,将你配置的Protocol实现类加载到JVM中来,然后实例化对象,就用你配置的那个Protocol实现类就可以了。
上面那行代码就是dubbo里面大量使用的,就是对很多组件,都是保留一个接口和多个实现,然后在系统运行的时候动态的根据配置去找到对应的实现类。如果你没有配置,那就走默认的实现类。
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
}
在dubbo自己的jar中,在META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol文件中:
filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=org.apache.dubbo.rpc.support.MockProtocol
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
injvm=org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol
rmi=org.apache.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=org.apache.dubbo.rpc.protocol.hessian.HessianProtocol
http=org.apache.dubbo.rpc.protocol.http.HttpProtocol
org.apache.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=org.apache.dubbo.rpc.protocol.thrift.ThriftProtocol
native-thrift=org.apache.dubbo.rpc.protocol.nativethrift.ThriftProtocol
memcached=org.apache.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=org.apache.dubbo.rpc.protocol.redis.RedisProtocol
rest=org.apache.dubbo.rpc.protocol.rest.RestProtocol
xmlrpc=org.apache.dubbo.xml.rpc.protocol.xmlrpc.XmlRpcProtocol
registry=org.apache.dubbo.registry.integration.RegistryProtocol
qos=org.apache.dubbo.qos.protocol.QosProtocolWrapper
所以这就看到了dubbo的SPI机制默认是怎么玩的了,其实就是Protocol接口,@SPI(“dubbo”) 说的是,通过 SPI 机制来提供实现类,实现类是通过 dubbo 作为默认 key 去配置文件里找到的,配置文件名称与接口全限定名一样的,通过 dubbo 作为 key 可以找到默认的实现类就是 org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
如果想要动态替换掉默认的实现类,需要使用 @Adaptive 接口,Protocol 接口中,有两个方法加了 @Adaptive 注解,就是说那俩接口会被代理实现。
比如这个 Protocol 接口搞了俩 @Adaptive 注解标注了方法,在运行的时候会针对 Protocol 生成代理类,这个代理类的那俩方法里面会有代理代码,代理代码会在运行的时候动态根据 url 中的 protocol 来获取那个 key,默认是 dubbo,你也可以自己指定,你如果指定了别的 key,那么就会获取别的实现类的实例了。
如何自己扩展 dubbo 中的组件
下面来说说怎么自己来扩展dubbo中的组件:
新建一个Java class实现Protocol接口,由于我们这里只是演示如何自己扩展Dubbo中的组件,所以这里只实现getDefaultPort方法,其它方法都空着:
在src/main/resources目录下创建META-INF/dubbo文件夹,然后创建文件,名字叫org.apache.dubbo.rpc.Protocol,文件里添加我们自己实现的Protocol接口的实现类,myProtocol是我们自定义协议的key。
在调用处执行如下代码 :
public class App
{
public static void main( String[] args )
{
// Main.main(args);
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myProtocol");
System.out.println(protocol.getDefaultPort());
}
}
运行后可以看到如下结果,证明执行的是我们自定义的Protocol扩展点。
总结:总的来说,思路和 SPI 是差不多。都是基于约定的路径下制定配置文件。目的是通过配置的方式轻松实现功能的扩展。
我们的猜想是,一定有一个地方通过读取指定路径下的所有文件进行 load。然后讲对应的结果保存到一个 map 中,key 对应为 名称,value 对应为实现类。那么这个实现,一定就在 ExtensionLoader 中了。接下来我们就可以基于这个猜想去看看代码的实 现。
Dubbo 的扩展点原理实现
在看Dubbo SPI的实现代码之前,我们先思考一个问题,所谓的扩展点,就是通过指定目录下配置一个对应接口的实现类,然后程序会进行查找和解析,找到对应的扩展点,那么这里就涉及到两个问题:
- 怎么解析
- 被加载的类如何存储和使用
ExtensionLoader.getExtensionLoader.getExtension
我们通过上面的例子可以知道,我们是通过下面这个代码去加载扩展点的:
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myProtocol");
- 1
我们从这段代码着手,去看看到底做了什么事情,能够通过这样一段代码实现扩展协议的查找和加载。
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;
}
从上面代码可以看出, 先会去检查我们想要的扩展点是否已经存在于EXTENSION_LOADERS这个缓存中,如果存在则直接返回,否则新创建一个ExtensionLoader。
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
如果当前的 type=ExtensionFactory,那么 objectFactory=null, 否则会创建一个自适应扩展点给到 objectFacotry,目前来说具 体做什么咱们先不关心,现在只要知道objectFactory 在这里赋值了,并且是返回一个 AdaptiveExtension(). 这个暂时不展开,后面再分析
getExtension
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.set(instance);
}
}
}
return (T) instance;
}
这个方法就是根据一个名字来获得一个对应类的实例,所以我们来猜想一下,回想一下前面咱们配置的自定义协议,name 实际上就是 myProtocol,而返回的实现类应该就是 MyProtocol。
同样的,先去看缓存中是否已经存在我们想要的扩展点,如果没有则新建一个
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));
}
}
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;
}
// synchronized in getExtensionClasses
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;
}
这个方法,会查找指定目录 /META-INF/dubbo和 /META-INF/services 下对应的 type->也就是本次演示案例的 Protocol 的 properties 文件,然后扫描这个文 件下的所有配置信息。然后保存到一个 HashMap 中(classes),key=name(对应 protocol 文件中配置的 myprotocol), value=对 应配置的类的实例
injectExtension
private T injectExtension(T instance) {
try {
if (objectFactory != null) { //objectFactory在这里用到了
for (Method method : instance.getClass().getMethods()) {
if (isSetter(method)) {
/**
* 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 {
String property = getSetterProperty(method);
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
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;
}
这个方法是用来实现依赖注入的,如果被加载的实例中,有成员属性本身也是一个扩展点,则会通过 set 方法进行注入。
分析到这里我们发现,所谓的扩展点,套路都一样,不管是 springfactorieyLoader,还是 Dubbo 的 spi。实际上,Dubbo 的功能 会更加强大,比如自适应扩展点,比如依赖注入
Adaptive 自适应扩展点
什么叫自适应扩展点呢?我们先演示一个例子,在下面这个例子中,我们传入一个 Compiler 接口,它会返回一个 AdaptiveCompiler。这个就叫自适应。
public class App
{
public static void main( String[] args )
{
Compiler compiler=ExtensionLoader.getExtensionLoader(Compiler.class).getAdaptiveExtension();
System.out.println(compiler.getClass());
}
}
运行结果:
通过结果我们可以知道,它返回的是AdaptiveCompiler这个类,它是怎么实现的呢?我们查看AdaptiveCompiler这个类,可以发现这个类上有一个注解@Adaptive。
这个就是一个自适应 扩展点的标识。它可以修饰在类上,也可以修饰在方法上面。这两者有什么区别呢?
简单来说,放在类上,说明当前类是一个确定的自适应扩展点的类。如果是放在方法级别,那么需要生成一个动态字节码,来 进行转发。
比如拿 Protocol 这个接口来说,它里面定义了 export 和 refer 两个抽象方法,这两个方法分别带有@Adaptive 的标识,标识是 一个自适应方法。 我们知道 Protocol 是一个通信协议的接口,具体有多种实现,那么这个时候选择哪一种呢? 取决于我们在使用 dubbo 的时候所 配置的协议名称。而这里的方法层面的 Adaptive 就决定了当前这个方法会采用何种协议来发布服务。
@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;
// 省略部分代码
}
getAdaptiveExtension
这个方法主要就是要根据传入的接口返回一个自适应的实现类 :
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError == null) {
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);
}
}
}
} else {
throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
cachedAdaptiveInstance是一个缓存,如果缓存中没有,则通过createAdaptiveExtension创建一个。
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
createAdaptiveExtension这个方法主要做了以下两件事:
- 获得一个自适应扩展点的实例
- 实现依赖注入
getAdaptiveExtensionClass方法中先调用getExtensionClasses()方法,这个方法我们前面已经提到,会加载当前传入的类型的所有扩展点,保存在一个 hashmap 中 这里有一个判断逻辑,如果 cachedApdaptiveClas!=null ,直接返回这个 cachedAdaptiveClass,这里大家可以猜一下,这个 cachedAdaptiveClass 是一个什么?
还记得前面讲过 Adaptive 可以方在两个位置,一个是类级别,一个是方法级别。那么这个 cachedAdaptiveClass 很显然,就是放在类 级别的 Adaptive,表示告诉 dubbo spi loader,“我是一个自适应扩展点,你来加载我吧” cachedAdaptiveClass 应该是在 加载解析/META-INF/dubbo 下的扩展点的时候加载进来的。在加载完之后如果这个类有@Adaptive 标识,则会赋值赋值而给 cachedAdaptiveClass
如果 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 会动态生成一个代理类 Protocol$Adaptive. 前面的名字 protocol 是根据当前 ExtensionLoader 所加载的扩展点来定义的。
动态生成字节码,然后进行动态加载。那么这个时候锁返回的 class,如果加载的是 Protocol.class,应该是 Protocol$Adaptive 这个 cachedDefaultName 实际上就是扩展点接口的@SPI 注解对应的名字,如果此时加载的是 Protocol.class,那么 cachedDefaultName=dubbo
Protocol$Adaptive
动态生成的代理类,以下是我通过 debug 拿到的代理类
前面传入进来的 cachedDefaultName,在这个动态生成的类中,会体现在下面标红的部分,也就是它的默认实现是 DubboProtocol
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException(
"The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException(
"The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0)
throws org.apache.dubbo.rpc.RpcException {
if (arg0 == null)
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url ("
+ url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader
.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1)
throws org.apache.dubbo.rpc.RpcException {
if (arg1 == null)
throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg1;
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url ("
+ url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader
.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
}
我们可以看到在export方法中,会先通过String extName = (url.getProtocol() == null ? “dubbo” : url.getProtocol());去获取extName, 在我们之前的例子中,url.getProtocol()方法返回的应该是"myProtocol",所以extName是myProtocol,然后通过下面代码去获取Protocol的实例:
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader
.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
图形理解
简单来说,上面的基于方法层面的@Adaptive,基本实现原理的图形大概是这样
injectExtension
对于扩展点进行依赖注入,简单来说就是如果当前加载的扩展点中存在一个成员属性(对象),并且提供了 set 方法,那么这个 方法就会执行依赖注入.
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
if (isSetter(method)) {
/**
* 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 {
String property = getSetterProperty(method);
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
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;
}
在 injectExtension 这个方法中,我们发现入口出的代码首先判断了 objectFactory 这个对象是否为空。这个是在哪里初始化的呢? 实际上我们在获得 ExtensionLoader 的时候,就对 objectFactory 进行了初始化。
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
然后通过 ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()去获得一个自适应的扩展点,进 入 ExtensionFactory 这个接口中,可以看到它是一个扩展点,并且有一个自己实现的自适应扩展点 AdaptiveExtensionFactory;
注意:@Adaptive 加载到类上表示这是一个自定义的适配器类,表示我们再调用 getAdaptiveExtension 方法的时候,不需要走 上面这么复杂的过程。会直接加载到 AdaptiveExtensionFactory。然后在 getAdaptiveExtensionClass()方法处有判断
我们可以看到除了自定义的自适应适配器类以外,还有两个实现类,一个是 SPI,一个是 Spring,AdaptiveExtensionFactory
AdaptiveExtensionFactory 轮询这 2 个,从一个中获取到就返回。
Activate 自动激活扩展点
自动激活扩展点,有点类似我们讲 springboot 的时候用到的 conditional,根据条件进行自动激活。但是这里设计的初衷是,对 于一个类会加载多个扩展点的实现,这个时候可以通过自动激活扩展点进行动态加载, 从而简化配置我们的配置工作。
@Activate 提供了一些配置来允许我们配置加载条件,比如 group 过滤,比如 key 过滤。
举个例子,我们可以看看 org.apache.dubbo.Filter 这个类,它有非常多的实现,比如说 CacheFilter,这个缓存过滤器,配置信息 如下
group 表示客户端和和服务端都会加载,value 表示 url 中有 cache_key 的时候
通过下面这段代码,演示关于 Filter 的自动激活扩展点的效果。没有添加“红色部分的代码”时,list 的结果是 10,添加之后 list 的结果是 11. 会自动把 cacheFilter 加载进来
public class App
{
public static void main( String[] args )
{
ExtensionLoader<Filter> loader=ExtensionLoader.getExtensionLoader(Filter.class);
URL url= new URL("","",0);
// url=url.addParameter("cache","cache");
List<Filter> filters=loader.getActivateExtension(url,"cache");
System.out.println(filters.size());
filters.forEach(filter -> {
System.out.println(filter);
});
}
}
Spring 中的SPI机制
SPI机制有什么使用场景
在项目中,想修改或扩展jar包中的执行逻辑,可以用SPI,达到此目的。
javaasist 不也可以达到这个目的么,修改字节码的方式。
用SPI的方式扩展,可以在jar包的限制规则(接口)下,扩展代码,不是乱扩展。jar包还是拥有主动权,而字节码那种形式,完全脱离了jar包的掌控。
所以一般是,拓展中间件编写SPI,或者编写中间件暴露SPI,这俩个场景。
最后总的来说,Java的SPI实现的比较简单,并没有什么其它功能;Spring得益于自身的ioc和aop的功能,所以也没有实现太复杂的SPI机制,仅仅是对Java做了一点简化和优化;但是dubbo的SPI机制为了满足自身框架的使用要求,实现的功能就比较多,不仅将ioc和aop的功能集成到SPI机制中,还提供注入自适应和自动激活等功能。
Java的SPI与dubbo的SPI有什么区别
- dubbo SPI带了 AOP,IOC逻辑。
- dubbo SPI可以选择性加载,而非全加载。
- dubbo SPI的生成方式是,javaassit,性能优于JDK自带SPI。