深度解析dubbo扩展技术dubbo spi(实现二)

1.从配置文件加载扩展点实现

接着上篇文章从getExtensionClasses()方法开始,来看看这个获取获取扩展点实现类class这个方法

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

首先先从cachedClasses 成员变量获取存储class 对象的map,这个cachedClasses 也是一个holder对象,只有一个volatile修饰的value,咱们cachedClasses这个holder的value值就是一个存储扩展点实现类class的map,然后接着往下,判断classes是否为空,如果为空的话加了个同步锁synchronized,然后再获取一下,再判断,咱们一般判空加锁的时候也应该再判断空,这样是防止重复加载的情况,接着往下走到loadExtensionClasses方法

  // synchronized in getExtensionClasses
    private Map<String, Class<?>> loadExtensionClasses() {

        //获取到接口上面的 spi 注解信息
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {


            //拿到spi注解上默认的值
            String value = defaultAnnotation.value();
            if ((value = value.trim()).length() > 0) {
                String[] names = NAME_SEPARATOR.split(value);  // 使用, 分割
                if (names.length > 1) {  //出现多个默认实现   --->报错(spi 中的value 只支持一个)
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }
                //  将默认实现类的名字缓存起来
                if (names.length == 1) cachedDefaultName = names[0];
            }
        }

        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        // 去下面的这3个位置找 扩展配置文件
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);//   META-INF/dubbo/internal/
        loadDirectory(extensionClasses, DUBBO_DIRECTORY);  //   META-INF/dubbo/
        loadDirectory(extensionClasses, SERVICES_DIRECTORY);  //    META-INF/services/    兼容jdk spi
        return extensionClasses;
    }

首先是拿到扩展点接口上面的@SPI信息,然后拿到注解默认值,然后使用逗号分隔,如果多个话,就抛出异常,也就是只有一个是默认实现,然后缓存这个默认实现的name。
再往下就是创建了一个map,专门用来存储扩展实现类的class的,key就是你那个名字,然后value就是实现类class,再往下就是调用了三次loadDirectory方法,然后第二个参数传入的就是dubbo允许你扩展实现类配置文件的文件的位置,它们分别是:

  1. META-INF/dubbo/internal/
  2. META-INF/dubbo/
  3. META-INF/services/ 这个主要是兼容jdk的spi
    接着我们再看下loadDirectory 方法:
 private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
        String fileName = dir + type.getName();
        try {
            Enumeration<java.net.URL> urls;

            //获取classloader

            ClassLoader classLoader = findClassLoader();

            //使用类加载器获取Enumeration<java.net.URL> urls
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {

                //
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

首先是拼装装载文件的路径,由规定路径+ 接口类型全名
我们可以先看下,在dubbo中是怎样配置的
在这里插入图片描述
我们可以看到配置文件 的文件名字就是扩展点接口的全路径名,然后我们在看下配置文件中具体的内容,就是“name=实现类全路径名”的形式
在这里插入图片描述
上面这个是dubbo的,jdk的没有name 跟等号,上来就是实现类全类名,这个是jdk规定的。
再接着看loadDirectory这个方法,获取ClassLoader,如果获取到了就使用获取到的classloader获取资源,得到Enumeration<java.net.URL> urls,这个就跟迭代器一样的,没有获取到classloader就使用ClassLoader的静态方法获取资源,接着就是遍历这个urls,还有没有更多资源,如果有,就获取下一个资源url,然后执行loadResource方法。

 private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
        try {

            // 使用reader 读取文件, 按照约定一行一行读取
            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 跟实现类
                                name = line.substring(0, i).trim();//名字
                                line = line.substring(i + 1).trim();//实现类
                            }
                            if (line.length() > 0) {
                                /**
                                 * extensionClasses : 存储扩展的map, 整个查找扩展这块就是使用这个map
                                 * resourceURL:资源url
                                 * Class.forName(line, true, classLoader): 实现类class
                                 * name:实现类的名字
                                 */
                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                            }
                        } catch (Throwable t) {
                            IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                            exceptions.put(line, e);
                        }
                    }
                }
            } finally {
                reader.close();
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }

首先这里是先new了个BufferedReader,因为咱们配置文件都是一行行的,所以这里使用BufferedReader来读取遍历,拿到一行后先将#开头的截取掉,就是将注释去掉,然后再用等号分割成一个name,一个line,这个name可以对应到上面咱们那个配置文件的dubbo,然后line对应的就是“com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol”,如果line小于0 抛出异常,大于0 ,进行loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name)这个方法,咱们代码中有对这几个参数的解释。

 /**
     * laoxu
     * @param extensionClasses  存储map
     * @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 {
        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.");
        }

        //缓存自适应拓展对象的类到 `cachedAdaptiveClass`
        if (clazz.isAnnotationPresent(Adaptive.class)) {//   Adaptive 注解是否在该类上面
            if (cachedAdaptiveClass == null) {
                cachedAdaptiveClass = clazz;
            } else if (!cachedAdaptiveClass.equals(clazz)) {// Adaptive 注解在类上面的时候,只允许一个实现类上面有@Adaptive注解
                throw new IllegalStateException("More than 1 adaptive class found: "
                        + cachedAdaptiveClass.getClass().getName()
                        + ", " + clazz.getClass().getName());
            }
        } else if (isWrapperClass(clazz)) {// 该类是否是wapper (包装类)类 , 其实就是判断构造方法有没有传入扩展的这个类class

            Set<Class<?>> wrappers = cachedWrapperClasses;  // 获取缓存wrapper set
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();//没有的创建wrapper set
                wrappers = cachedWrapperClasses;
            }
            wrappers.add(clazz);
        } else {  // 其他的
            clazz.getConstructor();
            if (name == null || name.length() == 0) {  //兼容jdk的spi
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }
            String[] names = NAME_SEPARATOR.split(name);
            if (names != null && names.length > 0) {
                //获取自动激活的
                Activate activate = clazz.getAnnotation(Activate.class);
                if (activate != null) { //能够自动激活  有@Active 缓存起来
                    cachedActivates.put(names[0], activate);
                }
                //缓存
                for (String n : names) {
                    if (!cachedNames.containsKey(clazz)) {
                        cachedNames.put(clazz, n); ///"class com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory" -> "spi"
                    }
                    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());
                    }
                }
            }
        }
    }
  1. 首先判断了一下实现类是否是接口的子类,不是的话抛出异常
  2. 接下来判断实现类上面有没有@Adaptive注解,如果有的话,再判断 cachedAdaptiveClass 是否是null,这个cachedAdaptiveClass 主要就是缓存带有@Adaptive注解的实现类的,然后没有的话,就将实现类class赋值给cachedAdaptiveClass 成员,然后有的话,就要判断是不是同一个实现类class,不是的话就抛出异常,这里主要是为了一个扩展点接口只能有一个@Adaptive 放到类上面的实现类。
  3. 接下来就是判断是不是包装类,他这里判断是否是包装类的依据就是判断有没有一个构造是传入自身类型。可以看下代码如果是包装类的话,就添加到cachedWrapperClasses 这个set中缓存起来。
    /// 是否是wapper类型,判断有没有一个构造是传入自身类型的, private boolean isWrapperClass(Class<?> clazz) { try { clazz.getConstructor(type); return true; } catch (NoSuchMethodException e) { return false; } }
  4. 接下来就是判断name是否是空,如果为空的话就调用findAnnotationName方法获取一下name,咱们可以看下这个代码,
private String findAnnotationName(Class<?> clazz) {
        //获取@Extension 注解
        com.alibaba.dubbo.common.Extension extension = clazz.getAnnotation(com.alibaba.dubbo.common.Extension.class);
        if (extension == null) {//如果没有extension注解的话
            String name = clazz.getSimpleName();// 类名
            if (name.endsWith(type.getSimpleName())) {//如果实现类是以接口的名字结尾的话,就去取前面的字符串
                //实现类的类名是以接口名结尾的,就取实现类类名的前一段
                name = name.substring(0, name.length() - type.getSimpleName().length());
            }
            // 转成小写
            return name.toLowerCase();
        }
        return extension.value();
    }

主要就是判断有没有@Extension注解,如果没有的话,就要拿实现类的类名,然后判断实现类类名是否是以接口名为结尾的,如果是的话截取下要前面那块,如果不是以接口名为结尾的,就返回实现类的名字,如果有@Extension注解就直接拿注解里面的value值。
这块name为空的操作,主要是为了兼容jdk spi
接下来就是用逗号切割name,如果有name的话,再从实现类上get @Activate这个注解,这个注解主要是有激活的作用,如果Activate不为空的话,就放到cachedActivates 成员缓存起来,
接着就是遍历分割完的names,如果cachedNames 不包含 实现类class 的话就put进去缓存起来,如果extensionClasses中没有当前name的class就put进去缓存起来,如果有了的话,就要判断两个class是否相等,不相等话就要抛出异常了,因为同一个扩展点接口不允许有相同name的扩展点实现类的出现。
到最后咱们的 getExtensionClasses 方法返回的就是extensionClasses 这个里面放着以name为key ,扩展点实现类为value的map。

2.setter注入过程

我们来看下 injectExtension(instance) 这个方法,其中参数就是你这个扩展点实现类对象

  // 注入扩展
    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;
    }

首先 objectFactory != null 这句,主要为了判断你这个扩展点不是ExtensionFactory这个扩展点,接着遍历实现类的所有方法,然后方法得满足是set开头,只有一个参数,public方法这三个条件才能有可能帮你注入,接着就是判断方法上面是否有@DisableInject注解,有这个注解就可以跳过去了,不会自动注入。
接着就是拿到参数的class,截取方法名中除了set那块,之后便是objectFactory.getExtension(pt, property);这个objectFactory一般是AdaptiveExtensionFactory的对象,

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

这里就能根据扩展点类型获取到具体的实现类,然后调用 method.nvoke(instance, object)设置进去。

3.自动包装

自动注入完就是自动包装了,这一块最好是看着代码说,这里把上篇中关于自动包装的代码拿过来了

Set<Class<?>> wrapperClasses = cachedWrapperClasses;  //所有的wrapper缓存
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                for (Class<?> wrapperClass : wrapperClasses) {// 包装Wrapper
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }

首先获取缓存包装类的cachedWrapperClasses成员,然后判断如果不是空的话,就进行遍历
调用包装类的有参构造,传入要包装的实现类创建包装类对象,然后再自动注入赋值给instance,如果还有包装类,又把上个循环生成的包装类包装了一层,就是这样一层一层,就实现了自动包装。

扫描二维码关注公众号,回复: 11293421 查看本文章

猜你喜欢

转载自blog.csdn.net/yuanshangshenghuo/article/details/106401222