ExtensionLoader相关技术源码解析

上一片着重分析了ExtensionLoader在实际应用中的几个核心方法的工作原理,这篇将作为上一篇的补充,重点分析ExtensionLoader源码中使用的几个核心技术点。 在ExtensionLoader的源码分析中曾提到过一个objectFactory属性未做详细解释,这里将给你解密。

不积跬步,无以至千里;不积小流,无以成江河。

  1. ExtensionFactory原理
    顾名思义,ExtensionFactory是一个扩展点的工厂类。
/**
 * ExtensionFactory
 * 扩展点工厂,加载接口的实现类。这个接口包括Dubbo中的SPI接口和一般类型的接口
 */
@SPI
public interface ExtensionFactory {

    /**
     * Get extension.
     * 获取扩展点实例
     * @param type object type 接口类型.
     * @param name object name 接口的扩展点名称.
     * @return object instance 扩展点实例.
     */
    <T> T getExtension(Class<T> type, String name);

}

从源码可以看出:
① 这是一个SPI接口
② 接口中只有一个方法getExtension 用于获取接口的扩展点实例。
③ 接口中有两个参数,一个是接口的类型,一个扩展实例的名称

我们在看一下ExtensionFactory接口在Dubbo框架中结构层次:
在这里插入图片描述
从上图可以看出 ExtensionFactory接口有三个实现类:
SpiExtensionFactory
获取dubbo容器中SPI接口扩展点实例工厂,具体实现我们来撸源码:

/**
 * SpiExtensionFactory
 * Dubbo SPI扩展点工厂类,主要功能是从Dubbo容器中获取SPI接口的默认的扩展点
 */
public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        //1要求type必须是一个接口,并且有@SPI注解。这是dubbo中SPI接口的标准配置
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            //2调用ExtensionLoader的静态方法获取type接口的ExtensionLoader实例,getExtensionLoader逻辑很简单,想了解的自己翻阅源码
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            //3 loader.getSupportedExtensions()返回的是扩展点名称的TreeSet集合
            if (!loader.getSupportedExtensions().isEmpty()) {
                //4使用ExtensionLoader#getAdaptiveExtension()获取默认的实现类,这个方法在上一篇中有详细介绍
                return loader.getAdaptiveExtension();
            }
        }
        //不满足1就直接返回null
        return null;
    }
}

从SpiExtensionFactory 的实现可以看出,底层还是使用的ExtensionLoader#getAdaptiveExtension() 。而参数name在整个过程没有被使用。这里获取SPI接口实例的关键是接口type

SpringExtensionFactory
这是Dubbo整合Spring框架时,获取spring的bean容器中的实例的工厂类

/**
 * SpringExtensionFactory
 * dubbo与spring容器的整合
 * 通过SpringExtensionFactory可以获取到spring容器中的扩展类
 */
public class SpringExtensionFactory implements ExtensionFactory {
    private static final Logger logger = LoggerFactory.getLogger(SpringExtensionFactory.class);
    /**
     *  自动去重的set集合保存spring的上下文对象
     */
    private static final Set<ApplicationContext> CONTEXTS = new ConcurrentHashSet<ApplicationContext>();
    /**
     * Dubbo自动关闭的监听器,当监听到spring应用关闭事件时,自动关闭dubbo
     */
    private static final ApplicationListener SHUTDOWN_HOOK_LISTENER = new ShutdownHookListener();

    public static void addApplicationContext(ApplicationContext context) {
        CONTEXTS.add(context);
        if (context instanceof ConfigurableApplicationContext) {
            ((ConfigurableApplicationContext) context).registerShutdownHook();
            DubboShutdownHook.getDubboShutdownHook().unregister();
        }
        ApplicationContextUtils.addApplicationListener(context, SHUTDOWN_HOOK_LISTENER);
    }

    public static void removeApplicationContext(ApplicationContext context) {
        CONTEXTS.remove(context);
    }

    public static Set<ApplicationContext> getContexts() {
        return CONTEXTS;
    }

    // currently for test purpose
    public static void clearContexts() {
        CONTEXTS.clear();
    }

