• 4-Dubbo架构设计与底层原理-服务导出源码分析(上)


    服务导出

    服务导出过程

    Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑。整个逻辑大致可分为三个部分,第一是前置工作,主要用于检查参数,组装 URL。第二是导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程。第三是向注册中心注册服务,用于服务发现。

    Dubbo 支持两种服务导出方式,分别延迟导出和立即导出。延迟导出的入口是 ServiceBean 的 afterPropertiesSet 方法,立即导出的入口是 ServiceBean 的 onApplicationEvent 方法。分析服务延迟导出过程,因此不会分析 afterPropertiesSet 方法。

    从 onApplicationEvent 方法说起,该方法收到 Spring 容器的刷新事件后,会调用 export 方法执行服务导出操作。服务导出之前,要进行对一系列的配置进行检查,以及生成 URL。准备工作做完,随后开始导出服务。首先导出到本地,然后再导出到远程。导出到本地就是将服务导出到 JVM 中,此过程比较简单。
    导出到远程的过程则要复杂的多,以 dubbo 协议为例,DubboProtocol 类的 export 方法将会被调用。该方法主要用于创建 Exporter 和 ExchangeServer。

    ExchangeServer 本身并不具备通信能力,需要借助更底层的 Server 实现通信功能。因此,在创建 ExchangeServer 实例时,需要先创建 NettyServer 或者 MinaServer 实例,并将实例作为参数传给 ExchangeServer 实现类的构造方法。ExchangeServer 实例创建完成后,导出服务到远程的过程也就接近尾声了。服务导出结束后,服务消费者即可通过直联的方式消费服务。
    当然,一般不会使用直联的方式消费服务。所以,在服务导出结束后,紧接着要做的事情是向注册中心注册服务。此时,客户端即可从注册中心发现服务。

    源码分析

    从 ServiceBean 的 onApplicationEvent 方法开始,探索 Dubbo 服务导出的全过程。看一下 onApplicationEvent 方法的源码。

    public void onApplicationEvent(ContextRefreshedEvent event) {
        // 是否有延迟导出 && 是否已导出 && 是不是已被取消导出
        if (isDelay() && !isExported() && !isUnexported()) {
            // 导出服务
            export();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    onApplicationEvent 是一个事件响应方法,该方法会在收到 Spring 上下文刷新事件后执行。这个方法首先会根据条件决定是否导出服务,比如有些服务设置了延时导出,那么此时就不应该在此处导出。还有一些服务已经被导出了,或者当前服务被取消导出了,此时也不能再次导出相关服务。
    注意这里的 isDelay 方法,这个方法字面意思是“是否延迟导出服务”,返回 true 表示延迟导出,false 表示不延迟导出。但是该方法真实意思却并非如此,当方法返回 true 时,表示无需延迟导出。返回 false 时,表示需要延迟导出。

    // -- ServiceBean
    private boolean isDelay() {
        // 获取 delay
        Integer delay = getDelay();
        ProviderConfig provider = getProvider();
        if (delay == null && provider != null) {
            // 如果前面获取的 delay 为空,这里继续获取
            delay = provider.getDelay();
        }
        // 判断 delay 是否为空,或者等于 -1
        return supportedApplicationListener && (delay == null || delay == -1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    暂时忽略 supportedApplicationListener 这个条件,当 delay 为空,或者等于-1时,该方法返回 true,而不是 false。

    supportedApplicationListener 该变量用于表示当前的 Spring 容器是否支持 ApplicationListener,这个值初始为 false。在 Spring 容器将自己设置到 ServiceBean 中时,ServiceBean 的 setApplicationContext 方法会检测 Spring 容器是否支持 ApplicationListener。若支持,则将 supportedApplicationListener 置为 true。

    前置工作

    前置工作主要包含两个部分,分别是配置检查,以及 URL 装配。在导出服务之前,Dubbo 需要检查用户的配置是否合理,或者为用户补充缺省配置。配置检查完成后,接下来需要根据这些配置组装 URL。在 Dubbo 中,URL 的作用十分重要。Dubbo 使用 URL 作为配置载体,所有的拓展点都是通过 URL 获取配置。

    检查配置

    onApplicationEvent 方法在经过一些判断后,会决定是否调用 export 方法导出服务。那么下面从 export 方法开始进行分析:

    public synchronized void export() {
        if (provider != null) {
            // 获取 export 和 delay 配置
            if (export == null) {
                export = provider.getExport();
            }
            if (delay == null) {
                delay = provider.getDelay();
            }
        }
        // 如果 export 为 false,则不导出服务
        if (export != null && !export) {
            return;
        }
    
        if (delay != null && delay > 0) {    // delay > 0,延时导出服务
            delayExportExecutor.schedule(new Runnable() {
                @Override
                public void run() {
                    doExport();
                }
            }, delay, TimeUnit.MILLISECONDS);
        } else {    // 立即导出服务
            doExport();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    export 对两个配置进行了检查,并配置执行相应的动作。首先是 export,这个配置决定了是否导出服务。有时候只是想本地启动服务进行一些调试工作,这个时候并不希望把本地启动的服务暴露出去给别人调用。此时,就可以通过配置 export 禁止服务导出,比如:

    <dubbo:provider export="false" />
    
    • 1

    delay 用于延迟导出服务。下面继续分析源码,这次要分析的是 doExport 方法。

    protected synchronized void doExport() {
        if (unexported) {
            throw new IllegalStateException("Already unexported!");
        }
        if (exported) {
            return;
        }
        exported = true;
        // 检测 interfaceName 是否合法
        if (interfaceName == null || interfaceName.length() == 0) {
            throw new IllegalStateException("interface not allow null!");
        }
        // 检测 provider 是否为空,为空则新建一个,并通过系统变量为其初始化
        checkDefault();
    
        // 下面几个 if 语句用于检测 provider、application 等核心配置类对象是否为空,
        // 若为空,则尝试从其他配置类对象中获取相应的实例。
        if (provider != null) {
            if (application == null) {
                application = provider.getApplication();
            }
            if (module == null) {
                module = provider.getModule();
            }
            if (registries == null) {...}
            if (monitor == null) {...}
            if (protocols == null) {...}
        }
        if (module != null) {
            if (registries == null) {
                registries = module.getRegistries();
            }
            if (monitor == null) {...}
        }
        if (application != null) {
            if (registries == null) {
                registries = application.getRegistries();
            }
            if (monitor == null) {...}
        }
    
        // 检测 ref 是否泛化服务类型
        if (ref instanceof GenericService) {
            // 设置 interfaceClass 为 GenericService.class
            interfaceClass = GenericService.class;
            if (StringUtils.isEmpty(generic)) {
                // 设置 generic = "true"
                generic = Boolean.TRUE.toString();
            }
        } else {    // ref 非 GenericService 类型
            try {
                interfaceClass = Class.forName(interfaceName, true, 
                                               Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            // 对 interfaceClass,以及  必要字段进行检查
            checkInterfaceAndMethods(interfaceClass, methods);
            // 对 ref 合法性进行检测
            checkRef();
            // 设置 generic = "false"
            generic = Boolean.FALSE.toString();
        }
    
        // local属性Dubbo官方文档中没有说明,不过local和stub在功能应该是一致的,用于配置本地存根
        if (local != null) {
            if ("true".equals(local)) {
                local = interfaceName + "Local";
            }
            Class<?> localClass;
            try {
                // 获取本地存根类
                localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            // 检测本地存根类是否可赋值给接口类,若不可赋值则会抛出异常,提醒使用者本地存根类类型不合法
            if (!interfaceClass.isAssignableFrom(localClass)) {
                throw new IllegalStateException("The local implementation class " + 
                   localClass.getName() + " not implement interface " + interfaceName);
            }
        }
    
        // stub 和 local 均用于配置本地存根
        if (stub != null) {
            // 此处的代码和上一个 if 分支的代码基本一致,这里省略了
        }
    
        // 检测各种对象是否为空,为空则新建,或者抛出异常
        checkApplication();
        checkRegistry();
        checkProtocol();
        appendProperties(this);
        checkStubAndMock(interfaceClass);
        if (path == null || path.length() == 0) {
            path = interfaceName;
        }
    
        // 导出服务
        doExportUrls();
    
        // ProviderModel 表示服务提供者模型,此对象中存储了和服务提供者相关的信息。
        // 比如服务的配置信息,服务实例等。每个被导出的服务对应一个 ProviderModel。
        // ApplicationModel 持有所有的 ProviderModel。
        ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, 
                                                        ref);
        ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109

    以上就是配置检查的相关分析,代码比较多。下面对配置检查的逻辑进行简单的总结,如下:

    1. 检测 dubbo:service 标签的 interface 属性合法性,不合法则抛出异常
    2. 检测 ProviderConfig、ApplicationConfig 等核心配置类对象是否为空,若为空,则尝试从其他配置类对象中获取相应的实例。
    3. 检测并处理泛化服务和普通服务类
    4. 检测本地存根配置,并进行相应的处理
    5. 对 ApplicationConfig、RegistryConfig 等配置类进行检测,为空则尝试创建,若无法创建则抛出异常
    多协议多注册中心导出服务

    Dubbo 允许使用不同的协议导出服务,也允许向多个注册中心注册服务。Dubbo 在 doExportUrls 方法中对多协议,多注册中心进行了支持。相关代码如下:

    private void doExportUrls() {
        // 加载注册中心链接
        List<URL> registryURLs = loadRegistries(true);
        // 遍历 protocols,导出每个服务
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    首先是通过 loadRegistries 加载注册中心链接,然后再遍历 ProtocolConfig 集合导出每个服务。并在导出服务的过程中,将服务注册到注册中心处。下面看一下 loadRegistries 方法的逻辑。

    protected List<URL> loadRegistries(boolean provider) {
        // 检测是否存在注册中心配置类,不存在则抛出异常
        checkRegistry();
        List<URL> registryList = new ArrayList<URL>();
        if (registries != null && !registries.isEmpty()) {
            for (RegistryConfig config : registries) {
                String address = config.getAddress();
                if (address == null || address.length() == 0) {
                    // 若 address 为空,则将其设为 0.0.0.0
                    address = Constants.ANYHOST_VALUE;
                }
    
                // 从系统属性中加载注册中心地址
                String sysaddress = System.getProperty("dubbo.registry.address");
                if (sysaddress != null && sysaddress.length() > 0) {
                    address = sysaddress;
                }
                // 判断 address 是否合法
                if (address.length() > 0 && 
                    !RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
                    Map<String, String> map = new HashMap<String, String>();
                    // 添加 ApplicationConfig 中的字段信息到 map 中
                    appendParameters(map, application);
                    // 添加 RegistryConfig 字段信息到 map 中
                    appendParameters(map, config);
                    map.put("path", RegistryService.class.getName());
                    map.put("dubbo", Version.getProtocolVersion());
                    map.put(Constants.TIMESTAMP_KEY, 
                            String.valueOf(System.currentTimeMillis()));
                    if (ConfigUtils.getPid() > 0) {
                        map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
                    }
                    if (!map.containsKey("protocol")) {
                        if (ExtensionLoader.getExtensionLoader(RegistryFactory.class)
                            .hasExtension("remote")) {
                            map.put("protocol", "remote");
                        } else {
                            map.put("protocol", "dubbo");
                        }
                    }
    
                    // 解析得到 URL 列表,address 可能包含多个注册中心 ip,
                    // 因此解析得到的是一个 URL 列表
                    List<URL> urls = UrlUtils.parseURLs(address, map);
                    for (URL url : urls) {
                        url = url.addParameter(Constants.REGISTRY_KEY, 
                                               url.getProtocol());
                        // 将 URL 协议头设置为 registry
                        url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
                        // 通过判断条件,决定是否添加 url 到 registryList 中,条件如下:
                        // (服务提供者 && register = true 或 null) 
                        //    || (非服务提供者 && subscribe = true 或 null)
                        if ((provider && url.getParameter(Constants.REGISTER_KEY, true))
                                || (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, 
                                                                  true))) {
                            registryList.add(url);
                        }
                    }
                }
            }
        }
        return registryList;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    包含如下逻辑:

    1. 检测是否存在注册中心配置类,不存在则抛出异常
    2. 构建参数映射集合,也就是 map
    3. 构建注册中心链接列表
    4. 遍历链接列表,并根据条件决定是否将其添加到 registryList 中
    组装 URL

    配置检查完毕后,紧接着要做的事情是根据配置,以及其他一些信息组装 URL。前面说过,URL 是 Dubbo 配置的载体,通过 URL 可让 Dubbo 的各种配置在各个模块之间传递。在阅读 Dubbo 服务导出相关源码的过程中,要注意 URL 内容的变化。

    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        String name = protocolConfig.getName();
        // 如果协议名为空,或空串,则将协议名变量设置为 dubbo
        if (name == null || name.length() == 0) {
            name = "dubbo";
        }
    
        Map<String, String> map = new HashMap<String, String>();
        // 添加 side、版本、时间戳以及进程号等信息到 map 中
        map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
        map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
        map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
        if (ConfigUtils.getPid() > 0) {
            map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
        }
    
        // 通过反射将对象的字段信息到 map 中
        appendParameters(map, application);
        appendParameters(map, module);
        appendParameters(map, provider, Constants.DEFAULT_KEY);
        appendParameters(map, protocolConfig);
        appendParameters(map, this);
    
        // methods 为 MethodConfig 集合,MethodConfig 中存储了  标签的配置信息
        if (methods != null && !methods.isEmpty()) {
            // 这段代码用于添加 Callback 配置到 map 中,代码太长,待会单独分析
        }
    
        // 检测 generic 是否为 "true",并根据检测结果向 map 中添加不同的信息
        if (ProtocolUtils.isGeneric(generic)) {
            map.put(Constants.GENERIC_KEY, generic);
            map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
        } else {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put("revision", revision);
            }
    
            // 为接口生成包裹类 Wrapper,Wrapper中包含了接口的详细信息,比如接口方法名数组,字段信息等
            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            // 添加方法名到 map 中,如果包含多个方法名,则用逗号隔开,比如 method = init,destroy
            if (methods.length == 0) {
                logger.warn("NO method found in service interface ...");
                map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
            } else {
                // 将逗号作为分隔符连接方法名,并将连接后的字符串放入 map 中
                map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>
                                                     (Arrays.asList(methods)), ","));
            }
        }
    
        // 添加 token 到 map 中
        if (!ConfigUtils.isEmpty(token)) {
            if (ConfigUtils.isDefault(token)) {
                map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
            } else {
                map.put(Constants.TOKEN_KEY, token);
            }
        }
        // 判断协议名是否为 injvm
        if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
            protocolConfig.setRegister(false);
            map.put("notify", "false");
        }
    
        // 获取上下文路径
        String contextPath = protocolConfig.getContextpath();
        if ((contextPath == null || contextPath.length() == 0) && provider != null) {
            contextPath = provider.getContextpath();
        }
    
        // 获取 host 和 port
        String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
        Integer port = this.findConfigedPorts(protocolConfig, name, map);
        // 组装 URL
        URL url = new URL(name, host, port, (contextPath == null || contextPath.length() 
                                             == 0 ? "" : contextPath + "/") + path, map);
        
        // 省略无关代码
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    首先是将一些信息,比如版本、时间戳、方法名以及各种配置对象的字段信息放入到 map 中,map 中的内容将作为 URL 的查询字符串。构建好 map 后,紧接着是获取上下文路径、主机名以及端口号等信息。最后将 map 和主机名等数据传给 URL 构造方法创建 URL 对象。需要注意的是,这里出现的 URL 并非 java.net.URL,而是 com.alibaba.dubbo.common.URL。

    这段代码用于检测 dubbo:argument 标签中的配置信息,并将相关配置添加到 map 中。代码如下:

    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        // ...
        // methods 为 MethodConfig 集合,MethodConfig 中存储了  标签的配置信息
        if (methods != null && !methods.isEmpty()) {
            for (MethodConfig method : methods) {
                // 添加 MethodConfig 对象的字段信息到 map 中,键 = 方法名.属性名。
                // 比如存储  对应的 MethodConfig,
                // 键 = sayHello.retries,map = {"sayHello.retries": 2, "xxx": "yyy"}
                appendParameters(map, method, method.getName());
    
                String retryKey = method.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    // 检测 MethodConfig retry 是否为 false,若是,则设置重试次数为0
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
                
                // 获取 ArgumentConfig 列表
                List<ArgumentConfig> arguments = method.getArguments();
                if (arguments != null && !arguments.isEmpty()) {
                    for (ArgumentConfig argument : arguments) {
                        // 检测 type 属性是否为空,或者空串(分支1)
                        if (argument.getType()!=null&&argument.getType().length() > 0) {
                            Method[] methods = interfaceClass.getMethods();
                            if (methods != null && methods.length > 0) {
                                for (int i = 0; i < methods.length; i++) {
                                    String methodName = methods[i].getName();
                                    // 比对方法名,查找目标方法
                                    if (methodName.equals(method.getName())) {
                                        Class<?>[] argtypes = 
                                            methods[i].getParameterTypes();
                                        if (argument.getIndex() != -1) {
                                            //检测 ArgumentConfig 中的 type 属性与方法参数列表
                                            // 中的参数名称是否一致,不一致则抛出异常(分支2)
                                            if (argtypes[argument.getIndex()].
                                                getName().equals(argument.getType())) {
                                                // 添加 ArgumentConfig 字段信息到 map 中,
                                                // 键前缀 = 方法名.index,比如:
                                                // map = {"sayHello.3": true}
                                                appendParameters(map, argument, 																	method.getName() + "."
                                                                 + argument.getIndex());
                                            } else {
                                                throw new 	
                                                    IllegalArgumentException("argument 
                                                                     config error: ...");
                                            }
                                        } else {    // 分支3
                                            for (int j = 0; j < argtypes.length; j++) {
                                                Class<?> argclazz = argtypes[j];
                                           //从参数类型列表中查找类型名称为 argument.type 的参数
                                                if (argclazz.getName().equals
                                                    (argument.getType())) {
                                                    appendParameters(map, argument, 
                                                              method.getName() + "." + j);
                                                    if (argument.getIndex() != -1 && 
                                                        argument.getIndex() != j) {
                                                        throw new IllegalArgumentException
                                                           ("argument config error: ...");
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
    
                        // 用户未配置 type 属性,但配置了 index 属性,且 index != -1
                        } else if (argument.getIndex() != -1) {    // 分支4
                            // 添加 ArgumentConfig 字段信息到 map 中
                            appendParameters(map, argument, method.getName() + "." + 
                                             argument.getIndex());
                        } else {
                            throw new IllegalArgumentException
                                ("argument config must set index or type");
                        }
                    }
                }
            }
        }
    
        // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    用伪代码解释一下这几个分支的含义。

    // 获取 ArgumentConfig 列表
    for (遍历 ArgumentConfig 列表) {
        if (type 不为 null,也不为空串) {    // 分支1
            1. 通过反射获取 interfaceClass 的方法列表
            for (遍历方法列表) {
                1. 比对方法名,查找目标方法
            	2. 通过反射获取目标方法的参数类型数组 argtypes
                if (index != -1) {    // 分支2
                    1. 从 argtypes 数组中获取下标 index 处的元素 argType
                    2. 检测 argType 的名称与 ArgumentConfig 中的 type 属性是否一致
                    3. 添加 ArgumentConfig 字段信息到 map 中,或抛出异常
                } else {    // 分支3
                    1. 遍历参数类型数组 argtypes,查找 argument.type 类型的参数
                    2. 添加 ArgumentConfig 字段信息到 map 中
                }
            }
        } else if (index != -1) {    // 分支4
    		1. 添加 ArgumentConfig 字段信息到 map 中
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    appendParameters 这个方法出现的次数比较多,该方法用于将对象字段信息添加到 map 中。实现上则是通过反射获取目标对象的 getter 方法,并调用该方法获取属性值。
    然后再通过 getter 方法名解析出属性名,比如从方法名 getName 中可解析出属性 name。如果用户传入了属性名前缀,此时需要将属性名加入前缀内容。最后将 <属性名,属性值> 键值对存入到 map 中就行了。

  • 相关阅读:
    Android OKHTTP发起请求提示:SSLException: Unable to parse TLS packet header
    Flip技术
    异常校验处理
    MyBatis实现多层级collection嵌套查询
    分布式数据库选型之争:数据库向左,中间件向右
    网络程序通信的流程---socket与TCP的简单认识
    Greenplum数据库故障排查及修复
    抖音数据抓取工具|短视频下载工具|视频内容提取软件
    Springboot实战:员工管理系统
    一文读懂,MES管理系统模块功能
  • 原文地址:https://blog.csdn.net/xianghanscce/article/details/126065517