注:本文dubbo源码版本v2.6.x

1.介绍

由于jdk自身提供的spi技术不满足dubbo框架的需要,dubbo在此基础上做了一定的改进和优化,同时又兼容jdk spi。正是基于dubbo spi加载机制,让整个框架的接口和具体的实现完全解耦,dubbo几乎所有的组件都是基于扩展机制来实现的,是整个框架良好扩展性的基础。它不仅屏蔽了jdk spi的一些短板,而且还增加了对扩展IOC与AOP的功能。在dubbo-common模块的com.alibaba.dubbo.common.extension包中就是我们要找dubbo spi了。

dubbo注解配置_dubbo注解配置

2.扩展点@SPI注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {

    /**
     * default extension name
     * 默认实现
     */
    String value() default "";

}

该注解可以使用在类,接口,枚举上,在dubbo框架中都使用在接口上,它的作用就是标记该接口是一个dubbo spi接口,是一个扩展点,可以有多个接口的实现。该注解有一个value属性,表示该接口的默认实现。

3.自适用注解@Adaptive

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    String[] value() default {};
}

该注解可以使用在类,接口,枚举,方法上面。如果标注在接口的方法上时,可以根据参数动态获取实现类,在第一次getExtension时,会自动生成和编译一个动态的Adaptive类,达到动态实现类的效果。如果标注在实现类上的时候主要是为了直接固定对应的实现而不需要动态生成代码实现。
该注解有一个参数value,是个string数组类型,表示可以通过多个元素依次查找实现类。
举个例子:

@SPI("netty") // 默认是netty3的
public interface Transporter {
    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
    Server bind(URL url, ChannelHandler handler) throws RemotingException;
    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;
}

在@Adaptive传入了两个参数,分别是server与transporter,当外部调用Transporter.bind方法时,会动态从传入的参数URL中提取key参数是‘server’的value值,如果能匹配上某个扩展实现类则直接使用对应的实现类,如果匹配不上,就会通过第二个key参数‘transpoter’来提取value值,如果都没有匹配上,就会抛出异常,总而言之,就是会根据参数value查找实现类,知道最后抛出异常。

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

上面这段代码就是调用:

ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();

产生的自适应类。在这段代码中的bind方法的实现可以看出是从URL中拿server与transporter属性值,如果没有实在没有就是用默认的netty作为extName 的值,然后再执行ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName)来获取最终的实现类。URL中的参数值是项目配置的。

4.自动激活@Activate

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
    String[] group() default {};
    String[] value() default {};
    String[] before() default {};
    String[] after() default {};
    int order() default 0;
}

该注解可以标记在类,接口,枚举和方法上,主要使用在有多个扩展点实现,需要根据不通条件激活的场景中,比如说Filter需要多个同时激活
@Activate参数解释:
group表示URL中的分组如果匹配的话就激活,可以设置多个
value 查找URL中如果含有该key值,就会激活
before填写扩展点列表,表示哪些扩展点需要在本扩展点的前面
after 表示哪些扩展点需要在本扩展点的后面
order 排序信息

5.不自动注入@DisableInject

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface DisableInject {
}

该注解可以使用在类,接口,方法上,在createExtension的时候表示不自动注入。
在ExtensionLoader 类中,创建子类实现的时候会自动注入由ExtensionLoader管理的类,就会调用 injectExtension(instance)方法,然后在方法遍历的时候会检测有没有该DisableInject注解,如果有的话就会跳过,不会自动注入。

// 注入扩展
    private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        /**
                         * Check {@link DisableInject} to see if we need auto injection for this property
                         * 方法上面有@DisableInject注解, 表示不想自动注入
                         */
                        if (method.getAnnotation(DisableInject.class) != null) {
                            continue;
                        }
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            /**
                             * 判断 方法名的长度 >3
                             * 然后将第4个字符转成小写然后拼接后面的字符
                             * 比如说setName
                             * 获取到的property就是 name
                             */
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }