目录
getExtensionLoader(Protocol.class)
Dubbo SPI的Demo
Dubbo 的 SPI 扩展机制,有两个规则:
1、需要在 resource 目录下配置 META-INF/dubbo 或者 META-INF/dubbo/internal 或者 META-INF/services,并基于 SPI 接口去创建一个文件
2、文件名称和接口名称保持一致,文件内容和 SPI 有差异,内容是 KEY 对应 Value
Dubbo中的接口org.apache.dubbo.rpc.Protocol,有多种通讯协议实现,如下图,可以看到org.apache.dubbo.rpc.Protocol文件下的key以及对应的实现类的全路径。
那我们也可以自己写一个实现Protocol接口的实现类,做一个协议扩展。
public class MyProcol implements Protocol {
@Override
public int getDefaultPort() {
return 8888;
}
//暴露服务(Dubbo-> ;)
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
return null;
}
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
return null;
}
@Override
public void destroy() {
}
}
在resources下添加META-INF.dubbo 文件。类名和 Dubbo 提供的协议扩展点接口保持一致
测试
public class MainDemo {
public static void main(String[] args) {
Protocol protocol=ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myprotocol");
System.out.println(protocol.getDefaultPort());
}
}
可以看到自定义的协议扩展生效了。
Dubbo扩展点原理的实现
从上面可以看出,这个扩展点的实现关键在于这句代码
Protocol protocol=ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myprotocol");
getExtensionLoader(Protocol.class)
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);
}
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
先尝试在EXTENSION_LOADERS中获取ExtensionLoader,若获取不到则初始化一个ExtensionLoader,如果当前的 type=ExtensionFactory,type,那么 objectFactory=null, 否则会创建一个自适应扩展点给到 objectFacotry。
getExtension("myprotocol")
这个方法就是根据一个名字来获得一个对应类的实例,根据之前配置的自定义协议(myprotocol=com.dubbo.practice.practiceprovider.MyProcol),name 实际上就是 myprotocol,而返回的实现类应该就是 MyProtocol。
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {// 如果 name=true ,表示返回一个默认的扩展点
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;
}
这个默认的扩展点其实就是dubbo
createExtension(String name)
根据名称获取实例
private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);//EXTENSION_INSTANCES用于缓存实例
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()
会查找指定目录/META-INF/dubbo || /META-INF/services 下对应的 type->也就是 Protocol 的 properties 文件,然后扫描这个文件下的所有配置信然后保存到一个 HashMap 中(classes),key=name(对应 protocol 文件中配置的 myprotocol), value=对应配置的类的实例
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<>();
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;
}
injectExtension
回到 createExtension(String name)下的injectExtension(T instance)
这个方法是用来实现依赖注入的,如果被加载的实例中,有成员属性本身也是一个扩展点,则会通过 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) {
//调用 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;
}
所谓的扩展点,套路都一样,不管是 springfactorieyLoader,还是 Dubbo 的 spi。实际上,Dubbo 的功能会更加强大,比如自适应扩展点,比如依赖注入
Adaptive自适应扩展点
参考博客:https://www.liangzl.com/get-article-detail-5461.html
注解在类上
Compiler compiler=ExtensionLoader.getExtensionLoader(Compiler.class).getAdaptiveExtension();
System.out.println(compiler.getClass());
打印出的是class org.apache.dubbo.common.compiler.support.AdaptiveCompiler
设计目的是为了实现Dubbo SPI 时用来固定已知的类和扩展未知类。
注解在接口的实现类上:代表人工实现,实现一个装饰类,它主要用于固定已知类,目前整个系统只有两个,AdaptiveCompiler、AdaptiveExtensionFactory。
注解在方法上
注解在方法上,代表自动生成和编译一个动态的Adaptive类,每个方法都可以根据方法参数动态获取各自的扩展点,主要由于SPI 获取类为不固定的位置的扩展类,所以设计了动态的$Adaptive类。
例如 Protocol的spi类有injvm、dubbo、registry、filter、listener等很多未知扩展类,它设计了Protocol$Adaptive的类,再通过ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(spi类);来提取对象。
Protocol
所有扩展实现类上都没有@Adaptive
注解,且扩展接口含有两个 @Adaptive
注解的方法:exporter() refer()
,所以dubbo会生成一个动态类Protocol$Adaptive
,且它实现Protocol
接口来扩展这两个Adaptive方法。
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );从arg0中解析出扩展点名称extName,extName的默认值为@SPI的value。这是adaptive的精髓:每一个方法都可以根据方法参数动态获取各自需要的扩展点。
Protocol extension =(Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).
getExtension(extName);根据extName重新获取指定的Protocol.class扩展点。