    /**
     * 从spring容器中获取指定class类型和名称的对象
     * @param type object type. 扩展点类型
     * @param name object name. 扩展点名称
     * @param <T> 扩展点class
     * @return 扩展点
     */
    @Override
    @SuppressWarnings("unchecked")
    public <T> T getExtension(Class<T> type, String name) {
        /*
         * SPI should be get from SpiExtensionFactory
         * 如果扩展类时一个接口,并且接口上由@SPI注解,就换回null。
         * 意思是:SPI接口的扩展点实现应该从SpiExtensionFactory中获取
         */
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            return null;
        }
        //遍历Spring的上下文对象ApplicationContext
        for (ApplicationContext context : CONTEXTS) {
            //通过接口类型和接口实现的名称从上下文中获取接口的实例对象,如果有多个实现,默认获取第一个
            T bean = BeanFactoryUtils.getOptionalBean(context, name, type);
            if (bean != null) {
                return bean;
            }
        }

        logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());
        //Spring容器中没有找到就返回null   
        return null;
    }

    private static class ShutdownHookListener implements ApplicationListener {
        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            if (event instanceof ContextClosedEvent) {
                DubboShutdownHook shutdownHook = DubboShutdownHook.getDubboShutdownHook();
                shutdownHook.doDestroy();
            }
        }
    }
}

由实现逻辑可以看出,就是在spring容器的上下文对象作为静态变量存入SpringExtensionFactory中,获取接口实例时就遍历spring的上下文,从中根据接口类型和实现类的名称去查找,有就返回,没有就返回null

AdaptiveExtensionFactory
标注了@Adaptive注解,可见这个是ExtensionFactory默认的实现类。AdaptiveExtensionFactory有个List factories参数

/**
 * AdaptiveExtensionFactory
 * 由于本实现类上有@Adaptive注解,因此它才是ExtensionFactory的默认实现
 * 其本身包含ExtensionFactory的所有实现类
 * 在获取接口实例时,就遍历其他的ExtensionFactory实例。调用他们的getExtension方法
 */
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
    /**
     * 存放SpiExtensionFactory ,SpringExtensionFactory实例
     */
    private final List<ExtensionFactory> factories;
    /**
     * 在构造方法中就加载所有的ExtensionFactory的实例
     */
    public AdaptiveExtensionFactory() {
        //ExtensionLoader#getExtensionLoader((Class<T> type)获取ExtensionFactory对应的ExtensionLoader实例
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        /*
          ExtensionLoader.getSupportedExtensions()返回的TreeSet集合,
          里面会对ExtensionFactory进行排序,默认排序会使SpiExtensionFactory实例排在前面
          这样就会优先从Dubbo的SPI容器中获取扩展点,如果获取不到再从SpringExtensionFactory容器中获取 。
         */
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        //并使用不可变的list存到内存中
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        /*
          获取扩展点实例,实际是调用SpiExtensionFactory,SpringExtensionFactory等的getExtension 。 妙啊!!!
         */
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}

到这里就能明白了ExtensionFactory接口的工作流程,具体功能实现在SpiExtensionFactory和SpringExtensionFactory中,而AdaptiveExtensionFactory作为默认实现,主要作用是用于管理协调其他的实现。

下面再来看下ExtensionFactory在ExtensionLoader中的具体应用:

public class ExtensionLoader<T> {
    /**
     * 扩展点获取工厂类,实际上是对ExtensionLoader功能的进一步封装
     */
    private final ExtensionFactory objectFactory;
       /**
     * 在唯一的构造方法中,初始化objectFactory参数
     * 使用的getAdaptiveExtension()方法获取默认实现类(也就是标注了@Adaptive的AdaptiveExtensionFactory的实例)
     * @param type 接口type
     */
    private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

}

揭秘彩蛋
在这里插入图片描述
相信结合原理以及具体的应用场景,现在对ExtensionFactory接口的原理应该是非常明了了

  1. Compiler之扩展点动态生产编译原理
    ExtensionLoader源码解析中讲到获取SPI接口自适应扩展点实例源码的时候,提到过在通过ExtensionLoader#getAdaptiveExtension()获取接口的默认实现时,如果这个接口没有使用@Adaptive注解标注的实现类,dubbo会根据原始接口来生产它的自适应实现类的Java代码(String),然后通过Compiler编译为class 。

