Dubbo源码解析-框架扩展机制

1,174

1.加载机制

     Dubbo拥有良好的扩展性得益于运用恰到好处的各种设计模式,还有就是加载机制,基于Dubbo SPI加载机制,可以让整个框架的接口和具体实现完全解耦。

1.1 Java SPI和Dubbo SPI

**API (Application Programming Interface)**在大多数情况下,都是实现方制定接口并完成对接口的实现,调用方仅依赖接口调用,且无权选择不同实现。 从使用人员上来说,API 直接被应用开发人员使用。

**SPI (Service Provider Interface)**是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。 从使用人员上来说,SPI 被框架扩展人员使用。

下面是使用Java SPI的简单例子,其中需要在META-INF/Services/目录下,创建一个以接口全路径命名的文件,如com.seewo.dubbo.demo.spi.printService文件,文件内容为具体实现类的全路径名,如果有多个实现类则用换行符分隔。

META-INF/services/com.seewo.dubbo.demo.spi.printService文件内容为实现类的全路径名
com.seewo.dubbo.demo.spi.PrintServiceImpl
com.seewo.dubbo.demo.spi.PrintServiceNewImpl

public interface PrintService {
    void printInfo();
}

public class PrintServiceNewImpl implements PrintService {
    @Override
    public void printInfo() {
        System.out.println("new hello world");
    }
}

public class PrintServiceImpl implements PrintService {
    @Override
    public void printInfo() {
        System.out.println("hello world");
    }
}

public class jdkDemo {
     public static void main(String[] args) {
        ServiceLoader<PrintService> serviceServiceLoader =
            ServiceLoader.load(PrintService.class);
        //获取所有的SPI实现,循环调用printInfo方法
        for (PrintService printService : serviceServiceLoader) {
            //打印出"new hello world" "hello world"
            printService.printInfo();        }
    }
}

        其中ServiceLoader的部分核心实现如下所示,这里指定了配置文件的保存路径,这里实现了迭代器,当进行循环迭代时会使用到当前线程的ClassLoader类加载器。

public final class ServiceLoader<S> implements Iterable<S> {

    //扫描目录前缀
    private static final String PREFIX = "META-INF/services/";
    // 被加载的类或接口
    private final Class<S> service;
    // 用于定位、加载和实例化实现方实现的类的类加载器
    private final ClassLoader loader;
    // 上下文对象
    private final AccessControlContext acc;
    // 按照实例化的顺序缓存已经实例化的类
    private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
    // 懒查找迭代器
    private java.util.ServiceLoader.LazyIterator lookupIterator;

    public static <S> ServiceLoader<S> load(Class<S> service) {
        //使用当前线程的类加载器
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }    

    // 私有内部类,提供对所有的service的类的加载与实例化
    private class LazyIterator implements Iterator<S> {
        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        String nextName = null;

        private boolean hasNextService() {
            if (configs == null) {
                try {
                    //获取扫描的路径
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    //...
                }
                //....
            }
        }

        private S nextService() {
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                //反射加载类
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
            }
            try {
                //实例化
                S p = service.cast(c.newInstance());
                //放进缓存
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                //..
            }
            //..
        }
    }
}

Dubbo作为一个高度可扩展的RPC框架,也依赖于Java的SPI,并且Dubbo对Java原生的SPI 机制作出了一定的扩展,使得其功能更加强大。 使用Dubbo框架中的ExtensionLoader加载器也可以实现SPI效果,但需要在接口类上添加@SPI注解和对应的的实现类别名"impl"对应配置文件上的key=value(实现类全路径名)。

META-INF/dubbo/com.seewo.dubbo.demo.spi.printService文件内容如下
impl=com.seewo.dubbo.demo.spi.PrintServiceImpl

@SPI("impl")
public interface PrintService {
    void printInfo();
}

PrintService printService = 
    ExtensionLoader.getExtensionLoader(PrintService.class).getDefaultExtension();
printService.printInfo();

