自适应扩展机制

刚开始看代码,其实并不能很好地理解dubbo的自适应扩展机制的作用,我们不妨先把代码的主要逻辑过一遍,梳理一下,在了解了代码细节之后,回过头再来思考自适应扩展的作用,dubbo为什么要设计这样一种扩展机制??它的优点在什么地方??

Adaptive注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
/**
* Decide which target extension to be injected. The name of the target extension is decided by the parameter passed
* in the URL, and the parameter names are given by this method.
* 该值决定了哪个扩展类会被自动注入。目标扩展名由传入的URL中的参数和Adaptive注解中的value值共同决定。
* value相当于一些key值,用这些key值从URL中获取相应的value值,最终决定扩展名。
* <p>
* If the specified parameters are not found from {@link URL}, then the default extension will be used for
* dependency injection (specified in its interface's {@link SPI}).
* 如果传入的URL中没有符合条件的扩展名,那么使用默认扩展名(默认扩展名由SPI注解的value决定)
* <p>
* For examples, given <code>String[] {"key1", "key2"}</code>:
* 例如,value的值是:String[] {"key1", "key2"}
* <ol>
* <li>find parameter 'key1' in URL, use its value as the extension's name</li>
* 首先在URL中查找key1,如果存在使用key1作为扩展名
* <li>try 'key2' for extension's name if 'key1' is not found (or its value is empty) in URL</li>
* 如果URL中找不到key1,查找key2,如果能找到使用key2作为扩展名
* <li>use default extension if 'key2' doesn't appear either</li>
* 如果key2也找不到,那么使用默认扩展名
* <li>otherwise, throw {@link IllegalStateException}</li>
* 如果默认扩展名为空,则抛异常
* </ol>
* If default extension's name is not give on interface's {@link SPI}, then a name is generated from interface's
* class name with the rule: divide classname from capital char into several parts, and separate the parts with
* dot '.', for example: for {@code org.apache.dubbo.xxx.YyyInvokerWrapper}, its default name is
* <code>String[] {"yyy.invoker.wrapper"}</code>. This name will be used to search for parameter from URL.
* 如果SPI注解中没有给出默认扩展名,就会通过如下规则生成默认扩展名:将驼峰式风格的接口名转换成.号分隔的名称作为扩展名,例如
* 接口名org.apache.dubbo.xxx.YyyInvokerWrapper会被转换成yyy.invoker.wrapper作为扩展名。用这个名称在URL参数中查找。
*
*
* @return parameter key names in URL
*/
String[] value() default {};

}

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;
}

主要逻辑就是检查缓存,如果缓存不错存在则加载自适应扩展类,使用双检查锁机制保证在多线程的情况下不会重复加载。都是惯用法,没什么可说的。

createAdaptiveExtension

