概述
ExtensionLoader的原理
SPI
java中的SPI
SPI(Service Provider Interface)是java中一种服务发现机制,通过在classpath下的META-INF/services下指定一接口全路径名的文件中指定具体实现类(可配置多个),通过ServiceLoader进行动态可替换的服务方式调用。
测试类
package com.example.spi;
import java.util.ServiceLoader;
public class SpiMain {
public static void main(String[] args) {
ServiceLoader<Ilanguage> serviceLoader = ServiceLoader.load(Ilanguage.class);
for(Ilanguage service:serviceLoader){
service.call();
}
}
}
接口类及实现类
package com.example.spi;
public interface Ilanguage {
void call();
}
package com.example.spi;
//实现类之一,中文
public class IChineseLanguage implements Ilanguage {
@Override
public void call() {
System.out.println("Chinese call impl");
}
}
//实现类之一,英文
package com.example.spi;
public class IEnglishLanguage implements Ilanguage {
@Override
public void call() {
System.out.println("English call impl");
}
}
提供扩展配置文件倍SPI加载
接口名文件中指定实现类全路径名,比如com.example.spi.IChineseLanguage(如有多个,换行添加不同实现类全路径名)
此时通过serviceLoader会加载到所有配置的服务实现类并调用
Spring中的SPI
spring作为java中流行的应用框架之一,也有对于SPI的变体支持。这个常见于spring boot的自动装配中,应用可以提供自己的自动配置类在resourses/META-INF/spring.factories中,可以被@SpringBootApplication中->@EnableAutoConfiguration->AutoConfigurationImportSelector进行扫描加载
其中SpringFactoriesLoader中会去当前classpath的指定路径下(META-INF/spring.factories)扫描出所有适合的注解类(EnableAutoConfiguration 指定的类)
在默认的autoconfigure包下,可以看到spring已经默认提供了很多自己内置的自动装配类,包括Aop配置类等,因此在springboot中不再需要像framework里加上@EnableAspectJAutoProxy注解,手动开启aop切面
dubbo中的SPI
dubbo中很多核心功能点都是通过SPI的机制进行动态配置和选择的,包括常见的Protocol,Proxy等,允许开发者在自己的应用中实现并选用自己的扩展类。
ExtensionLoader是一个扩展加载类,相当于Java中的ServiceLoader,用于加载发现接口配置的实现类。每一种SPI接口都有一个对应的ExtensionLoader类,其中EXTENSION_LOADERS,EXTENSION_INSTANCES是类静态变量会将所有已经加载过的接口及对应的实例保存起来,避免二次加载。
其中每个SPI接口的获得,通过调用getExtensionLoader(Class type)入口开始;
1.校验: type必须是SPI注解的接口才满足条件
2.缓存拿或者实例化:从EXTENSION_LOADERS中拿,或者实例化new ExtensionLoader(type)存入
3.获取指定extension的实现类:T getExtension(String name)
具体流程参照下面xmind图解
如Protocol自适应扩展类,动态根据url中的Protocol参数值选择具体Protocol实现并生成代理java代码如下
package org.apache.dubbo.rpc;
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.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);
}
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 java.util.List getServers() {
throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
}
injectExtension(T instance) VS Wrapper
injectExtension要求SPI必须至少有一个对应的Adaptive方法,否则会报错。
因为AdaptiveExtensionFactory
构造时通过dubbo加载目录下的ExtensionFactory接口通过type+name获取所有的扩展实现
在支持的扫描目录下发现dubbo-commonsrcmainresourcesMETA-INFdubbointernalorg.apache.dubbo.common.extension.ExtensionFactory实际上只有一个真正的实现接口即SpiExtensionFactory
,因此调用getAdaptiveExtension总是拿到动态适应扩展接口
所以要求必须是一个带自适应方法的SPI接口,如Protocol。
如果接口实现类中有相应的setter方法,会尝试去根据方法名解析出来的key作为extension name,通过ExtensionFactory getExtension(Class type, String name)找到自适应代理类,调用方法反射注入。例如,下面定义的MockProtocolWrapper中setMockprotocol方法会解析出property: mockprotocol去ExtensionLoader找该实现类
String property = getSetterProperty(method);
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
以Protocol为例,自己加一个扩展类MockProtocolWrapper并放在SPI加载的目录中
该实现类是一个包装类,也是一个可以injectExtension(调用set方法注入的类)
package org.apache.dubbo.config.mock;
//@Wrapper(matches = "mockprotocol")
public class MockProtocolWrapper implements Protocol {
private Protocol mockprotocol;
private Protocol ctrMockprotocol;
public MockProtocolWrapper(Protocol protocol){
System.out.println("construct "+ctrMockprotocol+" to "+protocol);
this.ctrMockprotocol = protocol;
}
public void setMockprotocol(Protocol protocol) {
System.out.println("invoke setMockprotocol method with "+protocol);
this.mockprotocol = protocol;
}
执行完后的结果如下,可以知道通过构造方法传入的Protocol是一个可无限包装扩展的包装类,而setter方法找到的是一个运行时动态生成的Adaptive代理对象
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class)
.getExtension("mockprotocol");
包装类本身并不作为一种扩展实现类存在ExtensionLoader中,而是用于包装其他符合条件的同类型接口的实现类。例如上述代码中,你通过包装类配置的name即mockProtocolWrapper无法找到一种实现类
java.lang.IllegalStateException: No such extension org.apache.dubbo.rpc.Protocol by name mockProtocolWrapper, no related exception was found, please check whether related SPI module is missing.
而通过包装类可修饰的其他实现类的配置名如mockprotocol找出来的实际是一个包装对象MockProtocolWrapper(如果没有自定义包装类,一般为ProtocolFilterWrapper),并不是原实现类本身。
最后
以上就是贪玩墨镜为你收集整理的Dubbo系列(一) 从SPI开始看ExtensionLoader的全部内容,希望文章能够帮你解决Dubbo系列(一) 从SPI开始看ExtensionLoader所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复