第一章 Dubbo风格的SPI-ExtensionLoader

来源:互联网 发布:知豆汽车能开几年 编辑:程序博客网 时间:2024/04/30 01:21

java原生SPI

1、原生SPI的使用方式

1、路径 META-INF/services

2、文件名 interface 完整限定名

3、文件内容 implement 完整限定名

4、使用方式,通过迭代器迭代实现类型。

2、原生API的优缺点。

优点1、不需要引入第三方包,util包中既有api使用,实现方便,功能简单。
缺点2、不能指定想要的实现类,需要迭代获取;迭代的时候会加载用不到的实现类型,对资源造成浪费。

2 Motan ExtensionLoader实现服务发现。

Motan SPI的使用方式

1、路径 META-INF/services/

2、文件名@Spi注解的接口的完整限定名。

3、文件内容 每行一个实现类型。可以使用 #注释(注释内容在#右侧)

4、使用方式,通过name获取,或者通过关键字过滤获取拓展点集合

3 Dubbo ExtensionLoader实现服务发现。

Dubbo SPI的使用方式

1、路径 META-INF/services/;META-INF/dubbo/;META-INF/dubbo/internal/。

2、文件名@SPI注解的接口的完整限定名。

3、文件内容,name=implemts(完整限定名)。

4、使用方式,通过名称获取实现,或者通过URL获取可激活实现类型

4 Dubbo服务发现的特性。

1默认实现

通过SPI注解的value值来设置该接口所拥有的默认实现

public @interface SPI {    /**     * 缺省扩展点名。     */    String value() default "";}

2扩展点实例属性自动注入

在创建拓展点实例后会自动注入实例中所包含的属性通过ExtensionFactory注入拓展点实现。

private T injectExtension(T instance) {        try {                for (Method method : instance.getClass().getMethods()) {                    /**                    *如果是set方法,设置值到实例中                    **/                           Object object = objectFactory.getExtension(pt, property);                            if (object != null) {                                method.invoke(instance, object);                            }                        }         return instance;    }

3 Adaptive拓展点,通过Adaptive注解标识一个类型为适配类型,或者通过Compiler的Adaptive实现(通过application标签的Compiler属性可以设置为jdk或者javasist)动态生成Adaptive类型

1、具体生成规则。

①、针对具有Adaptive注解的方法生成方法,如果没有Adaptive注解,该方法跑出运行时异常

②、根据URL属性,Adaptive注解方法中必须有URL参数,或者带URL属性的参数。或者Invocation属性获取拓展点名称(获取属性)

③、通过ExtensionLoader根据名称获取拓展点实例,然后调用方法。

④、将拼成的code生成CLass并返回。

private String createAdaptiveExtensionClassCode() {        /**        *省略部分        **/        //遍历接口中的方法,检查是否存在Adaptive注解的方法        for(Method m : methods) {            if(m.isAnnotationPresent(Adaptive.class)) {                hasAdaptiveAnnotation = true;                break;            }        }        // 完全没有Adaptive方法,则不需要生成Adaptive类        if(! hasAdaptiveAnnotation)            throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");        /**        *准备生成类型,生成,包名,引用,类型名(interfaceName+$+Adaptive),实现接口        **/       /**       *遍历方法,根据Adaptive注解决定生成方式等。        **/      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);            //没有注解,那这个方法有问题,不清真,没法用Adaptive方式调用            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;                for (int i = 0; i < pts.length; ++i) {                    if (pts[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类型,那从参数的属性中找,找不到就别玩了,抛异常,这个类型不清真,不能生成Adaptive类型                else {                    String attribMethod = null;                    // 找到参数的URL属性                    LBL_PTS:                    for (int i = 0; i < pts.length; ++i) {                        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 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, 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);                    s = String.format("%s url = arg%d.%s();",URL.class.getName(), urlTypeIndex, attribMethod);                     code.append(s);                }                /**                 * 获取注解的值                 */                String[] value = adaptiveAnnotation.value();                // 没有设置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()};                }                //如果有Invocation参数,那么从方法参数中去获取扩展点名称                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;                    }                }                //从方法参数中获取扩展点名称,或者从URL参数中获取拓展点名称,如果是协议拓展,那么从url中获取协议即可                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(");");            }            //实现接口中定义的方法,将上面拼好的方法放进去。            codeBuidler.append("\npublic " + rt.getCanonicalName() + " " + method.getName() + "(");            for (int i = 0; i < pts.length; i ++) {                if (i > 0) {                    codeBuidler.append(", ");                }                codeBuidler.append(pts[i].getCanonicalName());                codeBuidler.append(" ");                codeBuidler.append("arg" + i);            }            codeBuidler.append(")");            if (ets.length > 0) {                codeBuidler.append(" throws ");                for (int i = 0; i < ets.length; i ++) {                    if (i > 0) {                        codeBuidler.append(", ");                    }                    codeBuidler.append(ets[i].getCanonicalName());                }            }            codeBuidler.append(" {");            codeBuidler.append(code.toString());            codeBuidler.append("\n}");        }        codeBuidler.append("\n}");        if (logger.isDebugEnabled()) {            logger.debug(codeBuidler.toString());        }        return codeBuidler.toString();    }

4 Activate拓展点,通过Activate注解标识一个类型为可激活类型,

public List getActivateExtension(URL url, String[] values, String group) {
List exts = new ArrayList();
List names = values == null ? new ArrayList(0) : Arrays.asList(values);
if (! names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
getExtensionClasses();
for (Map.Entry

wrapper类型增强扩展点类型

1.wapper类型判断条件

一、在SPI接口的拓展点中,如果存在以接口类型作为唯一参数的拓展点类型,那就是一个拓展点类型

//如果有以接口类型作为入参的构造,那这就是个wrapper                                                try {                                                    clazz.getConstructor(type);                                                    Set<Class<?>> wrappers = cachedWrapperClasses;                                                    if (wrappers == null) {                                                        cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();                                                        wrappers = cachedWrapperClasses;                                                    }                                                    wrappers.add(clazz);                                                }

二、warpper类型被载入的时机

在创建Extension实例的时候,将wrapper类型包装在实例之外。以达到功能增强目的。

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

5 三种SPI的比较

name 获取方式 默认实现 有无wrapper增强 根据条件激活拓展点 拓展点名字配置 适配类型拓展点 单例与原型支持 拓展点属性注入支持 java 迭代器 无 无 无 无 无 单例 不支持 motan 1、根据名字获取拓展点;2、根据key获取关键字拓展,结果被排序 无 无 通过Activation注解支持通过key过滤拓展点,并且可以设置排序输出 通过spiMeta注解配置,默认为实现类型名称 无 支持原型和单例,通过Spi注解配置 不支持 dubbo 1、根据名字获取单例。2、通过getActivateExtension方法获取对应Active注解过滤后的拓展列表 有,通过getDefault获取 在扩展点实例生成时应用wrapper 通过Activate与URL参数来达到条件激活(group与key) 在类路径文件中的key中指明,默认为类上Extension注解value值,或扩展点名字去掉接口名 通过Adaptive注解申明适配类型拓展点,如果没有,则通过拼接字节码生成适配类型拓展点 只支持单例 对于拓展点类型实例,默认从ExtensionFactory中获取该类型的拓展类型
2 0
原创粉丝点击