从上面的Java SPI的原理中可以了解到,Java的SPI机制有着如下的弊端:

  • 只能遍历所有的实现,并全部实例化,如果有扩展实现实现初始化很耗时,但后面可能没使用到,则浪费资源。
  • z配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们。
  • 扩展如果依赖其他的扩展,做不到自动注入和装配。 扩展很难和其他的框架集成,比如扩展里面依赖了一个Spring bean,原生的Java SPI不支持。

Dubbo SPI中有如下几个概念:

  • 扩展点: 接口;

  • 扩展: 扩展的实现;

  • 扩展自适应实例: 其实就是一个Extension的代理,实现了扩展点接口。在调用扩展点的接口方法时,会根据实际的参数来决定要使用哪个扩展。dubbo框架会根据接口中的参数,自动地决定选择哪个实现;

  • @SPI: 该注解作用于扩展点的接口上,表明该接口是一个扩展点;

  • @Adaptive:@Adaptive注解用在扩展接口的方法上。表示该方法是一个自适应方法。Dubbo在为扩展点生成自适应实例时,如果方法有@Adaptive注解,会为该方法生成对应的代码。

1.2 扩展点配置规范

       Dubbo SPI和Java SPI类似,需要在 META-INF/dubbo/路径下放置对应的SPI配置文件,文件名称以需要命名为接口的全路径名字,配置文件的内容为 key=扩展点具体实现类的全路径名,如果有多个实现类则使用换行符作为分隔符。其中,key作为Dubbo SPI注解中传入的参数。另外,Dubbo SPI还兼容了Java SPI的配置路径和内容配置方式,在Dubbo框架启动的时候,会去默认扫描META-INF/services、META-INF/dubbo/、META-INF/dubbo/internal/这个三个路径。

  • SPI配置文件路径: META-INF/services、META-INF/dubbo/、META-INF/dubbo/internal/
  • SPI配置文件名称: 全路径类名
  • 文件内容格式: key=value方式,多个则用换行符分隔

1.3 扩展点分类与缓存

       Dubbo SPI分为Class类缓存、实例缓存。这两种缓存又能根据扩展类的种类分为普通扩展类、包装扩展类(Wrapper类)、自适应扩展(Adaptive类)等。

  • Class类缓存: Dubbo SPI获取扩展类时,会先从缓存中读取,如果缓存中不存在,则加载配置文件,根据配置文件加载的Class缓存到内存中,而不实例化。
  • 实例缓存: Dubbo不仅会缓存Class,而且也会缓存Class实例化后的对象,每次获取的时候,也是先从缓存中读取,如果缓存中不存在,则重新加载并缓存起来。

被缓存起来的Class和对象实例可以根据不同的特性分为不同的类别:

  • 普通扩展类,配置在SPI配置文件中的扩展类实现;
  • 包装扩展类,这种Wrapper类没有具体的实现,只做了通用逻辑的抽象,并且需要在构造方法中传入一个具体的扩展接口的实现。
  • 自适应扩展类,一个扩展接口会有多种实现类,具体使用哪个实现类在运行时根据传入的URL中的某些参数动态确定和调整。
  • 其他缓存,如扩展类加载器缓存、扩展名缓存等。

具体缓存实现在ExtensionLoader的实现如下所示:

public class ExtensionLoader<T> {

    //扩展类与对应的扩展类加载器缓存
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>>
         EXTENSION_LOADERS = new ConcurrentHashMap<>(64);

    //扩展类与类实例缓存
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES 
        = new ConcurrentHashMap<>(64);

    //扩展类与扩展名缓存
    private final ConcurrentMap<Class<?>, String> cachedNames         = new ConcurrentHashMap<>();

    //普通扩展类缓存,不包括自适应扩展类和Wrapper类
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();

    //扩展名与有@Activate注解对象的缓存
    private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
   
     //扩展类名与扩展对象缓存
    private final ConcurrentMap<String, Holder<Object>> cachedInstances 
        = new ConcurrentHashMap<>();

    //实例化后的自适应(Adaptive)扩展对象,只能同时存在一个
    private final Holder<Object> cachedAdaptiveInstance = new Holder<>();

    //Wrapper类缓存
    private Set<Class<?>> cachedWrapperClasses;
}

1.4 扩展点的特性

