深度解析dubbo扩展技术dubbo spi(自适应实现)

注:本文基于dubbo v2.6.1

1. @Adaptive 注解在方法上

咱们在dubbo框架中经常会看到类似下面获取自适应实现类的代码

ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()

接着我们就跟着getAdaptiveExtension()方法看下dubbo怎样实现自适应的。

@SuppressWarnings("unchecked")
    public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        // 先从缓存获取
        if (instance == null) {
            if (createAdaptiveInstanceError == null) { // 判断之前有没有报错
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {

                            //创建adaptive
                            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;
    }

首先先从cachedAdaptiveInstance 成员中获取一下缓存着的自适应实现类对象,如果没有的话再判断createAdaptiveInstanceError是否是null,如果createAdaptiveInstanceError不是null,说明,之前再获取自适应实现类对象的时候报过错,然后被缓存下来了,接着就是执行createAdaptiveExtension方法来创建自适应实现类对象,如果抛出异常就会被记录下来,接着再看下createAdaptiveExtension方法

 private T createAdaptiveExtension() {
        try {

            /**
             * 首先 获取class
             * 然后创建对象
             */
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

首先这里getAdaptiveExtensionClass()来获取自适应扩展实现对象的class,然后创建对象,再然后就是将对象传入injectExtension方法实现自动注入。
接着看下getAdaptiveExtensionClass方法看看是怎样获取class对象的。

//获取adaptiveExtension class
    private Class<?> getAdaptiveExtensionClass() {
        //获取extension classs
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

首先是获取所有的扩展实现类class,之前咱们分析过,会将实现类上带有@Adaptive注解的缓存到cachedAdaptiveClass 这个成员中。
那如果没有实现类上带有@Adaptive注解的时候,就要通过createAdaptiveExtensionClass来创建自适应扩展实现类了。
接着我们看下createAdaptiveExtensionClass是怎样创建自适应扩展实现类的

 private Class<?> createAdaptiveExtensionClass() {

        // 拼装代理类的java代码
        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);
    }

首先是通过createAdaptiveExtensionClassCode方法来拼装成自适应实现类的代码,接着就是使用spi获取编译器,然后进行编译,加载,返回自适应实现类的class对象。
接下来看下createAdaptiveExtensionClassCode方法是怎样拼装代码的。

 private String createAdaptiveExtensionClassCode() {
        StringBuilder codeBuilder = new StringBuilder();
        Method[] methods = type.getMethods();

        // 查找方法上面有没有@Adaptive  注解
        boolean hasAdaptiveAnnotation = false;
        for (Method m : methods) {
            if (m.isAnnotationPresent(Adaptive.class)) {
                hasAdaptiveAnnotation = true;
                break;
            }
        }
        // no need to generate adaptive class since there's no adaptive method found.   没有一个自适用的方法没有必要 生成自适应类了
        if (!hasAdaptiveAnnotation)
            throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");

        codeBuilder.append("package ").append(type.getPackage().getName()).append(";");
        codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";");
        codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Adaptive").append(" implements ").append(type.getCanonicalName()).append(" {");

        for (Method method : methods) {

            //返回值
            Class<?> rt = method.getReturnType();
            //参数类型

            Class<?>[] pts = method.getParameterTypes();
            //异常
            Class<?>[] ets = 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 {
                int urlTypeIndex = -1;

                // 查找参数列表中有没有 URL
                for (int i = 0; i < pts.length; ++i) {
                    if (pts[i].equals(URL.class)) {
                        urlTypeIndex = i;
                        break;
                    }
                }
                // found parameter in URL type
                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);
                }
                // did not find parameter in URL type
                else {   // 没有url参数的
                    String attribMethod = null;

                    // find URL getter method
                    LBL_PTS:
                    for (int i = 0; i < pts.length; ++i) {

                        // 查找参数里面 有get方法并且返回值是URL的
                        Method[] ms = pts[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 adaptive class for interface " + type.getName()
                                + ": not found url parameter or url attribute in parameters of method " + method.getName());
                    }

                    // Null point check  验证有get方法返回值是URL的参数 是否为null  然后get到的URL 是否为null
                    String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
                            urlTypeIndex, pts[urlTypeIndex].getName());
                    code.append(s);
                    s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
                            urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
                    code.append(s);
                    // URL url = argX.getXXXX();
                    s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, attribMethod);
                    code.append(s);
                }

                String[] value = adaptiveAnnotation.value();
                // value is not set, use the value generated from class name as the 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 < pts.length; ++i) {
                    if (pts[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);
                    }
                }
                code.append("\nString extName = ").append(getNameCode).append(";");
                // check extName == null?
                String s = String.format("\nif(extName == null) " +
                                "throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");",
                        type.getName(), Arrays.toString(value));
                code.append(s);
                // 获取扩展
                s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
                        type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
                code.append(s);

                // return statement
                if (!rt.equals(void.class)) {
                    code.append("\nreturn ");
                }
                //调用 源方法
                s = String.format("extension.%s(", method.getName());
                code.append(s);
                //拼装调用的参数
                for (int i = 0; i < pts.length; i++) {
                    if (i != 0)
                        code.append(", ");
                    code.append("arg").append(i);
                }
                code.append(");");
            }
            // 拼装代理类
            codeBuilder.append("\npublic ").append(rt.getCanonicalName()).append(" ").append(method.getName()).append("(");
            for (int i = 0; i < pts.length; i++) {
                if (i > 0) {
                    codeBuilder.append(", ");
                }
                codeBuilder.append(pts[i].getCanonicalName());
                codeBuilder.append(" ");
                codeBuilder.append("arg").append(i);
            }
            codeBuilder.append(")");



            if (ets.length > 0) {  // 异常处理
                codeBuilder.append(" throws ");
                for (int i = 0; i < ets.length; i++) {
                    if (i > 0) {
                        codeBuilder.append(", ");
                    }
                    codeBuilder.append(ets[i].getCanonicalName());
                }
            }
            codeBuilder.append(" {");
            codeBuilder.append(code.toString());
            codeBuilder.append("\n}");
        }
        codeBuilder.append("\n}");
        if (logger.isDebugEnabled()) {
            logger.debug(codeBuilder.toString());
        }
        return codeBuilder.toString();
    }

