前面在介绍Java SPI的时候,说了SPI是一种服务发现机制, 那么有了Java SPI,为什么还需要Dubbo SPI了?这里在开头先介绍一下Dubbo SPI相比较于Java SPI有哪些优点, 后面我们在结合Java SPI和Dubbo SPI的源代码来说明是怎么实现这些功能了。
1. Java SPI会一次性实例化所有服务拓展点实现, 如果有拓展点实现类初始化非常耗时,但是发现加载好了有没有用上,会比较浪费资源。
2. 如果拓展点实现加载失败,连加载失败的拓展点的名称都拿不到。 比如JDK标准的ScriptEngine, 通过getName获取脚本类型名称,但如果RubyScriptEngine因为依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉了,和ruby对应不起来, 当用户执行ruby脚本的时候,会报不支持ruby,而不是真正的原因。
3. 增加了拓展点AOP和IOC支持, 拓展点可以通过setter方法注入其他拓展点实例
使用方式
Java SPI:(使用的是DriverManager中代码)
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()){
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
Java SPI从_classpath*:META-INF/services_中和拓展点全路径相同的文件中加载拓展点实现
Mysql驱动jar包中的文件META-INF/services/java.sql.Driver内容就是:
com.mysql.jdbc.Drvier
Dubbo SPI: (这里截取的是Dubbo中使用SPI机制加载Protocol拓展点的代码)
ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(Constants.REGISTRY_PROTOCOL); //or .getAdaptiveExtension() .getActivateExtension()
其实在Dubbo SPI获取到ExtensionLoader实例之后还可以通过getAdaptiveExtension()获取动态的拓展点实现; getActivateExtension()获取。。。。? 这2个功能在讲解完基础功能之后再来讲解。
Dubbo SPI从以下目录
classpath*:META-INF/dubbo/ META-INF/services/ META-INF/dubbo/internal
中加载拓展点实现
Dubbo SPI源码
在了解了Dubbo SPI相比较于Java SPI有哪些优点和Dubbo SPI和Java Spi的使用方式之后,再通过源码来了解Dubbo SPI是怎么实现这些功能的。我们在这里阅读源码的切入点就是 ExtensionLoader.getExtensionLoader(Protocol.class)
.getExtension(Constants.REGISTRY_PROTOCOL);
这里最开始是通过ExtensionLoader.getExtensionLoader(clazz)来获取一个ExtensionLoader实例,然后再通过这个实例来获取指定名称的拓展点实现getExtension。
1. ExntesionLoader.getExtensionLoader
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 interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
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;
}
这里做了一些检查,但是最关键的还是new ExtensionLoader(type) , EXTENSION_LOADERS这里是对EextensionLoader实例做缓存,避免重复创建实例.
new ExtensionLoader(type): 这里的构造方法是一个私有的构造方法
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
主要功能是记录这个ExtensionLoader是哪个拓展点药获取拓展点实现: Class<?> type。
再就是创建了一个objectFactory:ExtensionFactory成员变量(这个objectFactory就是后面Dubbo SPI实现IOC基础)。
这里还说明一下,objectFactory:ExtensionFactory这个成员变量也是通过ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtesion()来获取拓展点实现的。这里ExtensionFactory因为明确了不需要其他的注入,所以这里的objectFactory就是Null值。
上面说完了怎么创建extensionLoader实例,再拿到extensionLoader实例,我们就可以通过getExtension(name)去获取指定名称的拓展点实现。
2. extensionLoader.getExtension(name)
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(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;
}
阅读上面的代码,忽略一些缓存的细节,发现createExtension(name)才是最关键的,在其中才是创建指定名称拓展点的实现代码。
3. createExtension(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);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && wrapperClasses.size() > 0) {
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 + ") could not be instantiated: " + t.getMessage(), t);
}
}
阅读createExtesion(name)的内容, 发现主要有4个主要关注点
1. Classs clazz = getExtensionClasses().get(name)
2. T instance = Clazz.newInstance
3. injectExtension(instance)
4. wrapperClass.getConsturcotor(type).newInstance(instance)
上面的方法基本都可以做到见名知意。
getExtensionClasses().get(name)
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;
}
可以看到getExtensionClasses()里面也是做了一些缓存的工作的,但是最主要的功能实现点还是_loadExtensionClasses()_。
// synchronized in getExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if (value != null && (value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if (names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
if (names.length == 1) cachedDefaultName = names[0];
}
}
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadFile(extensionClasses, DUBBO_DIRECTORY);
loadFile(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
可以看到loadExtesionClassess的工作就是从classpath*:META-INF/dubbo/ ; META-INF/services ; META-INF/dubbo/internal这3个目录中加载type对应Class的全路径相同的文件中加载文件内容,文件内容格式:
_name:_拓展点实现类全路径
具体的加载工作在loadFile中完成,(这里不具体讲解这个读取的细节,后面可能会带一点,这个内容其实也比较简单,不需要详细说), 加载完成的结果就会保存在Map<String,Class<?>> extensionClasses这个Map中,同时会在这个结果返回之后保存在Holder中做为一个缓存. (这个是在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;
}
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();
public class Holder<T> {
private volatile T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
这里的Holder就是包装类,里面使用volatile变量来保存数据,保证多线程环境下的数据可见性. (这里并不是我们讨论的主要关注点,轻轻带过)
在这里我们了解了getExtensionClasses()的主要内容,就是去dubbo指定的3个目录下去加载指定拓展点的有哪些拓展实现,并将这些拓展实现类Class加载进来。
这里说到加载拓展实现类的Class到JVM中来,就插入一下
浏览loadFile中的内容,可以看到是使用class.forName(line,true,classLoader)方式将拓展点实现的Class对象加载到JVM中来的。(还有种方式classLoader.loadClass)
clazz.newInstance
在通过getExtensionClassess().get(name)获取到指定名称拓展点实现的Class对象后,就可以通过clazz.newInstance的反射获取拓展点实现的实例.
这里的关于clazz.newInstance反射创建拓展点实现类实例没有什么好说的,需要注意的一点就是clazz.newInstance是使用的默认无参构造方法创建的实例,如果没有无参构造函数是会抛出异常的。
InjectExtension(instance)
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
Class<?> pt = method.getParameterTypes()[0];
try {
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
injectExtension(instance)这里就是Dubbo SPI中增加的IOC功能的实现.
injectExtension要注入的对象也是带有setxxx方法的成员变量,说明它也是通过setxxx方法反射注入的。 对于注入进入的实例是通过objectFacotry.getExtension(pt,property)来获取的.
在injectExtension中要注入的对象明确了(带setxxx方法的成员变量需要注入),用什么方法注入也知道了(使用setxxx method反射注入实例到成员变量), 那么就来看看是怎么获取这个注入的实例的: objectFactory.getExtension(pt,proeprty)
这里的objectFactory就是在ExtensionLoader实例创建的时候初始化的ExtensionFactory实例:
这里获取到的ExtensionFactory的adaptive实现就是AdaptiveExtensionFactory这个类(这里我们先跳过getAdaptiveExtension()这个方法实现,先直接得到结果,后面我们再讲getAdaptvieExtension()这个方法)
AdaptiveExtensionFactory
可以看到在AdaptiveExtensionFactory.getExtension中的行为也是遍历ExtensionFactory的实现类,调用它们的getExtension(type,name)方法来获取实例的。 这里AdaptiveExtensionFactory只是做了一个聚合作用。
在ExtensionFactory这个拓展点的所有实现中,只要SpringExtensionFactory和SpiExtensionFactory才是真正获取实例的实现。
SpringExtensionFactory
可以看到SpringExtensionFactory是使用ApplicationContext从Spring IOC容器中获取实例的,然后给Dubbo SPI完成injectExtension的。
SPIExtensionFactory
在SPIExtensionFactory中才是通过ExtensionLoader.getExtensionLoader(), 然后通过getAdaptiveExtension从Dubbo SPI自己的IOC容器中获取实例来完成injectExtension中的注入动作. (这里Dubbo SPI自己的IOC容器就是ExtensionLoader中的各种cache,对extensionLoader实例的cache,对拓展点实现类实例的cache等等,来完成和Spring beanFactory类似的功能)
wrapperClass.getConsturcotor(type).newInstance(instance)
在wrapperClass.getConstrutor(type).newInstance(instance)中其实是完成了Dubbo SPI中的AOP功能,接下来就了解一下适合怎么实现的.
首先来看看cachedWrapperClasses是哪里来的,跟踪cachedWrapperClasses成员变量的赋值, 最终在loadFile发现了它的赋值。(也就是前面getExtensionClasses()中涉及的从文件中读取拓展点实现的配置的地方)
loadFile
可以看到这里是只有存在clazz.getConstructor(type):也就是有带拓展点Class的构造函数的拓展点实现类才加入到cachedWrapperClasses里面, 如果没有的话就会抛出NoSuchMethodException,也就是下面这张图中的处理逻辑。
那么有带拓展点Class的构造函数拓展类的拓展实现类是什么了?拓展实现类实现拓展点这个接口,在其中还有带拓展点接口Class的构造函数,这想一下不就是静态代理(Proxy)设计模式的类结构么。
搞明白了是哪些是cachedWrapperClasses之后, 再来看Dubbo Spi的Aop功能
这里这里是使用的for循环来处理的wrapperClasses,那么也就是说明如果存在多个代理Proxy, 这里也是一层层的将它们套起来,完成多个代理的工作, 那么多个代理的顺序是怎么 样的了? Answer: 在loadeFile中是一行行的读取的配置,然后add到cachedWrapperClasses中的, 那么多个代理的顺序就是拓展点配置文件中的顺序。 那么如果是在多个文件中了?那么首先肯定是按照目录顺序来的,因为读取的时候时候这样的
META-INF/dubbo/internal -> META-INF/dubbo/ -> META-INF/services
那么这里还有个问题了:如果这里instance是代理本身实现类实例怎么做,是不是也会进行一遍遍的代理? Instance是通过clazz.newInstance方法也就是默认构造方法创建的实例,如果拓展点代理实现类本身也有默认的构造方法话,这里确实是也会一遍遍代理的,但是一般拓展点代理实现类是没有默认构造方法的,只有一个带拓展点接口类的有参构造函数,所以如果故意的使用extensionLoder.getExtension(proxyName)来获取代理拓展类实现是会抛出异常的, 可以看到在createExtension中也是对异常进行了处理的。
好了,到这里我们就对Dubbo Spi中的AOP功能实现了解的差不多了, Dubbo Spi中的AOP实现主要是使用的静态代理模式,在实现的时候是有Proxy实现类的,不像是spring aop中那样通过切点/切面等一系列配置来动态的生成一个Proxy实现类(动态的意思是,在编程类结构中没有Proxy实现类的,在运行的时候才将这个Proxy实现了加载到JVM中来)
阶段总结
这里再总结一下Dubbo SPI中都有哪些角色参与了,来一张类图
ExtensionFactory负责实现的是Dubbo Spi中的IOC功能.
Java SPI
这里也顺便说一下Java SPI中也都有哪些角色参与了,同样也是类图
在Java SPI中是通过serviceLoader.iterator()中的LazyIterator完成配置文件的读取(META-INF/serivces下面)和对应Class加载和对应的实例化的.
ServiceLoader.load(servcie,loader)
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
这里就是简单的创建了一个serviceLoader实例, 和Dubbo SPI 中的ExtensionLoader.getExtensionLoader一样的,只是简单的记录一下要加载的拓展点的class对象. 它的主要工作还是在serviceLoader.iterator()中返回的LazyIterator的hasNext/next方法中实现的.
serviceLoader.lterator
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
可以看到这个返回的iterator基本就是lookupIterator:LazyIterator; knownProviders这里是指做的缓存的作用.
来看LazyIterator中的hasNext和next功能
LazyIterator.hasNext
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
在hasNext其实就是去读取META-INF/services/下和拓展点全路径相同的文件里面的配置,配置了有哪些实现类。
LazyIterator.next
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
在LazyIterator.next中就将hasNext中读取的配置的实现类Class加载到Jvm中来,可以看到的是也是使用的Class.forName(cn,false,loader)来加载的Class到JVM中,和Dubbo SPI中保持的是一直的。 下面就是紧接着的c.newInstance 来通过反射方式创建实现类的实例。
通过阅读ExtensionLoader和ServcieLoader的源码实现,我们发现了ExtensionLoader在获取指定名称的拓展点实现的时候只创建了指定实现类的实例,没有创建其他实现类的实例。(在最开是的时候,我还是以为Dubbo SPI比较与Java SPI是没有加载多余的Class对象了,其实是没有创建多余的实例)
Question: 如果这里不加载多余的Class对象进入JVM是否有必要?
@Adaptive
Dubbo Spi中的@Adaptive注解是用来做什么的?在实际场景中,一个拓展点接口往往有多种实现。因为Dubbo是基于URL驱动的,所以在运行时,可以通过传入的URL中某些参数来动态的控制具体实现,这就是@Adaptive要实现的Dubbo拓展点自适应实现。
(这里不是某些文章中说的动态代理实现, 而是一种动态选择功能实现,算是一种策略模式吧… 也不是很准确)
关于@Adaptive的使用
@Adaptive看这个注解的注释,可以知道这个注释一般用在类和方法上面。 在整个Dubbo中,@Adaptive使用在类上的也就那么几个类: AdaptiveExtensionFactory和AdaptiveCompiler。其他的都是使用在方法上。
@Adaptive用在类上面,表示拓展点子类会在里面进行Adaptive机制的自定义实现,算是个标记作用,说我这里是一个Adaptive机制的实现。
像前面介绍的AdaptiveExtensionFactory中所谓的Adaptive机制实现就是使用SpringExtensionFactory和SPIExtensionFactory来获取要注入的实例。 这里的Adaptive(自适应)就体现在可以通过Spring IOC容器和Dubbo IOC容器来获取bean来完成注入。
(上面的都是个人理解,有好的阐述理解请告诉我哈,让我也知道一下哈)
@Adaptive使用在方法上,(这里我们先将用在方法上dubbo会怎么做,然后就知道它是用来做什么的了), 使用extensionLoader.getAdaptiveExtension()就会动态的生成一个子类拓展点实现。这个动态生成的拓展点子类里面的方法都是什么内容了?
没有@Adaptive注解的方法,就在方法体里面抛出UnsupportedOperationException.
有@Adaptive注解的方法,就会按照一定格式重写该方法:主要就是获取方法中的URL参数,然后通过URL获取某个参数值,再就是通过ExtensionLoader找和这个参数值匹配的实现类的实例,最后就是拿这个实例来调用对应的方法实现了。
这里以Dubbo中的拓展点Protocol为例子
@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();
}
这里有两个方法标记了@Adaptive,那么在使用extensionLoader.getAdaptiveExtension的时候生成的动态子类Protocol$Adaptive的内容就是
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);
}
}
可以看到getDefaultPort和destory方法都是抛出了UnsupportOperatorException. exprot和refer方法也按照一定规则override了。
在大致了解了@Adaptive使用的方法上会做了哪些事情之后,我们来看看是怎么生成这个$Adaptive子类的。
最开始的入口当然是: ExtensionLoader.getExtensionLoader(type).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("fail to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
可以看到这里也是和getExtension中一样的,有缓存之类的处理呀,我们通通跳过,直奔具体实现_createAdaptiveExtension_.
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
这里也有对getAdptiveExtensionClass().newInstance()的adaptive动态生成子类实例的注入动作。(和前面的getExtension里面差不多, 就不讲了,还是回到怎么生成子类上来)
那么很显然了,生成子类这个动作肯定就在_getAdaptiveExtensionClass_()这里面完成了.
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
private Class<?> createAdaptiveExtensionClass() {
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
这里在getAaptiveExtensionClass中调用一下getExtensionClasses是干啥??
生成子类的动作看到了在createAdaptiveExtensionClass()里面,createAdaptiveExtensionClassCode()负责生成子类的原始代码,然后由Compiler编译加载成Class对象.
Compiler的内容就暂时不聊了,来看看_createAdaptiveExtensionClassCode_()生成的原始代码是什么样的. (emm,这个写的很长,我们就一段段的解释,不全贴上来了)
这校验了一下,如果在所有方法上都没有@Adaptive的话,就不用生成子类了;如果生成了类名就是Protocol$Adaptive。(这里只是举例说明,具体的类就不同了)
这里对于拓展点接口中没有@Adaptive标记的方法,在生成的子类方法中是抛出了UnsupportOperationnException.
下面的else就是处理的有@Adaptive注解标记的方法了:
这里就是在方法体中找有没有URL类型的参数,如果有就是这了,顺便判断一下是否为空,并将值赋给url变量。这个URL就是用来获取参数的,然后动态获取实现类调用方法的。
如果是在方法体中没有找到URL类型的参数就在方法体参数找有没有参数的get方法是返回URL类型的。
这里找到了的话也加了一下对method的返回值的null的判断,毕竟是通过get method给的返回值。
上面就是加入的判null的代码,下面的就是给url赋值的代码。(和前面从方法体中直接获取URL参数赋值对应上)
这里我们有了URL参数了,那么我们到底是去获取URL中的哪个参数的值,然后用来动态调用实现类的方法了?
可以看到获取参数,是用到的value[i]来指定去获取哪个参数的值的。
那么我回过头来上去看看这个value中是写什么值.
这里可以看到value默认的是@Adaptive注解中给定的值
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
String[] value() default {};
}
可以看到这个@Adaptive的value是可以为空的,如果为空的话,就是用拓展点接口simpleName转换后的一个结果作为参数。
像blog.csdn.net/weixin_3396… 这里面举例说明的拓展点接口FruitGranter, 那么这个url参数就是 fruit.granter ; 如果是Protocol接口,这个url参数就是protocol 。
那么知道是获取URL中的哪个参数之后, 就是那一段获取这个url参数值的代码了:
这里很好理解,就是判断这个url参数该用什么方法去获取,然后生成相应的代码。
那么这个url参数值的获取的响应的代码也生成完了之后,那么它用来干啥。
这里在得到url参数值后,给extName这个变量.
这个变量用在ExtensionLoader.getExtensionLoader.getExtension(extName)中,也就是去获取指定名称的实现类。拿到实现类extension实例之后,就调用@Adaptive标记的这个方法,执行具体的实现操作。
所以看到这里,@Adaptive的功能就是根据URL中的参数来动态的调用拓展点的不同子类的实现。
这里的URL参数要么是@Adaptive中指定的,要么就是拓展点接口变换来的.
这里的动态体现在可以根据url中的参数来选择不同的实现。
举例说明,如果URL是_dubbo://192.168.0.101:20880?protocol=dubbo_的话,那么在Protocol$Adaptive这个子类中选择的实现就是DubboProtocol中实现的方法。
如果是_dubbo://192.168.0.101:20880?protocol=http_话就是时候用的HttpProtocol中的实现的方法。
到最后,也基本明白了@Adaptive注解在方法到底是实现了个什么功能了.