1.4.1 自动包装

        ExtensionLoader在加载扩展时,如果发现这个扩展类包含其他扩展点作为其构造方法的参数,则这个扩展类会被认为是Wrapper类。以ProtocolFilterWrapper类为例,这个类继承了Protocol接口,这个类的构造函数注入了一个Protocol类型的参数,因此ProtocolFilterWrapper认定为Wrapper类,这是一种装饰器模式,可以将通用的逻辑抽象进行封装或对其子类进行增强,让子类更加专注自身的实现。

public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolFilterWrapper(Protocol protocol) {
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }

    ....
}

1.4.2 自动加载

       除了在构造函数中传入其他扩展实例,还会经常使用setter方法设置属性值,ExtensionLoader在执行扩展点初始化时,会自动通过setter方法注入对应的实现类。如下Transport扩展点的定义,在@Adaptive中传入了两个参数分别是"server"和"transport"。当外部调用Transport#bind方法时,会动态传入的参数"URL"提取参数"server"的value值,如果匹配上某个扩展实现类则直接使用对应的扩展实现类。如果未匹配上,则通过第二个参数"transport"提取value值,如果都没匹配上则抛出异常。也就是@Adaptive中传入多个参数,则依次实现类的匹配,直到最后抛出异常。

@SPI("netty")
public interface Transporter {

    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
    RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException;
  
    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;

}

1.4.3 自适应

在Dubbo SPI中,使用了@Adaptive注解,可以动态地通过URL中的参数来确定要使用哪个具体的实现类。

1.4.4 自动激活

        使用@Activate注解,可以标记对应的扩展点默认被激活启用,该注解还可以通过传入不同的参数,设置扩展点在不同的条件下下被自动激活。

2.扩展点注解使用

2.1 @SPI注解

        @SPI注解有一个value属性,可以传入不同的参数来设置这个接口的默认实现类。Dubbo很多地方都是通过getExtension(Class type, String name)来获取扩展点接口的具体实现,此时对传入的Class做校验,判断是否是接口,是否有@SPI注解。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
    //默认实现的key名称
    String value() default "";
}

2.2 @Adaptive注解

      @Adaptive注解可以标注在类、接口、枚举和方法上,类级别的只有AdaptiveExtensionFactory和AdaptiveCompiler,其他都是标注在方法级上。方法级别的注解在第一次getExtension时,会自动生成和编译一个动态的Adaptive类,从而达到动态实现类的效果。注解上可以传入value数组参数,在初始化Adaptive注解的接口时,会先对传入的URL进行key值匹配,第一个key没匹配上则匹配第二个,知道所有的key匹配完毕,如果还没匹配到则使用@SPI注解的默认值去匹配。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    //可以设置多个,会被依次匹配
    String[] value() default {};

}

2.3 @Activate注解

       @Activate可以标记在的类、接口、枚举和方法上,主要使用在多个扩展点实现上,需要根据不同条件被激活的场景中,如Filter需要多个同时激活,因为每个Filter实现的是不同的功能。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
    //URL中的分组如果匹配则激活,可以设置多个
    String[] group() default {};
    //如果URL中含有该key值,则会被激活
    String[] value() default {};
    //排序信息
    int order() default 0;
}

3.ExtensionLoader工作原理

        ExtensionLoder是整个扩展机制的主要实现类,在这个类里面实现了配置加载、扩展类缓存、自适应对象生成缓存等工作。

3.1 ExtensionLoader创建

        ExtensionLoader通过工厂方法ExtensionFactory创建的,这里使用的是工厂设计模式,有多个ExtensionLoader生成实现。

@SPI
public interface ExtensionFactory {
    <T> T getExtension(Class<T> type, String name);
}

       工厂类有多个具体实现,可以看到的有SpringExtensionFactory、AdaptiveExtensionFactory、SpiExtensionFactory三个具体工厂实现类。

spring=org.apache.dubbo.config.spring.extension.SpringExtensionFactory
adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory

        其中Dubbo和Spring容器是通过SpringExtensionFactory进行打通的,该工厂实现类保存了Spring上下文的静态方法,可以把Spring上下文保存到Set集合中,这样可以多次调用时添加了重复的上下文引用。SpringExtensionFactory是在ReferenBean和ServiceBean初始化时调用静态方法时保存Spring上下文。