这一动态生产SPI接口自适应实现类的方式是的扩展的自适应特性非常的灵活好用,而字节码编译器Compiler是实现这一特性的基础

首先看一下Compiler接口在Dubbo框架中的层次结构:
在这里插入图片描述

心细的同学可能会发现,Compiler接口采用了类似与ExtensionFactoty接口相同设计模式。具体业务的实现类是JdkCompiler和JavassistCompiler。 AdaptiveCompiler上标记了@Adaptive,表示其作为Compiler接口默认的实现类。负责整合调用JdkCompiler和JavassistCompiler

首先还是撸源码,先看一下Compiler接口的源码:

/**
 * Compiler. (SPI, Singleton, ThreadSafe)
 * 编译器SPI接口 ,由于在@SPI注解中指定了参数值为:javassist
 * 表示Compiler使用javassist作为默认的实现
 */
@SPI("javassist")
public interface Compiler {

    /**
     * Compile java source code.
     * 将java源文件编译为Class
     * @param code        Java source code
     * @param classLoader classloader
     * @return Compiled class
     */
    Class<?> compile(String code, ClassLoader classLoader);

}

下图为Compile接口的配置文件:
在这里插入图片描述
再看一下Compiler接口唯一的一个抽象子类AbstractCompiler的源码,主要抽象的功能是从Java源码中获取类的全路径限定名

/**
 * Abstract compiler. (SPI, Prototype, ThreadSafe)
 * Compiler抽象类,主要抽象的功能是从Java源码中获取类的全路径限定名
 */
public abstract class AbstractCompiler implements Compiler {

    private static final Pattern PACKAGE_PATTERN = Pattern.compile("package\\s+([$_a-zA-Z][$_a-zA-Z0-9\\.]*);");

    private static final Pattern CLASS_PATTERN = Pattern.compile("class\\s+([$_a-zA-Z][$_a-zA-Z0-9]*)\\s+");

    @Override
    public Class<?> compile(String code, ClassLoader classLoader) {
        code = code.trim();
        Matcher matcher = PACKAGE_PATTERN.matcher(code);
        //获取Java源文件中定义的包名,值如:org.apache.dubbo.common.compiler.support
        String pkg;
        if (matcher.find()) {
            pkg = matcher.group(1);
        } else {
            pkg = "";
        }
        matcher = CLASS_PATTERN.matcher(code);
        //获取Java源文件中class的类名,值如:AbstractCompiler
        String cls;
        if (matcher.find()) {
            cls = matcher.group(1);
        } else {
            throw new IllegalArgumentException("No such class name in " + code);
        }
        //拼接出class的全路径名,值如:org.apache.dubbo.common.compiler.support.AbstractCompiler
        String className = pkg != null && pkg.length() > 0 ? pkg + "." + cls : cls;
        try {
            //通过class全路径名直接加载该class 。对于项目包中没有的类,这个方法会出错,然后走异常,这是重点
            return Class.forName(className, true, org.apache.dubbo.common.utils.ClassUtils.getCallerClassLoader(getClass()));
        } catch (ClassNotFoundException e) {
            if (!code.endsWith("}")) {
                throw new IllegalStateException("The java code not endsWith \"}\", code: \n" + code + "\n");
            }
            try {
                /*
                 * 如果从项目中找不到该类,就执行doCompile
                 * 可以发现这个方法是一个模版方法,在本类中没有实现,留给它的子类取实现
                 * 也就是JavassistCompiler和JdkCompiler去实现
                 */
                return doCompile(className, code);
            } catch (RuntimeException t) {
                throw t;
            } catch (Throwable t) {
                throw new IllegalStateException("Failed to compile class, cause: " + t.getMessage() + ", class: " + className + ", code: \n" + code + "\n, stack: " + ClassUtils.toString(t));
            }
        }
    }
    