private T createAdaptiveExtension() {
try {
//1. 获取自适应扩展类并实例化
//2. 自动注入属性值
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}

这里使用了dubbo实现的IOC功能,ExtensionLoader的IOC特性我们再上一节已经说过,通过ExtensionFactory接口的扩展类实现对属性的自动注入,不再赘述。

我们的重点还是放在如何生成自适应扩展类上,即getAdaptiveExtensionClass的逻辑。

getAdaptiveExtensionClass

private Class<?> getAdaptiveExtensionClass() {
//首先查找所有 资源文件,加载全部扩展类,但并未实例化
// 在加载扩展类的过程中,如果遇到带Adaptive注解的类,会把该类设到缓存cachedAdaptiveClass
//这种情况,由用户来实现自适应扩展的逻辑,比较简单
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
//大部分情况下,自适应扩展的逻辑不是由用户实现,而是由框架自动生成的,生成的逻辑就在下面的方法中
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

没啥好说的,继续看下一个方法

createAdaptiveExtensionClass

//重点来了,这个方法定义了生成自适应扩展类代码的总体方案,
//这个方法引出了自适应扩展机制的核心类AdaptiveClassCodeGenerator
private Class<?> createAdaptiveExtensionClass() {
//AdaptiveClassCodeGenerator类是关键代码
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
ClassLoader classLoader = findClassLoader();
//Compiler接口也是通过dubbo自己的spi机制加载的,
// Compiler接口的自适应扩展类是预先写好的代码,即AdaptiveCompiler
// 这里有一个有意思的问题,如果compiler的自适应扩展类也由框架自动生成,那么这里就会出现循环调用
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
//返回编译后的Class对象
return compiler.compile(code, classLoader);
}

AdaptiveClassCodeGenerator.generate

生成自适应扩展类代码的核心逻辑被封装在AdaptiveClassCodeGenerator类中,AdaptiveClassCodeGenerator的构造方法仅仅设置了要扩展的接口类型以及默认扩展类名(可能为空),我们直接从generate方法看起,

/**
* generate and return class code
* 生成自适应扩展类代码
*/
public String generate() {
// no need to generate adaptive class since there's no adaptive method found.
// 如果接口中没有带Adaptive注解的方法,说名该类型不需要自适应扩展,直接抛异常
if (!hasAdaptiveMethod()) {
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
} StringBuilder code = new StringBuilder();
// 生成package信息,与接口的包名相同
code.append(generatePackageInfo());
//生成import信息, 生成了如下的import语句
//import org.apache.dubbo.common.extension.ExtensionLoader
code.append(generateImports());
//生成类定义信息
// public class <接口名>$Adaptive implements <接口全限定名> {
code.append(generateClassDeclaration()); //生成方法信息,这一步是重点
Method[] methods = type.getMethods();
for (Method method : methods) {
code.append(generateMethod(method));
}
//最后在末尾加上一个花括号
code.append("}"); if (logger.isDebugEnabled()) {
logger.debug(code.toString());
}
return code.toString();
}

总体来看,逻辑还是比较简单的,就是我们平时写代码的步骤,依次生成包信息,import内容,类的具体代码包括每个方法的实现代码。

其中比较重要的是生成方法的实现代码,我们重点看一下这段代码。

generateMethod

private String generateMethod(Method method) {
// 方法的返回类型
String methodReturnType = method.getReturnType().getCanonicalName();
// 方法名称
String methodName = method.getName();
// 方法体,即方法的实现代码,这一步最关键
String methodContent = generateMethodContent(method);
// 方法参数,
// (ParamType0 arg0, ParamType1 arg1, ParamType2 arg2)
String methodArgs = generateMethodArguments(method);
// 异常信息,throws内容
String methodThrows = generateMethodThrows(method);
//最后按照java代码的规范拼接成一个完整的方法代码
return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
}

重点是生成方法具体实现代码的逻辑,generateMethodContent方法。

generateMethodContent

生成方法体的逻辑。这个方法比较长,我们分开来看。

/**
* generate method content
* 生成方法体
*/
private String generateMethodContent(Method method) {
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
// 这一句应该放到else分支内部
StringBuilder code = new StringBuilder(512);
// 如果方法不带Adaptive注解,就直接生成抛Unsupported异常的实现,
// 也就是说,自适应扩展机制必须要在需要自适应的方法上加Adaptive注解
if (adaptiveAnnotation == null) {
return generateUnsupported(method);
} else {
// 找到URL类型的参数的下标
int urlTypeIndex = getUrlTypeIndex(method); // found parameter in URL type
if (urlTypeIndex != -1) {
// Null Point check
// 如果方法参数列表中有URL类型的参数,那么添加对参数做非空检查的代码
code.append(generateUrlNullCheck(urlTypeIndex));
} else {
// did not find parameter in URL type
// 如果方法参数中没有URL类型,那么查找能够简介获取到URL的参数,这个参数类型应该含有返回类型的是URL的方法
code.append(generateUrlAssignmentIndirectly(method));
} // 获取Adaptive注解的value
// 如果Adaptive注解的value为空,获取会麻烦一些,将接口的驼峰式名称转换成.分隔的名称
// 如LoadBalance转换成load.balance
String[] value = getMethodAdaptiveValue(adaptiveAnnotation); // 检查Invocation类型的参数
boolean hasInvocation = hasInvocationArgument(method); // 生成检查Invocation类型参数非空的代码段,
// 只检查第一个参数??Invocation类型的参数只会有一个??
code.append(generateInvocationArgumentNullCheck(method)); // 生成获取扩展名的代码段,
// 扩展名就是资源文件中以k-v存储的key值
code.append(generateExtNameAssignment(value, hasInvocation));
// check extName == null?
// 生成扩展名非空检查语句
code.append(generateExtNameNullCheck(value)); // 获取到扩展名后,就可以生成扩展类的获取语句了
code.append(generateExtensionAssignment()); // return statement
// 最后加上函数调用和返回语句
code.append(generateReturnAndInovation(method));
} return code.toString();
}

生成的方法体大致分为三块

  • 获取URL的值。如果参数中有URL类型直接赋值;如果没有就间接获取,查找参数中包含返回URL类型的方法,调用这个方法。此外加上一些非空判断语句。
  • 获取扩展名。这一步比较复杂,要考虑的情况比较多。如果Adaptive注解中有protocol就要调用URL的getProtocol方法;用以Adaptive的value数组中的key值依次去URL中获取参数,直到获取到不为空的参数。加上扩展名的非空检查语句。
  • 通过ExtensionLoader获取扩展名对应的扩展类,赋值给变量extension
  • 最后调用扩展类的相应方法,如果返回值不是void就加上return。

其中获取扩展名代码的生成逻辑较为复杂,我们看一下这段代码。

generateExtNameAssignment

private String generateExtNameAssignment(String[] value, boolean hasInvocation) {
// TODO: refactor it
String getNameCode = null;
// 从后向前遍历可选的扩展名
for (int i = value.length - 1; i >= 0; --i) {
// 对于最后一个value值做如下处理
if (i == value.length - 1) {
if (null != defaultExtName) {
// 如果不是protocol
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
// 如果存在Invocation参数,生成获取方法参数的代码段
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
} else {
getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
}
} else {
// 如果是protocol,生成扩区protocol的代码段
getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
}
} else {
// 如果默认扩展名为空,在生成的代码中不使用带有默认扩展名的方法
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
} else {
getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
}
} else {
getNameCode = "url.getProtocol()";
}
}
//对于不是最后一个的value值做如下处理
} else {
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
} else {
getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
}
} else {
getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
}
}
} // 拼接成赋值语句
// String extName = %s;
return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode);
}

