转载

Dubbo SPI 特性及源码实现分析

  • 扩展点自动包装
  • 扩展点自动装配
  • 扩展点自适应
  • 扩展点自动激活

文章会顺着这四个特点解析源码,在分析四个特性的源码前,会先分析一下 配置加载

ExtensionLoader 类和配置加载

大部分的框架的生命周期往往都必定会包括: 配置加载实例使用 两个阶段。在 Dubbo SPI 中,加载的部分就是 注解配置文件

这四个特性都有一个前置步骤,就是类和配置的加载。所以在分析特性前,需要先解析 前置的加载过程

加载某个接口的扩展,大概步骤大概如下:

  • 配备三个加载路径,分别从三个加载路径中加载SPI配置文件(services、dubbo、dubbo/internal)
    • 寻找这个接口的文件(从不同的 URL 中搜索)
    • 遍历文件中提取 name - class 键值对
      • 通过加载器获得 class 对象
      • 分析注解、对象特征,并把信息保存在 ExtensionLoader 中
Robot defaultExtension =
ExtensionLoader.getExtensionLoader(Robot.class).getDefaultExtension();

// ExtensionLoader.java 以加载 DefaultExtension 为入口
    public T getDefaultExtension() {
        getExtensionClasses();  // 加载 Robot.class 所有扩展类 从这里切入
        if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
            return null;
        }
        return getExtension(cachedDefaultName);
    }

// 做一次线程安全的双重检测加载
    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;
    }

// 根据不同的策略(其实就是目录),把不同的目录下的配置 /XXXX/XXX/com.qpm.dubbo.test.spi.Robot 扫描并加载
    private Map<String, Class<?>> loadExtensionClasses() {
        // 通过 SPI 注解获得默认的扩展名称
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();

        // 通过三种不同的加载策略,加载三个目录下的所有扩展类
        for (LoadingStrategy strategy : strategies) {
            // loadDirecotry 加载某个目录下的Robot配置 从这里切入
            loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
            loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
        }

        return extensionClasses;
    }

// 
    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
                               boolean extensionLoaderClassLoaderFirst, String... excludedPackages) {
        String fileName = dir + type;   // 配置文件的路径
        try {
            Enumeration<java.net.URL> urls = null;
            // 找到 CLassLoader
            ClassLoader classLoader = findClassLoader();

            // try to load from ExtensionLoader's ClassLoader first
            // fixme 策略可能会prefer使用 扩展类加载器,为什么?
            if (extensionLoaderClassLoaderFirst) {
                ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
                if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
                    urls = extensionLoaderClassLoader.getResources(fileName);
                }
            }

            if(urls == null || !urls.hasMoreElements()) {
                if (classLoader != null) {
                    // 虽然我们寻找的只是一个文件,但不同的jar包可以也打包着相同的文件
                    // 例如 Mysql 的 jar 带有 Java.sql.Driver SPI 的配置
                    // Oracle 的 jar 也会有 Java.sqlDriver SPI 的配置
                    // 因此需要 ClassLoader 帮忙把所有 Java.sql.Driver 的URL都定位出来
                    urls = classLoader.getResources(fileName);
                } else {
                    urls = ClassLoader.getSystemResources(fileName);
                }
            }

            if (urls != null) {
                while (urls.hasMoreElements()) {    // urls 获得了多个 element
                    java.net.URL resourceURL = urls.nextElement();  // 获得绝对路径
                    // 获得了绝对路径,根据绝对路径逐个进行加载。 从这里 切入
                    loadResource(extensionClasses, classLoader, resourceURL, excludedPackages);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }
    
    // 加载某个资源文件的配置
    private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
                              java.net.URL resourceURL, String... excludedPackages) {
        try {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) { // 把文件读进来
                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 { // 从一行数据中提取 K-V 值
                            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 && !isExcluded(line, excludedPackages)) {
                                // 通过 classLoader 加载类文件,然后再执行 SPI 的 loadClass 从此处切入
                                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);
                        }
                    }
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }
    
    // 真正的加载 class 对象
    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 occurred when loading extension class (interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + " is not subtype of interface.");
        }
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            // 假如有自适应注解,则把该 class 记录在 ExtensionLoader 自适应缓存中
            cacheAdaptiveClass(clazz);
        } else if (isWrapperClass(clazz)) { // 通过反射获得构造器,假如获取成功,则认为其是一个包装类
            // 假如是一个包装类,则加到包装类的缓存中
            cacheWrapperClass(clazz);
        } else {
            // 处理正在的扩展实现类
            clazz.getConstructor();     // 检查无参构造函数
            if (StringUtils.isEmpty(name)) {
                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 (ArrayUtils.isNotEmpty(names)) {
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);    // 缓存名称
                    saveInExtensionClass(extensionClasses, clazz, n);   // 把类配置写入 extensionClasses, 至此 SPI 的类加载过程就结束了
                }
            }
        }
    }