    protected abstract Class<?> doCompile(String name, String source) throws Throwable;

}

再看一下AbstractCompiler两个子类中是如何实现doCompile的
Java实现(由于具体实现很复杂,这里只展示doCompile的实现步骤)

/**
 * JdkCompiler. (SPI, Singleton, ThreadSafe)
 */
public class JdkCompiler extends AbstractCompiler {

    private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    private final DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<JavaFileObject>();

    private final ClassLoaderImpl classLoader;

    private final JavaFileManagerImpl javaFileManager;

   ...
    @Override
    public Class<?> doCompile(String name, String sourceCode) throws Throwable {
        int i = name.lastIndexOf('.');
        String packageName = i < 0 ? "" : name.substring(0, i);
        String className = i < 0 ? name : name.substring(i + 1);
        JavaFileObjectImpl javaFileObject = new JavaFileObjectImpl(className, sourceCode);
        javaFileManager.putFileForInput(StandardLocation.SOURCE_PATH, packageName,
                className + ClassUtils.JAVA_EXTENSION, javaFileObject);
        Boolean result = compiler.getTask(null, javaFileManager, diagnosticCollector, options,
                null, Arrays.asList(javaFileObject)).call();
        if (result == null || !result) {
            throw new IllegalStateException("Compilation failed. class: " + name + ", diagnostics: " + diagnosticCollector);
        }
        return classLoader.loadClass(name);
    }

...

}

Javassist实现

/**
 * JavassistCompiler. (SPI, Singleton, ThreadSafe)
 */
public class JavassistCompiler extends AbstractCompiler {

    private static final Pattern IMPORT_PATTERN = Pattern.compile("import\\s+([\\w\\.\\*]+);\n");

    private static final Pattern EXTENDS_PATTERN = Pattern.compile("\\s+extends\\s+([\\w\\.]+)[^\\{]*\\{\n");

    private static final Pattern IMPLEMENTS_PATTERN = Pattern.compile("\\s+implements\\s+([\\w\\.]+)\\s*\\{\n");

    private static final Pattern METHODS_PATTERN = Pattern.compile("\n(private|public|protected)\\s+");

    private static final Pattern FIELD_PATTERN = Pattern.compile("[^\n]+=[^\n]+;");

    @Override
    public Class<?> doCompile(String name, String source) throws Throwable {
        CtClassBuilder builder = new CtClassBuilder();
        builder.setClassName(name);

        // process imported classes
        Matcher matcher = IMPORT_PATTERN.matcher(source);
        while (matcher.find()) {
            builder.addImports(matcher.group(1).trim());
        }

        // process extended super class
        matcher = EXTENDS_PATTERN.matcher(source);
        if (matcher.find()) {
            builder.setSuperClassName(matcher.group(1).trim());
        }

        // process implemented interfaces
        matcher = IMPLEMENTS_PATTERN.matcher(source);
        if (matcher.find()) {
            String[] ifaces = matcher.group(1).trim().split("\\,");
            Arrays.stream(ifaces).forEach(i -> builder.addInterface(i.trim()));
        }

        // process constructors, fields, methods
        String body = source.substring(source.indexOf('{') + 1, source.length() - 1);
        String[] methods = METHODS_PATTERN.split(body);
        String className = ClassUtils.getSimpleClassName(name);
        Arrays.stream(methods).map(String::trim).filter(m -> !m.isEmpty()).forEach(method -> {
            if (method.startsWith(className)) {
                builder.addConstructor("public " + method);
            } else if (FIELD_PATTERN.matcher(method).matches()) {
                builder.addField("private " + method);
            } else {
                builder.addMethod("public " + method);
            }
        });

        // compile
        ClassLoader classLoader = org.apache.dubbo.common.utils.ClassUtils.getCallerClassLoader(getClass());
        CtClass cls = builder.build(classLoader);
        return cls.toClass(classLoader, JavassistCompiler.class.getProtectionDomain());
    }

}