代码生成示例

这样光看代码很难对这块代码有直观的认识,理解也不够深刻,既然这段代码的作用主要是生成代码,那么我们就定义一个自己的接口,并实现一个扩展类,用AdaptiveClassCodeGenerator来生成一下,看生成的代码到底是什么样子。

  • 首先是接口

      package org.apache.dubbo.common.extension.adaptive.codeGen;
    
      import org.apache.dubbo.common.URL;
    import org.apache.dubbo.common.extension.Adaptive;
    import org.apache.dubbo.common.extension.SPI; @SPI("default")
    public interface MyExtension { @Adaptive
    String adaptiveMethod(String param1, int param2, URL url) throws Exception; @Adaptive("protocol")
    String adaptiveMethod2(String param1, int param2, URLGetter urlGetter) throws Exception; void noAdaptiveMethod(String param1, URLGetter urlGetter) throws Exception;
    }
  • 实现类

      package org.apache.dubbo.common.extension.adaptive.codeGen.impl;
    
      import org.apache.dubbo.common.URL;
    import org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension;
    import org.apache.dubbo.common.extension.adaptive.codeGen.URLGetter; public class DefaultMyExtension implements MyExtension {
    @Override
    public String adaptiveMethod(String param1, int param2, URL url) {
    return String.format("Params passed in are: param1:%s, param2:%d, url:%s");
    } @Override
    public String adaptiveMethod2(String param1, int param2, URLGetter urlGetter) throws Exception {
    return String.format("Params passed in are: param1:%s, param2:%d, urlGetter:%s");
    } @Override
    public void noAdaptiveMethod(String param1, URLGetter urlGetter) throws Exception { }
    }
  • URL获取类。为了测试简介获取URL

      package org.apache.dubbo.common.extension.adaptive.codeGen;
    
      import org.apache.dubbo.common.URL;
    
      public class URLGetter {
    private URL url; public URLGetter(URL url){
    this.url=url;
    } public URL getUrl() {
    return url;
    } public void setUrl(URL url) {
    this.url = url;
    } @Override
    public String toString() {
    return "URLGetter{" +
    "url=" + url +
    '}';
    }
    }
  • 测试类

      package org.apache.dubbo.common.extension.adaptive.codeGen;
    
      import org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator;
    import org.junit.jupiter.api.Test; public class AdaptiveClassCodeGeneratorTest { @Test
    public void testProtocolCodeGen() {
    AdaptiveClassCodeGenerator generator=
    new AdaptiveClassCodeGenerator(MyExtension.class,"default");
    String code=generator.generate();
    System.out.println(code);
    }
    }
  • 生成的代码。也就是自适应扩展类

      package org.apache.dubbo.common.extension.adaptive.codeGen;
    
      import org.apache.dubbo.common.extension.ExtensionLoader;
    
      public class MyExtension$Adaptive implements org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension {
    public java.lang.String adaptiveMethod(java.lang.String arg0, int arg1, org.apache.dubbo.common.URL arg2) throws java.lang.Exception {
    if (arg2 == null) throw new IllegalArgumentException("url == null");
    org.apache.dubbo.common.URL url = arg2;
    // Adaptive注解中没有value,用接口名称自动生成的key值
    String extName = url.getParameter("my.extension", "default");
    if (extName == null)
    throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension) name from url (" + url.toString() + ") use keys([my.extension])");
    org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension extension = (org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension) ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension.class).getExtension(extName);
    return extension.adaptiveMethod(arg0, arg1, arg2);
    } public java.lang.String adaptiveMethod2(java.lang.String arg0, int arg1, org.apache.dubbo.common.extension.adaptive.codeGen.URLGetter arg2) throws java.lang.Exception {
    if (arg2 == null)
    throw new IllegalArgumentException("org.apache.dubbo.common.extension.adaptive.codeGen.URLGetter argument == null");
    if (arg2.getUrl() == null)
    throw new IllegalArgumentException("org.apache.dubbo.common.extension.adaptive.codeGen.URLGetter argument getUrl() == null");
    // 间接获取URL
    org.apache.dubbo.common.URL url = arg2.getUrl();
    // 如果有protocol的key,那么调用URL.getProtocol方法获取协议名称
    String extName = (url.getProtocol() == null ? "default" : url.getProtocol());
    if (extName == null)
    throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension) name from url (" + url.toString() + ") use keys([protocol])");
    org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension extension = (org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension) ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension.class).getExtension(extName);
    return extension.adaptiveMethod2(arg0, arg1, arg2);
    } // 为标Adaptive注解的方法直接抛异常
    public void noAdaptiveMethod(java.lang.String arg0, org.apache.dubbo.common.extension.adaptive.codeGen.URLGetter arg1) throws java.lang.Exception {
    throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension.noAdaptiveMethod(java.lang.String,org.apache.dubbo.common.extension.adaptive.codeGen.URLGetter) throws java.lang.Exception of interface org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension is not adaptive method!");
    }
    }