复制代码

上述代码写得比较杂乱,主要是调试了 Dubbo SPI 机制,对一个标有 SPI 注解的 Robot 接口,完整的类加载跟踪,最终的结果是构造到一个 ExtensionLoader<Robot> 对象 ,里面通过扫描,加载,把 Robot 的实现类,包装类 Wrapper ,自适应 Adaptive ,自激活等信息都分别记录在 ExtensionLoader<Robot> 对象 ExtensionLoader<Robot> 对象里。

当程序执行完: ExtensionLoader.getExtensionClasses() ,就会配置文件都加载到 ExtensionLoader 中了。即为四个特性提供了足够的信息配置信息。

Dubbo SPI 特性及源码实现分析

扩展点自动包装 Wrapper

上一小节是分析了 SPI 配置的加载源码,这一小节会分析实例的创建。这个就很有 IOC 的味道了。获得实例的入口可以从: getExtension(String name)

  • PS: 关于 Dubbo SPI 的 extension 概念,和 Spring IOC 的 bean 概念是非常相似的

getExtension

// ExtensionLoader.java 
    public T getExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        final Holder<Object> holder = getOrCreateHolder(name);  // 使用 Holder 提前发布对象,类似先通过 Holder 占住坑位
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) { // double Check
                    instance = createExtension(name); // 从这里进入
                    holder.set(instance);   // 把对象创建好后,设置到 句柄 中
                }
            }
        }
        return (T) instance;
    }
// 构建一个 Extension 
    private T createExtension(String name) {
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            // 反射构建对象
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            // 依赖注入 (扩展点自动装配)
            injectExtension(instance);

            // 获取包装类集合 一个类是有多个包装类的
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;

            // Wrapper 包装类的实现过程,这里要实现的是多层包装类的包装,具体实现是这样的。
            // Wrapper 包装过程和实现细节
            // 假设有实现类 A,包装类 A1Wrapper,A2Wrapper,连续包装的步骤如下:
            // 1、迭代到A1Wrapper,A 给 A1Wrapper 做构造参数,然后获得 A1Wrapper[持有A]
            // 2、迭代到A2Wrapper,A1Wrapper 给 A2Wrapper 做构造参数,然后获得 A2Wrapper[持有A1Wrapper[A]]
            // 3、有更多的迭代器时也以此类推
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }
复制代码

以上就是创建一个 Extension 对象的过程,简单就是:

  • 检查缓存中有没该对象
  • 没有,进行 double check 创建对象
    • 1、获取类对象
    • 2、通过反射构建对象
    • 3、进行依赖注入
    • 4、进行 Wrapper 递归包装(详细在注释中)

扩展点自动装配 injectExtension(instance)

构建 Extension 对象时,除了通过反射构造对象实例,还需要进行依赖注入(inject),要进行依赖注入,必须要先拥有对象容器(Container, 在 Spring 里就是 BeanFactory),在分析 injectExtension 方法时,要分析对象容器 Factory 的来源。

private T injectExtension(T instance) {
        if (objectFactory == null) {
            return instance;
        }
        try {
            for (Method method : instance.getClass().getMethods()) {
                if (!isSetter(method)) {    // 获取所有的 Setter Method
                    continue;
                }
                /**
                 * Check {@link DisableInject} to see if we need auto injection for this property
                 */
                if (method.getAnnotation(DisableInject.class) != null) {
                    continue;
                }
                Class<?> pt = method.getParameterTypes()[0];
                if (ReflectUtils.isPrimitives(pt)) {
                    continue;
                }
                try {
                    String property = getSetterProperty(method);    // 取出参数
                    Object object = objectFactory.getExtension(pt, property);   
                    // 根据参数向 Factory 获取对象 这里需要知道 objectFactory 的对象从哪里来的???
                    if (object != null) {
                        method.invoke(instance, object);        // 执行 setter 对象
                    }
                } catch (Exception e) {
                    logger.error("Failed to inject via method " + method.getName()
                            + " of interface " + type.getName() + ": " + e.getMessage(), e);
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

// 上述并不难,关键是知道 ExtensionLoader.objectFactory 对象的来源。对象的来源也是在 ExtensionLoader 构造的时候设置的。

    private ExtensionLoader(Class<?> type) {
        this.type = type;
        // 构造时,先获取工厂对象,用于进行依赖注入的,使用的是自适应类型
        objectFactory = (type == ExtensionFactory.class ? null :
            ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
复制代码

ExtensionFactory ——Dubbo SPI 的 IOC 容器

上述代码可以查看到,SPI 机制的 ExtensionFactory 容器也是通过 ExtensionLoader 获得的。通过查看 ExtensionFactory 的实现,可以发现有以下两个实现。

先查看 ExtensionFactory 的自适应实现,它就是为了

@Adaptive   // 自适应注解
public class AdaptiveExtensionFactory implements ExtensionFactory {

    private final List<ExtensionFactory> factories;     // 有两个对象

    public AdaptiveExtensionFactory() {
        // 一个是 Dubbo SPI 本身的容器 (SpiExtensionFactory),
        // 一个是 Spring 容器 (SpringExtensionFactory)
        // 这里就意味着 SpringExtensionFactory 是被 Dubbo 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);
    }
    
    ... 省略代码
}
复制代码

从上面的注释上表明,SPI 的注入对象会从 SpiExtensionFactory 和 SpringExtensionFactory 这两个工厂里获得。

SpiExtensionFactory 类相比比较简单,就是获得根据接口

public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension(); // 获取自适应对象
            }
        }
        return null;
    }
}
复制代码

SpringExtensionFactory 就是链接 Dubbo SPI 容器和 Spring 容器的 桥梁 ,有了这个类, @SPI 注解就可以直接设置在 Spring 的 Bean 上,借用 Spring 更加强大的 IOC 和 AOP 功能了。

SpringExtensionFactory 的类是被设置在 dubbo-config-spring 的模块内的。

/**
 * SpringExtensionFactory
 */
public class SpringExtensionFactory implements ExtensionFactory {
    private static final Logger logger = LoggerFactory.getLogger(SpringExtensionFactory.class);

    private static final Set<ApplicationContext> CONTEXTS = new ConcurrentHashSet<ApplicationContext>();

    // Spring 一调用这个静态方法,就可以把自己的 Context 设置进来了
    public static void addApplicationContext(ApplicationContext context) {
        CONTEXTS.add(context);
        if (context instanceof ConfigurableApplicationContext) {
            ((ConfigurableApplicationContext) context).registerShutdownHook();
        }
    }
    
    ... 省略其他方法
}
复制代码

扩展点自适应 Adaptive

有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。 —— Dubbo 官方文档

设定有接口:

- Robot.java @SPI
    - sayHello @Adaptive
- Bumblebee implements Robot
- OptimusPrime implements Robot
复制代码

假如可以设计一个特别的 XClass implements Robot ,可以根据 参数 调用 BumblebeeOptimusPrime ,岂不美哉。这就是 适应 ,假如是 Dubbo SPI 自己生成的,那就可以理解成 自适应 了。

自适应机制:从 ExtensionLoader.getAdaptiveExtension() 方法获得一个对象,这个对象是 Dubbo SPI 机制自行建立的,可以根据 URL 自定义转发。

@Test
    public void testAdaptive() {
        System.out.println("Dubbo SPI Adaptive ");
        ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
        // 从 getAdaptiveExtension 切入查看
        Robot autoRobot = extensionLoader.getAdaptiveExtension();
        assertTrue(
                autoRobot.sayHelloAndReturnSelf(URL.valueOf("dubbo://127.0.0.1:20880/RobotService?robot=bumblebee"))
                instanceof
                Bumblebee);

        // console: Hello, I am Bumblebee
        assertTrue(
                autoRobot.sayHelloAndReturnSelf(URL.valueOf("dubbo://127.0.0.1:20880/RobotService?robot=optimusPrime"))
                instanceof
                OptimusPrime);
        // console:hello, I am Optimus Prime.
    }
    
    // ExtensionLoader.java 构建自适应的类
    private T createAdaptiveExtension() {
        try {
            // getAdaptiveExtensionClass Dubbo SPI 构建类对象
            // newInstance 通过反射构建
            // injectExtension 依赖注入
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }
    
    /**
     * 自适应类的构建
     *
     * 步骤:
     * 1、根据自适应的定义生成代理类代码 (XXX.java) 官方文档 https://dubbo.apache.org/zh-cn/docs/source_code_guide/adaptive-extension.html
     * 2、编译成 (XXX.class) 并加载类 (Class<XXX> 对象)
     *
     * @return
     */
    private Class<?> createAdaptiveExtensionClass() {
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); // XXX.java
        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);
    }
    
    // 以上是构建的源码,demo构建出来的代码如下:
    
    /**
     * 这是 Dubbo SPI 自动生成的代码
     * https://dubbo.apache.org/zh-cn/docs/source_code_guide/adaptive-extension.html
     * 有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载
     */
    public class Robot$Adaptive implements com.qpm.dubbo.test.spi.Robot {
        public Robot sayHelloAndReturnSelf(org.apache.dubbo.common.URL arg0)  {
            // 1、URL 参数是否为 null, 因此 URL 参数是必须的
            if (arg0 == null)
                throw new IllegalArgumentException("url == null");
            org.apache.dubbo.common.URL url = arg0;

            // 2、根据 URL 获取选择哪个扩展实例
            String extName = url.getParameter("robot", "bumblebee");
            if(extName == null)
                throw new IllegalStateException("Failed to get extension (com.qpm.dubbo.test.spi.Robot) name from url (" + url.toString() + ") use keys([robot])");

            // 3、获取实例
            com.qpm.dubbo.test.spi.Robot extension
                    = (com.qpm.dubbo.test.spi.Robot)ExtensionLoader.getExtensionLoader(com.qpm.dubbo.test.spi.Robot.class).getExtension(extName);

            // 4、执行实例方法
            return extension.sayHelloAndReturnSelf(arg0);
        }
    }
复制代码

至此,自适应机制就实现了,其实还是比较简单的。

扩展点自激活 Activate

自激活机制 @Activate ,定义比较简单:根据 URL 的参数和组别等信息对 扩展对象 做过滤,最终获得一系列符合 URL 规则的 扩展对象 ,这个过程就是自激活过程。

源码分析如下:

@Activate(value = "robot:autobotFighter", group = "autobot")    // 定义这个 扩展对象 一些用于过滤的 K-V 值
public class Bumblebee implements Robot{
    @Override
    public Robot sayHelloAndReturnSelf(URL url) {
        System.out.println("Hello, I am Bumblebee");
        return this;
    }
}

@Activate(value = "robot:autobotBoss", group = "autobot")
public class OptimusPrime implements Robot{

    @Override
    public Robot sayHelloAndReturnSelf(URL url) {
        System.out.println("hello, I am Optimus Prime.");
        return this;
    }
}


// ExtensionLoader.java
   /**
     * Get activate extensions.
     *
     * 自激活实现。规则如下:
     * 1、REMOVE_VALUE_PREFIX+name eg: "-name" 表示过滤掉某个 Extension
     * 2、URL的K-V,可以和 @Activate 中的注解 value 进行匹配
     * 3、group 参数指定 @Activate 的组别,和 2 一起进行匹配,两者关系为 且
     * 4、2和3条件都不合适(扩展对象没写 @Activate 注解),URL 提取出来的 value 匹配了 Extension 的 name
     *
     * @param url    url
     * @param values extension point names
     * @param group  group
     * @return extension list which are activated
     * @see org.apache.dubbo.common.extension.Activate
     */
    public List<T> getActivateExtension(URL url, String[] values, String group) {
        // 最终返回的激活扩展点列表
        List<T> activateExtensions = new ArrayList<>();
        List<String> names = values == null ? new ArrayList<>(0) : Arrays.asList(values);
        // 名字中不包含 "-default"
        if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
            getExtensionClasses();
            for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
                String name = entry.getKey();
                Object activate = entry.getValue();

                // 获取 group、activateGroup
                String[] activateGroup, activateValue;

                if (activate instanceof Activate) {
                    // 从注解中获得 自激活 的 Group 和 Value
                    activateGroup = ((Activate) activate).group();
                    activateValue = ((Activate) activate).value();
                } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
                    activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
                    activateValue = ((com.alibaba.dubbo.common.extension.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));
                }
            }
            // 设置排序
            activateExtensions.sort(ActivateComparator.COMPARATOR);
        }
        
        // 自激活不一定要走 K-V 匹配,也可以直接通过 Extensions.name 直接进行匹配,这时候就不一定需要 @Activate 注解了
        List<T> loadedExtensions = new ArrayList<>();
        for (int i = 0; i < names.size(); i++) {
            String name = names.get(i);
            if (!name.startsWith(REMOVE_VALUE_PREFIX)
                    && !names.contains(REMOVE_VALUE_PREFIX + name)) {
                if (DEFAULT_KEY.equals(name)) {
                    if (!loadedExtensions.isEmpty()) {
                        // 假如是 default,则强行插入在 扩展链条 的第一位
                        activateExtensions.addAll(0, loadedExtensions);
                        loadedExtensions.clear();
                    }
                } else {
                    loadedExtensions.add(getExtension(name));
                }
            }
        }
        if (!loadedExtensions.isEmpty()) {
            activateExtensions.addAll(loadedExtensions);
        }
        return activateExtensions;
    }
    
    
    // 判断一个 @Activate 注解的k-v值,是否和 url 的值相匹配
    private boolean isActive(String[] keys, URL url) {
        if (keys.length == 0) {
            return true;
        }
        for (String key : keys) {
            // @Active(value="key1:value1, key2:value2")
            String keyValue = null;
            if (key.contains(":")) {
                String[] arr = key.split(":");
                key = arr[0];   // 注解K
                keyValue = arr[1];  // 注解 V
            }

            for (Map.Entry<String, String> entry : url.getParameters().entrySet()) {
                String k = entry.getKey();  // URL K
                String v = entry.getValue();    // URL V
                
                // 查看是否匹配。假如 URL 和 注解 有一项匹配就可以返回 true
                if ((k.equals(key) || k.endsWith("." + key))
                        && ((keyValue != null && keyValue.equals(v)) || (keyValue == null && ConfigUtils.isNotEmpty(v)))) {  
                    return true;
                }
            }
        }
        return false;
    }

复制代码

自激活至此也分析完了,整体也说不是很难,但直接看代码去反推匹配规则会比较吃力。整体来说就是根据条件返回符合条件的 扩展对象

原文  https://juejin.im/post/5eacd041e51d454d8a2b985f
正文到此结束
Loading...