首先,获取接口的所有方法,判断方法上面是否有@Adaptive注解,只要有一个方法有注解就继续执行,没有的话就要抛出异常了。
接着就是拼装package,import,然后自适应扩展类名就是 接口名+$Adaptive 然后实现一下接口,接着就是遍历method, 判断method的上面是否有@Adaptive注解,如果没有将抛出异常的代码追加到code上,如果有@Adaptive注解,找出URL参数在参数列表的位置,如果有URL参数就拼装成判断是否是null,如果是null就抛出异常,然后设置到本地变量,类似下面这样

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

如果没有URL,就遍历参数类型里面有get方法并且get方法不是静态没有参数返回值是URL类型的,没有找到这么个条件的参数的时候,就会抛出异常 ,如果找到判断这个参数不是null,参数获取的URL不是null,然后URL设置到本地变量url中。
接着获取@Adaptive 注解值,如果没有值,就那class 名字来
如果参数有Invocation类型的,然后生成判断null的代码与String methodName = arg0.getMethodName(),并设置标志hasInvocation为true
接着就是遍历@Adaptive 上面value数组,这块主要是实现通过url来获取配置参数的值,这个值就是对应扩展实现类的name,判断extName如果是null的话就要抛出异常,接着就是通过getExtensionLoader(当前扩展接口).getExtension(extName) 来获取扩展实现类对象,然后再通过这个实现类对象调用自己对应的这个方法。可以看下Transporter这个扩展接口的自适应实现类

public class Transporter$Adaptive implements com.alibaba.dubbo.remoting.Transporter {
    public com.alibaba.dubbo.remoting.Client connect(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
        if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        return extension.connect(arg0, arg1);
    }
    public com.alibaba.dubbo.remoting.Server bind(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("server", url.getParameter("transporter", "netty"));
        if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        return extension.bind(arg0, arg1);
    }
}

说白了,这个自适应就是根据接口自己实现一个实现类,然后这个实现类是能够根据@Adaptive 配置的参数值然后去URL中获取对应的值,然后再根据这个值使用spi获取扩展实现类,最后调用这个实现类的对应方法。

2.@Adaptive 注解在类上

当@Adaptive在具体实现类上时,在加载classs的时候,就会被cachedAdaptiveInstance这个成员缓存起来,我们可以在回顾下加载class的时候是怎么处理@Adaptive 注解在类上的情况的。

//缓存自适应拓展对象的类到 `cachedAdaptiveClass`
        if (clazz.isAnnotationPresent(Adaptive.class)) {//   Adaptive 注解是否在该类上面
            if (cachedAdaptiveClass == null) {
                cachedAdaptiveClass = clazz;
            } else if (!cachedAdaptiveClass.equals(clazz)) {// Adaptive 注解在类上面的时候,只允许一个实现类上面有@Adaptive注解
                throw new IllegalStateException("More than 1 adaptive class found: "
                        + cachedAdaptiveClass.getClass().getName()
                        + ", " + clazz.getClass().getName());
            }
        } 

首先判断你这个实现类上有没有@Adaptive注解,如果有的话再判断cachedAdaptiveClass是不是null,如果是null,直接将咱们找出来的这个class 缓存到cachedAdaptiveClass成员中,如果不是null,说明被加载过了,然后判断咱们加载出来的这个class是不是缓存的那个,如果不是的话,就说明同一个扩展点接口有两个带有@Adaptive的扩展实现类,这个dubbo是不允许的,所以直接抛出异常。也就是说,当@Adaptive在扩展实现类时,只允许有一个。

猜你喜欢

转载自blog.csdn.net/yuanshangshenghuo/article/details/106416935