public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean,
        ApplicationContextAware, InitializingBean, DisposableBean {

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        SpringExtensionFactory.addApplicationContext(applicationContext);
    }
}

public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
        ApplicationContextAware, BeanNameAware, ApplicationEventPublisherAware {

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        SpringExtensionFactory.addApplicationContext(applicationContext);
    }
}

public class SpringExtensionFactory implements ExtensionFactory {

    //用来保存Spring上下文的Set集合
    private static final Set<ApplicationContext> CONTEXTS 
            = new ConcurrentHashSet<ApplicationContext>();

    public static void addApplicationContext(ApplicationContext context) {
        CONTEXTS.add(context);
        if (context instanceof ConfigurableApplicationContext) {
            ((ConfigurableApplicationContext) context).registerShutdownHook();
        }
    }
    ....
    @Override
    public <T> T getExtension(Class<T> type, String name) {
        //遍历所有Spring上下文,根据名字和类型从Spring容器中查找
        for (ApplicationContext context : CONTEXTS) {
            T bean = BeanFactoryUtils.getOptionalBean(context, name, type);
            if (bean != null) {
                return bean;
            }
        }
        return null;
    }
}

SpiExtensionFactory的实现比较简单,直接使用ExtensionLaoder实现获取返回对象。

public class SpiExtensionFactory implements ExtensionFactory {
    @Override
    public <T> T getExtension(Class<T> type, String name) {
        //判断类型是否为接口,接口类上是否有@SPI注解
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }
}

        AdaptiveExtensionFactory是默认实现,有@Adaptive注解,所有会去获取所有扩展类生成工厂并缓存起来,缓存的工厂类通过TreeSet排序,SPI排前面,Spring排后面,当使用getExtension时,会遍历所有的工厂,先从SPI容器中获取扩展类,如果没找到再从Spring容器中获取扩展类。

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
    
    //用来缓存所有的工厂类实现,包括SpiExtensionFactory/SpringExtensionFactory
    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        //工厂列表是也通过SPI实现的,这里可以获取到所有的工厂实现类
        ExtensionLoader<ExtensionFactory> loader = 
            ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        //遍历所有的工厂实现类名,并获取到工厂实现类,放入缓存列表
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        //遍历所有的工厂类进行查找
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }
}

3.2 ExtensionLoader初始化

        ExtensionLoader的初始化配置规定了几种资源文件加载策略,可以通过ExtensionLoader#setLoadingStrategies设置好配置文件加载策略。如果想从其他目录文件加载类,自定义资源加载策略并设置即可。

//加载策略接口
public interface LoadingStrategy extends Prioritized {
    //返回加载配置的路径
    String directory();
    //预处理
    default boolean preferExtensionClassLoader() {
        return false;
    }
    //排除在外的包路径
    default String[] excludedPackages() {
        return null;
    }
}

//自定义的资源加载策略
public class DubboExternalLoadingStrategy implements LoadingStrategy {
    @Override
    public String directory() {
        //配置文件保存路径
        return "META-INF/dubbo/external/";
    }
    @Override
    public boolean overridden() {
        return true;
    }
    @Override
    public int getPriority() {
        //加载优先级
        return MAX_PRIORITY + 1;
    }
}

    在获取扩展实例前是需要加载类信息进来的,通过ExtensionLoader#getExtensionClasses方法中,如果已经加载过放在缓存中就会获取缓存中的类信息,如果没有则调用ExtensionLoader#loadExtensionClasses方法,读取配置文件路径,然后加载类信息进来。

private Map<String, Class<?>> loadExtensionClasses() {
        ....
        //遍历多个配置文件加载策略
        for (LoadingStrategy strategy : strategies) {
            //从配置文件保存路径中读取配置并加载
            loadDirectory(extensionClasses, strategy.directory(), 
                type.getName(), strategy.preferExtensionClassLoader(), 
                strategy.overridden(), strategy.excludedPackages());
             ....
        }
        return extensionClasses;
}


