美文网首页Java 杂谈
Dubbo源码剖析之SPI机制<二>

Dubbo源码剖析之SPI机制<二>

作者: jerrik | 来源:发表于2018-07-13 14:59 被阅读1次

    一、开始

    接着Dubbo源码剖析之SPI机制<一>[https://www.jianshu.com/p/35eb72ec564b]开始讲。我们知道当调用getAdaptiveExtensionClass方法时,先去META-INF目录下去查找,如果找不到就会调用createAdaptiveExtensionClass创建一个,所以这里重点剖析下该方法,看Dubbo是怎么实现的。

    private String createAdaptiveExtensionClassCode() {
            StringBuilder codeBuidler = new StringBuilder();
    
            //获取type的所有方法,如果方法上加了@Adaptive,则跳出循环
            Method[] methods = type.getMethods();
            boolean hasAdaptiveAnnotation = false;
            for(Method m : methods) {
                if(m.isAnnotationPresent(Adaptive.class)) {
                    hasAdaptiveAnnotation = true;
                    break;
                }
            }
    
            // 针对方法上没有@Adaptive的情况,直接抛出异常
            if(! hasAdaptiveAnnotation)
                throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");
    
            //开始拼装.java
            codeBuidler.append("package " + type.getPackage().getName() + ";");
            codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";");
            codeBuidler.append("\npublic class " + type.getSimpleName() + "$Adpative" + " implements " + type.getCanonicalName() + " {");
            
            for (Method method : methods) {
                Class<?> returnType = method.getReturnType();
                Class<?>[] parameterTypes = method.getParameterTypes();
                Class<?>[] exceptionTypes = method.getExceptionTypes();
    
                //获取方法上的注解
                Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
                StringBuilder code = new StringBuilder(512);
                if (adaptiveAnnotation == null) {
                    code.append("throw new UnsupportedOperationException(\"method ")
                            .append(method.toString()).append(" of interface ")
                            .append(type.getName()).append(" is not adaptive method!\");");
                } else {
                    //针对方法上存在@Adaptive,且方法参数中存在URL,则返回脚标
                    int urlTypeIndex = -1;
                    for (int i = 0; i < parameterTypes.length; ++i) {
                        if (parameterTypes[i].equals(URL.class)) {
                            urlTypeIndex = i;
                            break;
                        }
                    }
                    // 有类型为URL的参数
                    if (urlTypeIndex != -1) {
                        // Null Point check
                        String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");",
                                        urlTypeIndex);
                        code.append(s);
                        
                        s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex); 
                        code.append(s);
                    }
                    // 如果参数没有URL类型,就通过反射获取参数的所有的方法。如果返回参数有URL,则保持该方法名和脚标
                    else {
                        String attribMethod = null;
                        
                        LBL_PTS:
                        for (int i = 0; i < parameterTypes.length; ++i) {
                            Method[] ms = parameterTypes[i].getMethods();
                            for (Method m : ms) {
                                String name = m.getName();
                                if ((name.startsWith("get") || name.length() > 3)
                                        && Modifier.isPublic(m.getModifiers())
                                        && !Modifier.isStatic(m.getModifiers())
                                        && m.getParameterTypes().length == 0
                                        && m.getReturnType() == URL.class) {
                                    urlTypeIndex = i;
                                    attribMethod = name;
                                    break LBL_PTS;
                                }
                            }
                        }
                        if(attribMethod == null) {
                            throw new IllegalStateException("fail to create adative class for interface " + type.getName()
                                    + ": not found url parameter or url attribute in parameters of method " + method.getName());
                        }
                        
                        // Null point check
                        String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
                                        urlTypeIndex, parameterTypes[urlTypeIndex].getName());
                        code.append(s);
                        s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
                                        urlTypeIndex, attribMethod, parameterTypes[urlTypeIndex].getName(), attribMethod);
                        code.append(s);
    
                        s = String.format("%s url = arg%d.%s();",URL.class.getName(), urlTypeIndex, attribMethod); 
                        code.append(s);
                    }
    
                    //开始解析方法上@Adaptive中的value
                    String[] value = adaptiveAnnotation.value();
                    // 没有设置Key,则使用“扩展点接口名的点分隔 作为Key
                    if(value.length == 0) {
                        char[] charArray = type.getSimpleName().toCharArray();
                        StringBuilder sb = new StringBuilder(128);
                        for (int i = 0; i < charArray.length; i++) {
                            //除去首字母,如果是大写字母,则拼接一个.做间隔符号
                            if(Character.isUpperCase(charArray[i])) {
                                if(i != 0) {
                                    sb.append(".");
                                }
                                sb.append(Character.toLowerCase(charArray[i]));
                            }
                            else {
                                sb.append(charArray[i]);
                            }
                        }
                        value = new String[] {sb.toString()};
                    }
                    
                    boolean hasInvocation = false;
                    for (int i = 0; i < parameterTypes.length; ++i) {
                        if (parameterTypes[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
                            // Null Point check
                            String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
                            code.append(s);
                            s = String.format("\nString methodName = arg%d.getMethodName();", i); 
                            code.append(s);
                            hasInvocation = true;
                            break;
                        }
                    }
                    
                    String defaultExtName = cachedDefaultName;
                    String getNameCode = null;
                    for (int i = value.length - 1; i >= 0; --i) {
                        if(i == value.length - 1) {
                            if(null != defaultExtName) {
                                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], defaultExtName);
                                else
                                    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()";
                            }
                        }
                        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);
                        }
                    }
                    ...省略部分代码
            }
            codeBuidler.append("\n}");
            if (logger.isDebugEnabled()) {
                logger.debug(codeBuidler.toString());
            }
            return codeBuidler.toString();
        }
    
            //获取type的所有方法,如果方法上加了@Adaptive,则跳出循环
            Method[] methods = type.getMethods();
            boolean hasAdaptiveAnnotation = false;
            for(Method m : methods) {
                if(m.isAnnotationPresent(Adaptive.class)) {
                    hasAdaptiveAnnotation = true;
                    break;
                }
            }
    
            // 针对方法上没有@Adaptive的情况,直接抛出异常
            if(! hasAdaptiveAnnotation)
                throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");
    

    这段代码做的事情就是获取SPI接口中的所有方法,如果没有方法存在@Adaptive注解,则直接抛出异常,不能为该SPI接口生成适配类。
    后面紧接着又开始循环所有方法,之前的那次循环只是做了是否有@Adaptive注解的判断,本次循环才是真正的处理逻辑。首先获取到方法的返回值、参数数组、异常类型,如果方法上不存在@Adaptive注解,则给方法体拼装了一个 UnsupportedOperationException异常,表示该方法不能调用。如果方法上加了@Adaptive注解,则迭代方法的入参列表,看是否存在有URL类型的参数,如果存在URL这个类型的参数,则记录脚标,然后拼装java代码:

    if (arg1 == null) throw new IllegalArgumentException("url == null");
    com.alibaba.dubbo.common.URL url = arg1;
    

    上面是存在URL类型参数的情况.如果不存在URL类型的参数则通过反射获取参数类型所有的方法,例如:Invoker.class.getMethods().如果存在以get方法开头,且是非静态、public,又没有入参、且返回值为URL的方法,则记录脚标和方法名,类似下面这种方法:

        public URL getUrl(){
    
        }
    

    如果找不到这种方法,则直接抛出异常fail to create adative class for interface,创建适配类失败.
    继续往下跟,首先通过String[] value = adaptiveAnnotation.value();获取@Adaptive注解上的value值,如果value为空,则使用扩展点接口作为value.假设这里的SPI接口为Protocol,那么value就是protocol,最后会通过该值是否等于protocol来封装方法.

       if(!"protocol".equals(value[i])){
              //如果参数有Invocation
               if (hasInvocation) {
                    getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
              }else{
                    getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i],       defaultExtName);
              }
       }else{
              getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
       }      
                         
    

    接下来看:

        String defaultExtName = cachedDefaultName;
        String getNameCode = null;
        for (int i = value.length - 1; i >= 0; --i) {
            if(i == value.length - 1) {
                if(null != defaultExtName) {
                    if(!"protocol".equals(value[i]))
                        //是否有Invocation参数
                        if (hasInvocation) 
                            getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                        else
                            getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
                    else
                        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()";
                }
            }
            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);
            }
        }
        ...省略部分代码                   
    

    如果默认的扩展名cachedDefaultName!=null,然后判断@adaptive中的value是否等于protocol,如果不相等,则判断参数中是否存在Invocation类型的参数,如果存在就拼接url.getMethodParameter()方法,否则就拼接url.getParameter().如果等于protocol,则直接拼接url.getProtocol().其它方法类似,我就不再赘述了。最后还生成了getExtension的方法。

    T extension = (T) ExtensionLoader.getExtensionLoader(T).getExtension(extName);
    
     s = String.format("extension.%s(", method.getName());
     code.append(s);
     for (int i = 0; i < parameterTypes.length; i++) {
           if (i != 0)
                code.append(", ");
                code.append("arg").append(i);
       }
      code.append(");");
    

    上面方法主要是通过getExtension获取到的实例来调用和SPI接口相同的目标方法。

     public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
            ...
            com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
            return extension.refer(arg0, arg1);
     }
    

    再回到最上层的createAdaptiveExtensionClass方法:

     private Class<?> createAdaptiveExtensionClass() {
            //自己拼装的java类
            String code = createAdaptiveExtensionClassCode();
            ClassLoader classLoader = findClassLoader();
            //编译.java文件
            com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
            return compiler.compile(code, classLoader);
        }
    

    利用Dubbo的Comiper来编译刚刚拼接出来的java文件,生成Class.其底层还是利用javassist来实现的。

    总结

    Adaptive机制的大体流程是,在初始化的时候一行一行的读取配置,然后通过反射生成Class,然后判断该Class是否加了@Adaptive注解,然后缓存起来。我们在获取适配类的时候,判断缓存中是否有适配类,如果存在就直接返回,否则通过javassit编译出一个适配类。但是创建适配类也是有条件的,必须满足几个条件:

    1. SPI接口必须有加了@Adaptive注解的方法
    2. 方法中要有URL类型的参数
    3. 参数中必须要有getXXX()方法,且返回值为URL

    如果这三个条件都不满足,就会抛出异常。

    相关文章

      网友评论

        本文标题:Dubbo源码剖析之SPI机制<二>

        本文链接:https://www.haomeiwen.com/subject/ecdjpftx.html