5

Dubbo-Adaptive实现原理 - 大魔王先生

 2 years ago
source link: https://www.cnblogs.com/wtzbk/p/16656309.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前面我们已经分析Dubbo SPI相关的源码,看过的小伙伴相信已经知晓整个加载过程,我们也留下两个问题,今天我们先来处理下其中关于注解Adaptive的原理。

什么是@Adaptive

对应于Adaptive机制,Dubbo提供了一个注解@Adaptive,该注解可以用于接口的某个子类上,也可以用于接口方法上。如果用在接口的子类上,则表示Adaptive机制的实现会按照该子类的方式进行自定义实现;如果用在方法上,则表示Dubbo会为该接口自动生成一个子类,并且重写该方法,没有标注@Adaptive注解的方法将会默认抛出异常。对于第一种Adaptive的使用方式,Dubbo里只有ExtensionFactory接口使用,AdaptiveExtensionFactory的实现就使用了@Adaptive注解进行了标注,主要作用就是在获取目标对象时,分别通过ExtensionLoader和Spring容器两种方式获取,该类的实现已经在Dubbo SPI机制分析过,此篇文章关注的重点是关于@Adaptive注解修饰在接口方法的实现原理,也就是关于Dubbo SPI动态的加载扩展类能力如何实现,搞清楚Dubbo是如何在运行时动态的选择对应的扩展类来提供服务。简单一点说就是一个代理层,通过对应的参数返回对应的类的实现,运行时编译。为了更好的理解我们来写个案例:

@SPI("china")
public interface PersonService {
    @Adaptive
    String queryCountry(URL url);
}

public class ChinaPersonServiceImpl implements PersonService {
    @Override
    public String queryCountry(URL url) {
        System.out.println("中国人");
        return "中国人";
    }
}

public class EnglandPersonServiceImpl implements PersonService{
    @Override
    public String queryCountry(URL url) {
        System.out.println("英国人");
        return "英国人";
    }
}

public class Test {
    public static void main(String[] args) {

        URL url = URL.valueOf("dubbo://192.168.0.101:20880?person.service=china");
        PersonService service = ExtensionLoader.getExtensionLoader(PersonService.class)
                .getAdaptiveExtension();
        service.queryCountry(url);

    }
}


china=org.dubbo.spi.example.ChinaPersonServiceImpl
england=org.dubbo.spi.example.EnglandPersonServiceImpl

该案例中首先构造了一个URL对象,这个URL对象是Dubbo中进行参数传递所使用的一个基础类,在配置文件中配置的属性都会被封装到该对象中。这里我们需要注意的是我们的对象是通过一个url构造的,并且在url的最后有一个参数person.service=china,这里也就是我们所指定的使用哪种基础服务类的参数,通过指向不同的对象就可以生成对应不同的实现。关于URL部分的介绍我们在下一篇文章介绍,聊聊Dubbo中URL的使用场景有哪些。 在构造一个URL对象之后,通过getExtensionLoader(PersonService.class)方法获取了一个PersonService对应的ExtensionLoader对象,然后调用其getAdaptiveExtension()方法获取PersonService接口构造的子类实例,这里的子类实际上就是ExtensionLoader通过一定的规则为PersonService接口编写的子类代码,然后通过javassist或jdk编译加载这段代码,加载完成之后通过反射构造其实例,最后将其实例返回。当发生调用的时候,方法内部就会通过url对象指定的参数来选择具体的实例,从而将真正的工作交给该实例进行。通过这种方式,Dubbo SPI就实现了根据传入参数动态的选用具体的实例来提供服务的功能。以下代码就是动态生成以后的代码:

public class PersonService$Adaptive implements org.dubbo.spi.example.PersonService {
    
    public java.lang.String queryCountry(org.apache.dubbo.common.URL arg0) {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg0;
        String extName = url.getParameter("person.service", "china");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.dubbo.spi.example.PersonService) name from url (" + url.toString() + ") use keys([person.service])");
        org.dubbo.spi.example.PersonService extension = (org.dubbo.spi.example.PersonService) ExtensionLoader.getExtensionLoader(org.dubbo.spi.example.PersonService.class).getExtension(extName);
        return extension.queryCountry(arg0);
    }
}

关于使用我们需要注意以下两个问题:

  1. 要使用Dubbo的SPI的支持,必须在目标接口上使用@SPI注解进行标注,后面的值提供了一个默认值,此处可以理解为这是一种规范,如果在接口的@SPI注解中指定了默认值,那么在使用URL对象获取参数值时,如果没有取到,就会使用该默认值;
  2. @Adaptive注解标注的方法中,其参数中必须有一个参数类型为URL,或者其某个参数提供了某个方法,该方法可以返回一个URL对象,此处我们可以再看源码的时候给大家标注一下,面试的时候防止大佬问:是不是一定要 @Adaptive 实现的方法的中必须有URL对象;

getAdaptiveExtension

关于getAdaptiveExtension方法我们在上篇文章已经讲过,此方法就是通过双检查法来从缓存中获取Adaptive实例,如果没获取到,则创建一个。

    public T getAdaptiveExtension() {
        //从装载适配器实例缓存里面找
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            //创建cachedAdaptiveInstance异常
            if (createAdaptiveInstanceError != null) {
                throw new IllegalStateException("Failed to create adaptive instance: " +
                        createAdaptiveInstanceError.toString(),
                        createAdaptiveInstanceError);
            }

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

        return (T) instance;
    }
    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);
        }
    }

getAdaptiveExtensionClass

