2、Dubbo的SPI机制分析1

Wesley13
• 阅读 823

1、Dubbo的SPI例子

@SPI
public interface Robot {
    void sayHello();
}

public class OptimusPrime implements Robot{
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Optimus Prime.");
    }
}

public class Bumblebee implements Robot{
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Bumblebee.");
    }
}


public class DubboSPITest {

    @Test
    public void sayHelloDubbo() throws Exception {
        ExtensionLoader<robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();
        Robot bumblebee = extensionLoader.getExtension("bumblebee");
        bumblebee.sayHello();
    }
}


输出:
Hello, I am Optimus Prime.
Hello, I am Bumblebee.

2、Dubbo的SPI源码分析

ExtensionLoader<robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    if (type == null)
        throw new IllegalArgumentException("Extension type == null");
    if (!type.isInterface()) {
        throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
    }
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type(" + type +
                ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
    }

    // 从缓存中获取与拓展类对应的ExtensionLoader
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        // 若缓存未命中,则创建一个新的实例,先简单看下new ExtensionLoader<T>(type)
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

这里的objectFactory创建可以参考: Dubbo的SPI机制分析3-Dubbo的IOC依赖注入

 private ExtensionLoader(Class<?> type) {
    this.type = type;
    // 这里的type是Robot.class,所以会执行后面的代码,加载并创建SpiExtensionFactory和SpringExtensionFactory,
    objectFactory = (type == ExtensionFactory.class ? null : 
                             ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}


Robot optimusPrime = extensionLoader.getExtension("optimusPrime");


public T getExtension(String name) {
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    // Holder也是用于持有对象的,用作缓存
    Holder<object> holder = cachedInstances.get(name);
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<object>());
        holder = cachedInstances.get(name);
    }
    Object instance = holder.get();
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                // 创建拓展实例,下面分析createExtension(name)
                instance = createExtension(name);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

这里依赖注入可以参考: Dubbo的SPI机制分析3-Dubbo的IOC依赖注入

private T createExtension(String name) {
    // 从配置文件中加载所有的拓展类,可得到“配置项名称”到“配置类”的映射关系表
    // 加载完后根据name获取,得到形如com.alibaba.dubbo.demo.provider.spi.OptimusPrime
    // 待会重点分析getExtensionClasses(),删去了一些非关键代码
    Class<?> clazz = getExtensionClasses().get(name);
    try {
        // 也是尝试先从缓存获取,获取不到通过反射创建一个并放到缓存中
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        // 依赖注入和cachedWrapperClasses,后面分析
        injectExtension(instance);
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
           for (Class<?> wrapperClass : wrapperClasses) {
              instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
           }
        }
        return instance;
    } 
}


 private Map<String, Class<?>> getExtensionClasses() {
    // 从缓存中获取映射关系表
    Map<string, class<?>> classes = cachedClasses.get();
    // 双重检查
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                // 若缓存中没有,去加载映射关系
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}


private Map<string, class<?>> loadExtensionClasses() {
    // 获取SPI注解,这里的type是在调用getExtensionLoader方法时传入的
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if (defaultAnnotation != null) {
        String value = defaultAnnotation.value();
        if ((value = value.trim()).length() > 0) {
            String[] names = NAME_SEPARATOR.split(value);
            if (names.length > 1) {
                // 抛异常
            }
            // 获取@SPI注解中的值,并缓存起来,可以关注下,后面会用到
            if (names.length == 1) cachedDefaultName = names[0];
        }
    }

    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    // 加载指定文件夹下的配置文件,META-INF/services/、META-INF/dubbo/、META-INF/dubbo/internal/
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    // 我是放在这个目录下的
    loadDirectory(extensionClasses, DUBBO_DIRECTORY);
    loadDirectory(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}


private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
    // fileName是META-INF/dubbo/com.alibaba.dubbo.demo.provider.spi.Robot
    String fileName = dir + type.getName();
    try {
        Enumeration<java.net.url> urls;
        ClassLoader classLoader = findClassLoader();
        // 根据文件名加载所有同名文件
        if (classLoader != null) {
            urls = classLoader.getResources(fileName);
        } else {
            urls = ClassLoader.getSystemResources(fileName);
        }
        if (urls != null) {
            while (urls.hasMoreElements()) {
                // 一个resourceURL就是一个文件
                java.net.URL resourceURL = urls.nextElement();
                loadResource(extensionClasses, classLoader, resourceURL);
            }
        }
    } 
}


private void loadResource(Map<string, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
    try {
        BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
        try {
            String line;
            // 按行读取配置内容
            while ((line = reader.readLine()) != null) {
                final int ci = line.indexOf('#');
                // 定位#字符,#之后的为注释,跳过
                if (ci >= 0) line = line.substring(0, ci);
                line = line.trim();

                if (line.length() > 0) {
                    try {
                        String name = null;
                        // 按等号切割
                        int i = line.indexOf('=');
                        if (i > 0) {
                            name = line.substring(0, i).trim();
                            line = line.substring(i + 1).trim();
                        }
                        if (line.length() > 0) {
                            // 真正的去加载类
                            loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                        }
                    }
                }
            }
        }
    } 
}

标注1,这里的可以参考: Dubbo的SPI机制分析2-Adaptive详解

标注2,这里的可以参考: Dubbo的SPI机制分析4-Dubbo通过Wrapper实现AOP

标注3,这里的cachedActivates可以参考: Dubbo的SPI机制分析5-Activate详解

private void loadClass(Map<string, Class<?>> extensionClasses, java.net.URL resourceURL, 
                                                     Class<?> clazz, String name) throws NoSuchMethodException {
    // clazz必须是type类型的,否则抛异常
    if (!type.isAssignableFrom(clazz)) {
    }
    // 判断clazz是否为标注了@Adaptive注解,标注1
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        if (cachedAdaptiveClass == null) {
            cachedAdaptiveClass = clazz;
        } else if (!cachedAdaptiveClass.equals(clazz)) {
            // 抛异常,不能有多个标注有@Adaptive注解的类
        }
    }
    // 判断是否是Wrapper类型,标注2
    else if (isWrapperClass(clazz)) {
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }
        wrappers.add(clazz);
    }

    // 程序进入此分支,表明clazz是一个普通的拓展类,Robot就是一个普通的拓展类
    else {
        // 检测clazz是否有默认的构造方法,如果没有,则抛出异常
        clazz.getConstructor();
        if (name == null || name.length() == 0) {
            name = findAnnotationName(clazz);
            if (name.length() == 0) {
               // 抛异常
            }
        }

        String[] names = NAME_SEPARATOR.split(name);
        if (names != null && names.length > 0) {
            // 用于@Activate根据条件激活,标注3
            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) {
                    // 存储名称到class的映射关系,这样就解析好了一行
                    extensionClasses.put(n, clazz);
                } else if (c != clazz) {
                    // 抛异常
                }
            }
        }
    }
}
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这