这样看是不是直观多了。回过头再去看一遍代码生成逻辑,就好懂多了。

编译加载

代码生成之后,还需要将代码编译为字节码并通过类加载器加载到虚拟机中,获取Class对象,然后才能使用。

负责编译的是org.apache.dubbo.common.compiler.Compiler接口,这个接口的实现类也是通过ExtensionLoader进行加载的,见ExtensionLoader.createAdaptiveExtensionClass方法。

我们看一下Compiler接口

Compiler

// 默认扩展名是javassist
@SPI("javassist")
public interface Compiler { /**
* Compile java source code.
*
* @param code Java source code
* @param classLoader classloader
* @return Compiled class
*/
Class<?> compile(String code, ClassLoader classLoader); }

另外,我们再看一下dubbo-common模块中META-INF/dubbo/inetrnal/org.apache.dubbo.common.compiler.Compiler文件中的内容:

adaptive=org.apache.dubbo.common.compiler.support.AdaptiveCompiler
jdk=org.apache.dubbo.common.compiler.support.JdkCompiler
javassist=org.apache.dubbo.common.compiler.support.JavassistCompiler

AdaptiveCompiler类上带有Adaptive注解,所以ExtensionLoader在加载时AdaptiveCompiler类会被设置为自适应扩展类。而在createAdaptiveExtensionClass方法中真是通过Compiler接口的自适应扩展类来进行编译。那么我们看一下AdaptiveCompiler中的逻辑。