在getAdaptiveExtensionClass方法中有两个分支,如果某个子类标注了@Adaptive注解,那么就会使用该子类所自定义的Adaptive机制,如果没有子类标注该注解,那么就会使用下面的createAdaptiveExtensionClass()方式来创建一个目标类class对象。整个过程通过AdaptiveClassCodeGenerator来为目标类生成子类代码,并以字符串的形式返回,最后通过javassist或jdk的方式进行编译然后返回class对象。

    private Class<?> getAdaptiveExtensionClass() {
        //获取所有的扩展类
        getExtensionClasses();
        //如果可以适配
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        //如果没有适配扩展类就创建
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
    private Class<?> createAdaptiveExtensionClass() {
        //生成代码片段
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        //获取ClassLoader
        ClassLoader classLoader = findClassLoader();
        //通过jdk或者javassist的方式编译生成的子类字符串,从而得到一个class对象
        org.apache.dubbo.common.compiler.Compiler compiler =
                ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        //编译
        return compiler.compile(code, classLoader);
    }

generate

generate方法是生成目标类的方法,其实和创建一个类一样,其主要四个步骤:

  1. 生成package信息;
  2. 生成import信息;
  3. 生成类声明信息;
  4. 生成各个方法的实现;
    public String generate() {
        // 判断目标接口是否有方法标注了@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信息 只导入了ExtensionLoader类,其余的类都通过全限定名的方式来使用
        code.append(generateImports());
        //生成类声明信息
        code.append(generateClassDeclaration());

        Method[] methods = type.getMethods();
        //为各个方法生成实现方法信息
        for (Method method : methods) {
            code.append(generateMethod(method));
        }
        code.append("}");

        if (logger.isDebugEnabled()) {
            logger.debug(code.toString());
        }
        //返回class代码
        return code.toString();
    }

接下来主要看方法实现的生成,对于包路径、类的生成的代码相对比较简单,这里进行忽略,对于方法生成主要包含以下几个步骤:

  1. 获取返回值信息;
  2. 获取方法名信息;
  3. 获取方法体内容;
  4. 获取方法参数;
  5. 获取异常信息;
    private String generateMethod(Method method) {
        //获取方法返回值
        String methodReturnType = method.getReturnType().getCanonicalName();
        //获取方法名称
        String methodName = method.getName();
        //获取方法体内容
        String methodContent = generateMethodContent(method);
        //获取方法参数
        String methodArgs = generateMethodArguments(method);
        //生成异常信息
        String methodThrows = generateMethodThrows(method);
        //格式化
        return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
    }

需要注意的是,这里所使用的所有类都是使用的其全限定类名,在上面生成的代码中也可以看到,在方法生成的整个过程中,方法的返回值,方法名,方法参数以及异常信息都可以通过反射的信息获取到,而方法体则需要根据一定规则来生成,这里我们要看一下方法体是如何生成的;

    private String generateMethodContent(Method method) {
        //获取Adaptive的注解信息
        Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
        StringBuilder code = new StringBuilder(512);
        if (adaptiveAnnotation == null) {
            //如果当前方法没有被Adaptive修饰则需要抛出异常
            return generateUnsupported(method);
        } else {
            //获取参数中类型为URL的参数所在的参数索引位 通过下标获取对应的参数值信息
            int urlTypeIndex = getUrlTypeIndex(method);

            if (urlTypeIndex != -1) {
                //如果参数中存在URL类型的参数,那么就为该参数进行空值检查,如果为空,则抛出异常
                code.append(generateUrlNullCheck(urlTypeIndex));
            } else {
                //如果参数中不存在URL类型的参数,则会检查每个参数,判断是否有某个方法的返回类型是URL类型,
                //如果存在该方法,首先对该参数进行空指针检查,如果为空则抛出异常。如果不为空则调用该对象的目标方法,
                //获取URL对象,然后对获取到的URL对象进行空值检查,为空抛出异常。
                code.append(generateUrlAssignmentIndirectly(method));
            }
            //获取@Adaptive注解的参数,如果没有配置,就会使用目标接口的类型由驼峰形式转换为点分形式
            //的名称作为将要获取的参数值的key名称
            String[] value = getMethodAdaptiveValue(adaptiveAnnotation);

            //判断是否存在Invocation类型的参数 关于这个对象我们在后续章节在进行讲解
            boolean hasInvocation = hasInvocationArgument(method);

            //为Invocation类型的参数添加空值检查的逻辑
            code.append(generateInvocationArgumentNullCheck(method));

            //生成获取extName的逻辑,获取用户配置的扩展的名称
            code.append(generateExtNameAssignment(value, hasInvocation));
            //extName空值检查代码
            code.append(generateExtNameNullCheck(value));
            
            //通过extName在ExtensionLoader中获取其对应的基础服务类
            code.append(generateExtensionAssignment());

            //生成实例的当前方法的调用逻辑,然后将结果返回
            code.append(generateReturnAndInvocation(method));
        }

        return code.toString();
    }

上面整体的逻辑还是比较清楚的,通过对比PersonService$Adaptive生成我们可以更容易理解改代码生成的过程,整体的逻辑可以分为四步:

  1. 判断当前方法是否标注了@Adaptive注解,如果没有标注,则为其生成默认抛出异常的方法,只有使用@Adaptive注解标注的方法才是作为自适应机制的方法;
  2. 获取方法参数中类型为URL的参数,如果不存在,则获取参数中存在URL类型的参数,如果不存在抛出异常,如果存在获取URL参数类型;
  3. 通过@Adaptive注解的配置获取目标参数的key值,然后通过URL参数获取该key对应的参数值,得到了基础服务类对应的名称;
  4. 通过ExtensionLoader获取该名称对应的基础服务类实例,最终调用该服务的方法来进行实现;

欢迎大家点点关注,点点赞!

1005447-20190620221533393-1847070636.png

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK