dubbo之SPI扩展机制注解:@Extension注解的作用

本文探讨了Dubbo的SPI扩展机制,特别是@Extension注解的作用。当不在配置文件中指定key时,Dubbo会检查实现类上的@Extension注解来确定key。在指定路径"META-INF/services"的文件中,Dubbo按行解析以'='分隔的key-value对。如果name为null,Dubbo会尝试从实现类的简单名称中获取key。三种可能的情况包括:1) 文件中指定name;2) 使用@Extension注解指定name;3) 实现类名称作为默认name。

我们说dubbo的spi机制和Java提供的spi机制,有一个很大的区别,dubbo的spi机制中,需要在文件中,指定key,我们在使用的时候可以根据key,只加载我们需要使用的实现类,那除了直接在文件中,通过
key=com.xxx.interfaceImpl这种方式之外,还可以通过@Extension这个注解来指定key,dubbo源码在解析的时候,会判断,如果没有在文件中指定key,会解析其实现类上是否有这个注解

文章中,指定路径指:META-INF/services
指定文件是指这个路径下的文件

com.alibaba.dubbo.common.extension.ExtensionLoader#loadClass

在spi机制被使用的时候,是会去指定路径下,解析指定的文件,然后从文件中解析出对应key和value,解析之后,会有一系列逻辑的判断,上面的这个方法,就是解析之后,对实现类进行的一系列判断,具体是如何执行到这一个方法的,在前面 dubbo源码之SPI机制源码中有介绍

其实大致就是说:如果在调用spi的时候,会一次去指定路径下解析指定的文件,解析到文件之后,会一行一行的解析,以 "="为分隔符,前面的是key,后面的是value,解析到之后,会根据value(全类名),生成对应的class文件,然后进入到这个方法中,对class进行解析

/**
 * 这个方法主要是将name和clazz赋值到extensionClasses中,只是在put之前,会有一系列的逻辑判断,会区分出来是哪种类型的类
 * @param extensionClasses
 * @param resourceURL
 * @param clazz
 * @param name
 * @throws NoSuchMethodException
 */
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    /**
     * 1.判断clazz是否是type的实现类
     * 如果type是Protocol,那么这里的clazz就是文件中配置的HttpProtocol实现类
     * 如果不是type的实现类,就抛出异常
     */
    if (!type.isAssignableFrom(clazz)) {
        throw new IllegalStateException("Error when load extension class(interface: " +
                type + ", class line: " + clazz.getName() + "), class "
                + clazz.getName() + "is not subtype of interface.");
    }
    /**
     * 2.判断当前实现类上是否有@Adaptive注解,需要注意的是:一个接口,只允许有一个adaptive实现类
     * 如果有多个,就抛出异常
     * cachedAdaptiveClass中保存的就是type对应的adaptive实现类,这里获取到的是我们自己定义的adaptive实现类
     */
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        if (cachedAdaptiveClass == null) {
            cachedAdaptiveClass = clazz;
        } else if (!cachedAdaptiveClass.equals(clazz)) {
            throw new IllegalStateException("More than 1 adaptive class found: "
                    + cachedAdaptiveClass.getClass().getName()
                    + ", " + clazz.getClass().getName());
        }
    } else if (isWrapperClass(clazz)) {
        /**
         * 在判断是否是包装类的时候,就看对应类中是否有目标接口的构造函数
         * 3.wrapper类的处理,如果当前clazz是type实现类的包装类,就暂时将包装类存入到一个集合中
         * CarWrapper就会进入到这里来处理
         */
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }
        wrappers.add(clazz);
    } else {
        /**
         * 4.1 进入到这里,表示既不是包装类,也没有添加@Adaptive注解
         * 必须要有无参构造函数,因为是通过class.newInstance()来初始化的
         */
        clazz.getConstructor();
        /**
         * 4.2 如果在META-INF下的文件中,没有配置name,就从实现类上去判断,是否有添加@Extension注解,如果有添加,就返回
         * @Extension 注解对应的value
         */
        if (name == null || name.length() == 0) {
            name = findAnnotationName(clazz);
            if (name.length() == 0) {
                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
            }
        }
        /**
         * 4.3 对name进行拆分
         * 并且判断是否有添加@Activate注解
         */
        String[] names = NAME_SEPARATOR.split(name);
        if (names != null && names.length > 0) {
            Activate activate = clazz.getAnnotation(Activate.class);
            if (activate != null) {
                cachedActivates.put(names[0], activate);
            }
            for (String n : names) {
                if (!cachedNames.containsKey(clazz)) {
                    cachedNames.put(clazz, n);
                }
                Class<?> c = extensionClasses.get(n);
                if (c == null) {
                    extensionClasses.put(n, clazz);
                } else if (c != clazz) {
                    throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                }
            }
        }
    }
}

我们要关心的是4.2这个注释

我们可以看到,有一个判断:
这里的判断也很简单,如果name为null,就表示我在文件中是这样来配置的:
com.xxxx.xxxx.InterfaceImpl
正常情况下,配置的时候,是需要指定key的,那如果是这种情况,dubbo会取实现类上解析是否有@Extension注解
也就是下面这个方法中

private String findAnnotationName(Class<?> clazz) {
    com.alibaba.dubbo.common.Extension extension = clazz.getAnnotation(com.alibaba.dubbo.common.Extension.class);
    /**
     * 1.如果没有添加注解,就取class的simpleName,然后判断是否是以接口名结尾的,如果是,就截取其前面部分作为name
     * RedCarInterface,如果实现类名是这个,就会截取red作为name
     */
    if (extension == null) {
        String name = clazz.getSimpleName();
        if (name.endsWith(type.getSimpleName())) {
            name = name.substring(0, name.length() - type.getSimpleName().length());
        }
        return name.toLowerCase();
    }
    /**
     * 2.如果配置了注解,就返回其value值,value值就是实现类对应的name
     */
    return extension.value();
}

这里可以看到,如果@Extension注解也没有加,dubbo还会进行一次挣扎,也就是直接解析实现类,取其simpleName,然后判断实现类是否是以接口结尾的,其实举个例子就是这样的
假如我自定义了一个protocol的实现类:MySelfProtocol,有三种情况
1.如果我在文件中指定:mySelf=com.xxx.MySelfProtocol,那就会使用文件中的name作为key(name)
2.如果我既没有在文件中指定name,但是添加了@Extension(value = “mySelf”),那就会使用这里的value作为name来赋值
3.如果我既没有在文件中指定,也没有在实现类上添加注解,那就会解析MySelfProtocol这个字符串,然后截取mySelf作为name,并将mySelf转换为小写

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值