AdaptiveCompiler

public Class<?> compile(String code, ClassLoader classLoader) {
Compiler compiler;
ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
// 可以通过设置静态变量DEFAULT_COMPILER指定默认扩展类
String name = DEFAULT_COMPILER; // copy reference
if (name != null && name.length() > 0) {
compiler = loader.getExtension(name);
} else {
// 如果未指定,则获取SPI注解指定的默认扩展类
compiler = loader.getDefaultExtension();
}
return compiler.compile(code, classLoader);
}

由Compiler接口上的SPI注解,我们知道默认的实现类是JavassistCompiler,所以我们看一下这个类是怎么进行编译的。

JavassistCompiler

JavassistCompiler继承自AbstractCompiler,有一部分逻辑在AbstractCompiler类中,所以我们先看一下

AbstractCompiler.compile

private static final Pattern PACKAGE_PATTERN = Pattern.compile("package\\s+([$_a-zA-Z][$_a-zA-Z0-9\\.]*);");

private static final Pattern CLASS_PATTERN = Pattern.compile("class\\s+([$_a-zA-Z][$_a-zA-Z0-9]*)\\s+");    

public Class<?> compile(String code, ClassLoader classLoader) {
//去除两端空格
code = code.trim();
// 匹配包名
Matcher matcher = PACKAGE_PATTERN.matcher(code);
String pkg;
if (matcher.find()) {
pkg = matcher.group(1);
} else {
pkg = "";
}
// 匹配类名
matcher = CLASS_PATTERN.matcher(code);
String cls;
if (matcher.find()) {
cls = matcher.group(1);
} else {
throw new IllegalArgumentException("No such class name in " + code);
}
// 根据包名和类名拼接类的全限定名
String className = pkg != null && pkg.length() > 0 ? pkg + "." + cls : cls;
try {
// 尝试直接加载???类的字节码已经存在??或者类已经被加载过了??
// 什么情况下会出现这种情况??
return Class.forName(className, true, ClassHelper.getCallerClassLoader(getClass()));
} catch (ClassNotFoundException e) {
// 感觉没必要多这一句吧??
// 代码合法性检查应该在生成代码的方法中做,这地方单独做一个花括号检查没什么意义吧?
if (!code.endsWith("}")) {
throw new IllegalStateException("The java code not endsWith \"}\", code: \n" + code + "\n");
}
try {
// 由子类实现
return doCompile(className, code);
} catch (RuntimeException t) {
throw t;
} catch (Throwable t) {
throw new IllegalStateException("Failed to compile class, cause: " + t.getMessage() + ", class: " + className + ", code: \n" + code + "\n, stack: " + ClassUtils.toString(t));
}
}
}

主要就是拼接处类的全限定名,然后做一些检查,尝试直接加载类。真正的编译过程交由子类实现。