由于技术层面原因,Java和Javassist两种方式将Java源文件编译为Class的具体实现细节这里就不再做相关介绍,想要了解的同学自己去它们官方网站学习。本博客的重点是讲述Dubbo框架的底层实现原理和流程。

接下来就是我们的官方指定的实现AdaptiveCompiler要登场了。我还是透过源码来看它是如何工作的

/**
 * AdaptiveCompiler. (SPI, Singleton, ThreadSafe)
 * 代码编译器的默认实现,用于管理JavassistCompiler和JdkCompiler 。 原理类似用AdaptiveExtensionFactory!
 */
@Adaptive
public class AdaptiveCompiler implements Compiler {
    /**
     * 默认编译器,在dubbo框架中提供了两种实现javassist和jdk
     */
    private static volatile String DEFAULT_COMPILER;

    /**
     * 设置代码编译器
     * 此方法会在ApplicationConfig中被调用
     * @param compiler 代码编译器实例的名称
     */
    public static void setDefaultCompiler(String compiler) {
        DEFAULT_COMPILER = compiler;
    }

    @Override
    public Class<?> compile(String code, ClassLoader classLoader) {
        Compiler compiler;
        //获取Compiler接口对应的ExtensionLoader实例
        ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
        // copy reference
        String name = DEFAULT_COMPILER;
        //如果指定使用什么编译器就是用指定的,没有指定就获取默认的,默认为Javassist Compiler
        if (name != null && name.length() > 0) {
            compiler = loader.getExtension(name);
        } else {
            compiler = loader.getDefaultExtension();
        }
        //调用真正的编译器编译字符串格式的Java类
        return compiler.compile(code, classLoader);
    }

}

可以看到,获取这个编译器的实现还是使用ExtensionLoader#getExtension(name) 。由此可见,ExtensionLoader在Dubbo框架中的地位。想要深入了解Dubbo框架的同学,必须得熟悉ExtensionLoader的实现原理!!!这个坎必须得迈过去!!!

  1. DCL原理
    DCL全称是Double Check Lock(双重校验锁),是在高并发环境中确保线程安全的一个重要的模式。多用于单例模式中。

先可以通过双重检验锁定DCL这一遍文章好好了解下 。

讲述DCL原理之前先说一下volatile 和 synchronized 的区别:
① synchronized 修饰方法或者代码块。它是强制加锁,保证读和写都有原子性,性能开销大。
② volatile 只能修饰成员属性,保证在读的时候对所有线程可见,而写就不保证了。适合用在一个线程写,多个线程读的情况。
DCL常见的用法如下:

public class LazySingletonDCL {
    private volatile static LazySingletonDCL instance;
    
    private LazySingletonDCL() {
    }

    public static LazySingletonDCL getInstance() {
        if (instance == null) {
            synchronized (LazySingletonDCL.class) {
                if (instance == null) {
                    instance = new LazySingletonDCL();
                }
            }
        }
        return instance;
    }

}

心细的同学会发现,dubbo框架源码中这种用法无处不在。如在ExtensionLoader#getExtension(name)中

/**
 * Find the extension with the given name.
 * If the specified name is not found, then {@link IllegalStateException} will be thrown.
 *
 * 扩展类加载器中最核心的方法:
 *  根据扩展点的名称返回扩展点的实现类
 */
@SuppressWarnings("unchecked")
public T getExtension(String name) {
    if (StringUtils.isEmpty(name)) {
        throw new IllegalArgumentException("Extension name == null");
    }
    //如果扩展点名称为true字符串,则返回默认的扩展类
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    /*
     *扩展容器,从缓存中获取,没有就新建一个加入缓存
     */
    final Holder<Object> holder = getOrCreateHolder(name);
    //典型的DCL单例模式
    Object instance = holder.get();
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                //如果Holder是一个空的Holder时,就新建一个给定扩展名的扩展点实例
                instance = createExtension(name);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}
/**
 * Helper Class for hold a value.
 */
public class Holder<T> {

    private volatile T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }

}

想要深入理解DCL保证线程安全的原理,需要熟悉Java的内存模型。这个现在已经有很多大牛的博客中有详细讲述到,我现在就不在赘述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值