private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
                               boolean extensionLoaderClassLoaderFirst, 
                                boolean overridden, String... excludedPackages) {        String fileName = dir + type;
        try {
            ....
            //通过getResources()或者getSystemResources得到配置文件
            if (urls == null || !urls.hasMoreElements()) {
                if (classLoader != null) {
                    urls = classLoader.getResources(fileName);
                } else {
                    urls = ClassLoader.getSystemResources(fileName);
                }
            }

            if (urls != null) {
                //遍历urls,解析字符串,得到扩展实现类,并缓存起来
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
                }
            }
        } catch (hrowable t) {
            ...
        }
    }
}

        加载完的扩展点配置后, 再通过反射获得所有扩展实现类实现并缓存起来。在加载Class文件时,会根据Class上的标识来判断扩展点类型,再根据类型分类做缓存。

private void loadClass(Map<String, Class<?>> extensionClasses, 
                        java.net.URL resourceURL, Class<?> clazz, String name,
                           boolean overridden) throws NoSuchMethodException {
        //如果类上有@Adaptive注解,则缓存为自适应类
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz, overridden);
        //如果是扩展包装类,则直接缓存起来
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            ...
            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n, overridden);
                }
            }
        }
    }
}

3.2 ExtensionLoader主要功能方法解析

         ExtensionLoader主要有获取普通扩展类(getExtension)、获取自适应扩展类(getAdaptiveExtension)、获取自动激活的扩展类(getActiveExtension)三个方法,这也是比较常用到的。 

3.2.1 getExtension实现原理

       在调用getExtension(String name)方法时,会先检查在cachedInstance缓存中的是否有现成的数据,没有则调用createExtension开始创建。

public T getExtension(String name) {

        //先从缓存中看有没有现成的数据
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    //没有就创建
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }
}

private T createExtension(String name) {
        
        ...
        try {
            //从缓存中获取扩展类实例的情况
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            //向扩展类注入其依赖的属性,如扩展类A又依赖类扩展类B
            injectExtension(instance);

            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                //遍历扩展点包装类,用于初始化包装类实例
                for (Class<?> wrapperClass : wrapperClasses) {
                    //找到构造方法的参数类型为type的包装类,为其注入扩展类实例
                    instance = injectExtension((T) wrapperClass
                            .getConstructor(type)
                            .newInstance(instance));
                }
            }

            //初始化扩展实例
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            ...
        }
}

       injectExtension总体上实现了Spring IOC功能, 通过反射获取到类的所有定义的方法,遍历获取set开头的方法,并得到set方法的参数类型,再通过ExtensionFactory寻找类型相同的扩展类实例,如果找到,则使用反射设置注入进去,这个在获取Wrapper包装类中比较常见。

private T injectExtension(T instance) {

        ....
        try {
            for (Method method : instance.getClass().getMethods()) {
                //如果不是set开头的方法则跳过
                if (!isSetter(method)) {
                    continue;
                }

                //获取方法第一个参数的类型
                Class<?> pt = method.getParameterTypes()[0];
                if (ReflectUtils.isPrimitives(pt)) {
                    continue;
                }
                ....
                try {
                    //通过字符串截取,获取小写开头的类名,如setTestService,截取testService
                    String property = getSetterProperty(method);
                    //通过ExtensionFactory获取实例
                    Object object = objectFactory.getExtension(pt, property);
                    //如果获取了这个扩展类实现,则调用set方法,将实例注入进去
                    if (object != null) {
                        method.invoke(instance, object);
                    }
                } catch (Exception e) {
                 .....
                }

            }
        } catch (Exception e) {
            .....
        }
        return instance;
    }

3.2.2 getAdaptiveExtension实现原理

     在getAdaptiveExtension方法中,会为扩展点接口自动生成实现字符串,实现主要逻辑如下:为接口中每个有@Adaptive注解的方法生成默认的实现(没有注解的方法则为空实现),每个默认实现都会从URL中提取Adaptive参数值,并以此为依据动态加载扩展点。然后,框架会使用不同的编译器,把实现字符串编译为自适应类并返回。

       该注解可以传入value参数,是一个数组。Adaptived可以传入多个key值,在初始化@Adaptive注解的接口时,会先对传入的URL进行key值匹配,第一个key没匹配上则匹配第二个,以此类推。直到所有的key匹配完毕,如果还没有匹配到,则使用@SPI注解中填写的默认值再去匹配,如果@SPI注解没有默认的值,则会抛出异常。