JavassistCompiler.doCompile

@Override
public Class<?> doCompile(String name, String source) throws Throwable {
// 实际执行编译的类
CtClassBuilder builder = new CtClassBuilder();
// 设置类名
builder.setClassName(name); // process imported classes
// 匹配出所有的import语句,并设置到CtClassBuilder对象中
Matcher matcher = IMPORT_PATTERN.matcher(source);
while (matcher.find()) {
builder.addImports(matcher.group(1).trim());
} // process extended super class
// 匹配出extends的类型,设置到CtClassBuilder对象中
matcher = EXTENDS_PATTERN.matcher(source);
if (matcher.find()) {
builder.setSuperClassName(matcher.group(1).trim());
} // process implemented interfaces
// 匹配出implements的类型,设置到CtClassBuilder对象中
matcher = IMPLEMENTS_PATTERN.matcher(source);
if (matcher.find()) {
String[] ifaces = matcher.group(1).trim().split("\\,");
Arrays.stream(ifaces).forEach(i -> builder.addInterface(i.trim()));
} // process constructors, fields, methods
// 添加方法和域
String body = source.substring(source.indexOf('{') + 1, source.length() - 1);
String[] methods = METHODS_PATTERN.split(body);
String className = ClassUtils.getSimpleClassName(name);
Arrays.stream(methods).map(String::trim).filter(m -> !m.isEmpty()).forEach(method-> {
if (method.startsWith(className)) {
builder.addConstructor("public " + method);
} else if (FIELD_PATTERN.matcher(method).matches()) {
builder.addField("private " + method);
} else {
builder.addMethod("public " + method);
}
}); // compile
ClassLoader classLoader = ClassHelper.getCallerClassLoader(getClass());
// 进行编译,实际的编译过程设计到字节码操作,由javassist库实现,这里不再深入下去
CtClass cls = builder.build(classLoader);
return cls.toClass(classLoader, JavassistCompiler.class.getProtectionDomain());
}

实际的编译过程设计到字节码操作,由javassist库实现,这里不再深入下去。

总结

自适应扩展模块主要分为两块,一是生成自适应扩展类的代码,二是将代码进行编译并加载编译后的字节码。

  • 其中dubbo实现的主要逻辑就是代码生成。

    自适应加载扩展类的主要思路就是从方法的参数中获取URL参数,可以是方法参数本省,也可以从参数的类型的方法中获取;

    然后从URL参数中查找扩展名(查找的范围通过Adaptive注解的value值和SPI注解中的默认值组成),找到扩展名后再由ExtensionLoader根据扩展名加载对应的扩展类,

    然后调用对应扩展类的对应方法并返回。

  • 编译代码的逻辑主要有javassist库实现,dubbo在javassist库基础上进行了一些简单封装,大部分的逻辑都是直接调用javassist库,这部分不再深入下去,如果对java代码编译感兴趣可以细看。

dubbo源码阅读之自适应扩展的更多相关文章

  1. 【Dubbo源码阅读系列】服务暴露之本地暴露

    在上一篇文章中我们介绍 Dubbo 自定义标签解析相关内容,其中我们自定义的 XML 标签 <dubbo:service /> 会被解析为 ServiceBean 对象(传送门:Dubbo ...

  2. 【Dubbo源码阅读系列】服务暴露之远程暴露

    引言 什么叫 远程暴露 ?试着想象着这么一种场景:假设我们新增了一台服务器 A,专门用于发送短信提示给指定用户.那么问题来了,我们的 Message 服务上线之后,应该如何告知调用方服务器,服务器 A ...

  3. dubbo源码分析5-dubbo的扩展点机制

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  4. 【Dubbo源码阅读系列】之远程服务调用(上)

    今天打算来讲一讲 Dubbo 服务远程调用.笔者在开始看 Dubbo 远程服务相关源码的时候,看的有点迷糊.后来慢慢明白 Dubbo 远程服务的调用的本质就是动态代理模式的一种实现.本地消费者无须知道 ...

  5. 【Dubbo源码阅读系列】之 Dubbo SPI 机制

    最近抽空开始了 Dubbo 源码的阅读之旅,希望可以通过写文章的方式记录和分享自己对 Dubbo 的理解.如果在本文出现一些纰漏或者错误之处,也希望大家不吝指出. Dubbo SPI 介绍 Java ...

  6. dubbo源码之一——xml schema扩展

    dubbo源码版本:2.5.4 dubbo-parent |----dubbo-config |----dubbo-config-api |----com.alibaba.dubbo.config.* ...

  7. Dubbo源码阅读顺序

    转载: https://blog.csdn.net/heroqiang/article/details/85340958 Dubbo源码解析之配置解析篇,主要内容是<dubbo:service/ ...

  8. dubbo源码阅读之服务导出

    dubbo服务导出 常见的使用dubbo的方式就是通过spring配置文件进行配置.例如下面这样 <?xml version="1.0" encoding="UTF ...

  9. Dubbo源码阅读-服务导出

    Dubbo服务导出过程始于Spring容器发布刷新事件,Dubbo在接收到事件后,会立即执行服务导出逻辑.整个逻辑大致可分为三个部分,第一部分是前置工作,主要用于检查参数,组装URL.第二部分是导出服 ...

随机推荐

  1. [C++] inline内联函数使用方法

    C++支持内联函数,目的是为了提高函数的执行效率,类似于C语言中的宏定义 内联函数在调用时将它在程序中的每个调用点展开,不用额外分配栈空间 内联函数的定义在一个源文件中出现一次,但在多个源文件中可以同 ...

  2. Nginx搭建简单文件下载服务器

    在C:\pleiades\nginx-1.16.1下新建一个目录files,然后放入若干文件,接下来修改nginx.conf,增加粗体字如下: #user nobody; worker_process ...

  3. js实现字符串切割并转换成对象格式保存到本地

    // split() 将字符串按照指定的规则分割成字符串数组,并返回此数组(字符串转数组的方法) //分割字符串 var bStr = "www.baidu.con"; var a ...

  4. @MatrixVariable的使用

    @MatrixVariable的使用 博客分类: J2EE   在Spring3.2 后,一个@MatrixVariable出现了,这个注解的出现拓展了URL请求地址的功能. Matrix Varia ...

  5. CentOS 7镜像下载

    官网下载链接:http://isoredirect.centos.org/centos/7/isos/x86_64/ step1: 进入下载页,选择阿里云站点进行下载 Actual  Country ...

  6. Linux_CentOS中Mongodb4.x 安装调试、远程管理、配置 mongodb 管理员密码

    Mongodb4.x 安装 官方文档:https://docs.mongodb.com/manual/tutorial/install-mongodb-on-red-hat/ 1.配置 yum 源 1 ...

  7. bim模型中所有IfcWallStandardCase构件

    ifc中的IfcWallStandardCase构件 //执行吊装 void startHoisting() { osg::Vec3f vec3f1 = index_node1->getBoun ...

  8. 使用肘部法确定k-means均值的k值

    import numpy as np from sklearn.cluster import KMeans from scipy.spatial.distance import cdist impor ...

  9. 通过Onvif设备探索获取EasyNVR网页无插件播放所需要的摄像机硬盘录像机NVR的RTSP地址

    想实现网络监控摄像头进行视频直播的朋友门应该知道,方法其实非常简单,你不需要使用支持直播的网络摄像机,只需要经过一套流媒体服务器将监控摄像头的RTSP视频流转为RTMP\HLS\HTTP-FLV视频流 ...

  10. 【技术】Arduino PID自整定库

    最近看到了Brett Beauregard发表的有关PID的系列文章,感觉对于理解PID算法很有帮助,于是将系列文章翻译过来!在自我提高的过程中,也希望对同道中人有所帮助.作者Brett Beaure ...