public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError != null) {
                 ....
            }
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        instance = createAdaptiveExtension();
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
                       ...
                    }
                }
            }
        }
        return (T) instance;
}

//动态编译生成Class
private Class<?> createAdaptiveExtensionClass() {
    //生成类文件定义字符串
    String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
    ClassLoader classLoader = findClassLoader();
    org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    return compiler.compile(code, classLoader);
}

3.2.3 getActivateExtension实现原理

       getActiveExtension(URL url, String key, String group)方法获取所有自动激活扩展点。参数分别为URL、URL中指定的多个key(多个逗号隔开)和URL中指定的分组信息(group)。

**
**

public List<T> getActivateExtension(URL url, String[] values, String group) {
        //初始化所有扩展类实现的集合
        List<T> activateExtensions = new ArrayList<>();
        List<String> names = values == null ? new ArrayList<>(0) : asList(values);

        if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
            //先加载所有扩展实现类
            getExtensionClasses();
            //遍历map缓存
            for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
                String name = entry.getKey();
                Object activate = entry.getValue();
                String[] activateGroup, activateValue;
                if (activate instanceof Activate) {
                    activateGroup = ((Activate) activate).group();
                    activateValue = ((Activate) activate).value();
                } else {
                    continue;
                }

                //如果满足分组
                if (isMatchGroup(group, activateGroup)
                        && !names.contains(name)
                        && !names.contains(REMOVE_VALUE_PREFIX + name)
                        && isActive(activateValue, url)) {
                    activateExtensions.add(getExtension(name));
                }
            }
            //根据用户URL
            activateExtensions.sort(ActivateComparator.COMPARATOR);
        }
        ...
    }
}

4.扩展点动态编译实现

       其中动态编译是自适应特性实现的基础,动态生成的自适应类只是字符串,需要通过编译才能真正转化成Class。动态编译就是为了提高性能,不用每次都是去使用反射代理一个类,而是采用直接编译生成好一个Class,Dubbo SPI通过代码动态生成,并配合动态编译器,灵活地在原始类基础上创建新的自适应类。Dubbo代码编译器有JDK、Javassist、AdaptiveCompiler编译器三种,都实现了Compiler接口。

       Compiler接口有个@SPI注解,默认代码编译器的实现类为Javassist。其中Java动态生成Class的方式有很多,常见的通过字节码方式如CGLIB/ASM等生成,而Javassist是通过字符串代码再编译为Class的方式完成的。

//生成类定义字符串
String getSimpleCodeWithSyntax0(){
     StringBuilder code = new StringBuilder();
     code.append("package org.apache.dubbo.common.compiler.support;");
     code.append("public class HelloServiceImpl_0 implements HelloService {");
     code.append("   public String sayHello() { ");
     code.append("       return \"Hello world!\"; ");
     code.append("   }");
    return code.toString();
}

//使用Javassist编译器编译类定义字符串后生成class
public void testCompileJavaClass1() throws Exception {
   JavassistCompiler compiler = new JavassistCompiler();
   Class<?> clazz = compiler.compile(getSimpleCodeWithSyntax0(), JavassistCompiler.class.getClassLoader());
   Object instance = clazz.newInstance();
   Method sayHello = instance.getClass().getMethod("sayHello");
   sayHello.invoke(instance);
}

5.总结

         本篇介绍了Dubbo SPI的一些概要信息,包括了Java SPI的区别、实现原理、Dubbo SPI的新特性、配置规范与的内部缓存等,比较重要的是@SPI/@Adaptive/@Activate三个注解,其中这三个注解的作用与实现原理,然后结合核心实现类ExtensionLoader,讲解了它的生成机制/初始化机制/使用的getExtension/getAdaptiveExtension/getActivateExtension三个主要方法的实现,最后还讲解了动态编译相关的内容。

参考文献

www.cnblogs.com/jy107600/p/… SPI原理解析

www.baidu.com/link?url=rY… 反射与动态代理性能对比