「Dubbo源码」服务导出过程分析

Dubbo版本:3.2.0

ps:有点长,忍一下。

Dubbo注册中心介绍

注册中心介绍

随着业务规模的不断扩大,传统的单体应用系统逐渐无法满足庞大的业务需求。因此,微服务架构成为不可避免的选择。然而,微服务之间的有效沟通和协作变得至关重要,而这正是服务注册中心应运而生的背景。

服务注册中心的主要目标是协助不同应用程序和微服务之间建立连接,以便它们可以相互通信和协同工作。可以将其类比为一个信息公告栏,作为一个独立于微服务之外的第三方服务。每当一个微服务上线时,它会主动注册自身的信息到注册中心。这种机制有点类似于贴一张信息卡片在公告栏上,以让其他服务了解自己的存在和可用性。

在需要调用其他服务时,微服务可以从注册中心获取所需服务的信息,这使得服务之间的发现和通信更加高效和灵活。总之,服务注册中心是微服务架构中的一个重要组成部分,促进了服务之间的协作和无缝通信。

Dubbo注册中心官网介绍

Dubbo依赖第三方注册中心组件来协调服务发现过程,服务发现包含提供者、消费者和注册中心三个参与角色,其中,Dubbo 提供者实例注册 URL 地址到注册中心,注册中心负责对数据进行聚合,Dubbo 消费者从注册中心读取地址列表并订阅变更,每当地址列表发生变化,注册中心将最新的列表通知到所有订阅的消费者实例。

  • providers启动时,向 /dubbo/com.foo.BarService/providers 目录下创建节点
  • consumer者启动时: 获取并订阅 /dubbo/com.foo.BarService/providers 下节点。并向 /dubbo/com.foo.BarService/consumers 目录下创建节点
  • 监控中心启动时: 订阅 /dubbo/com.foo.BarService 目录下的所有提供者和消费者 URL 地址。

源码分析部分

整体结构介绍

类图如上,核心类就是RegistryService,定义了注册中心的核心接口,各个子模版也是针对第三方服务各自进行实现。

  • register:服务注册
  • unregister:取消注册
  • subscribe:服务订阅
  • unsubscribe:取消订阅
  • lookup:查询符合条件的已注册数据 核心接口如下
java 复制代码
public interface RegistryService {
    /**
    *注册服务
    * 注册需处理契约:<br>
     * 1. 当URL设置了check=false时,注册失败后不报错,在后台定时重试,否则抛出异常。<br>
     * 2. 当URL设置了dynamic=false参数,则需持久存储,否则,当注册者出现断电等情况异常退出时,需自动删除。<br>
     * 3. 当URL设置了category=overrides时,表示分类存储,缺省类别为providers,可按分类部分通知数据。<br>
     * 4. 当注册中心重启,网络抖动,不能丢失数据,包括断线自动删除数据。<br>
     * 5. 允许URI相同但参数不同的URL并存,不能覆盖。<br>
     * 
     * @param url 注册信息,不允许为空,如:dubbo://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin    
    */
    void register(URL url);

    /**
     * 取消注册服务.
     * 
     * 取消注册需处理契约:<br>
     * 1. 如果是dynamic=false的持久存储数据,找不到注册数据,则抛IllegalStateException,否则忽略。<br>
     * 2. 按全URL匹配取消注册。<br>
     * 
     * @param url 注册信息,不允许为空,如:dubbo://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
     */
    void unregister(URL url);

   /**
     * 订阅服务.
     * 
     * 订阅需处理契约:<br>
     * 1. 当URL设置了check=false时,订阅失败后不报错,在后台定时重试。<br>
     * 2. 当URL设置了category=overrides,只通知指定分类的数据,多个分类用逗号分隔,并允许星号通配,表示订阅所有分类数据。<br>
     * 3. 允许以interface,group,version,classifier作为条件查询,如:interface=com.alibaba.foo.BarService&version=1.0.0<br>
     * 4. 并且查询条件允许星号通配,订阅所有接口的所有分组的所有版本,或:interface=*&group=*&version=*&classifier=*<br>
     * 5. 当注册中心重启,网络抖动,需自动恢复订阅请求。<br>
     * 6. 允许URI相同但参数不同的URL并存,不能覆盖。<br>
     * 7. 必须阻塞订阅过程,等第一次通知完后再返回。<br>
     * 
     * @param url 订阅条件,不允许为空,如:consumer://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
     * @param listener 变更事件监听器,不允许为空
     */
    void subscribe(URL url, NotifyListener listener);

    /**
     * 取消订阅服务.
     * 
     * 取消订阅需处理契约:<br>
     * 1. 如果没有订阅,直接忽略。<br>
     * 2. 按全URL匹配取消订阅。<br>
     * 
     * @param url 订阅条件,不允许为空,如:consumer://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
     * @param listener 变更事件监听器,不允许为空
     */
    void unsubscribe(URL url, NotifyListener listener);

    /**
     * 查询注册列表,与订阅的推模式相对应,这里为拉模式,只返回一次结果。
     * 
     * @see org.apache.dubbo.registry.NotifyListener#notify(List)
     * @param url 查询条件,不允许为空,如:consumer://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
     * @return 已注册信息列表,可能为空,含义同{@link org.apache.dubbo.registry.NotifyListener#notify(List<URL>)}的参数。
     */
    List<URL> lookup(URL url);
}

各个模块也是针对不同的第三方服务有不同的实现,后续也以本人目前使用的Zookeeper为例进行分析

服务注册

服务注册大致分为三个部分

  • 第一部分是参数准备
  • 第二部分是服务导出
  • 第三部分是向注册中心注册。

一、参数准备

作为一个服务提供者,首要任务是明确你能提供哪些服务(接口)。Dubbo框架在Bean创建时会缓存这些信息,以备后续服务的暴露。 当Spring发布结束事件,Dubbo会开启服务注册流程,入口为DefaultModuleDeployer#exportServices,其中包括遍历之前缓存的bean信息以进行服务的发布流程。 整个流程中第一步就是参数准备,在loadRegistries中构建注册中心链接列表,由于元数据中心可能在未来替代注册中心,genCompatibleRegistries默认情况下会添加元注册中心,可以添加register-mode: interface强行指定为注册中心模式

后续我会继续更新元数据中心,想要了解或者共同进步的铁子们可以点个关注,给我个鼓励。

java 复制代码
    public static List<URL> loadRegistries(AbstractInterfaceConfig interfaceConfig, boolean provider) {
        List<URL> registryList = new ArrayList<>();
        ApplicationConfig application = interfaceConfig.getApplication();
        List<RegistryConfig> registries = interfaceConfig.getRegistries();
        if (CollectionUtils.isNotEmpty(registries)) {
            for (RegistryConfig config : registries) {
                // try to refresh registry in case it is set directly by user using config.setRegistries()
                // 尝试刷新注册表,以防用户使用 config.setRegistries() 直接设置注册表
                if (!config.isRefreshed()) {
                    config.refresh();
                }
                String address = config.getAddress();
                if (StringUtils.isEmpty(address)) {
                    // 若 address 为空,则将其设为 0.0.0.0
                    address = ANYHOST_VALUE;
                }
                if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
                    Map<String, String> map = new HashMap<String, String>();
                    //添加属性到map中
                    AbstractConfig.appendParameters(map, application);
                    AbstractConfig.appendParameters(map, config);
                    map.put(PATH_KEY, RegistryService.class.getName());
                    AbstractInterfaceConfig.appendRuntimeParameters(map);
                    if (!map.containsKey(PROTOCOL_KEY)) {
                        map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
                    }
                    // 解析得到URL列表
                    List<URL> urls = UrlUtils.parseURLs(address, map);
                    // 注意此处是ServiceConfigURL
                    for (URL url : urls) {
                        url = URLBuilder.from(url)
                            .addParameter(REGISTRY_KEY, url.getProtocol())
                            .setProtocol(extractRegistryType(url))
                            .setScopeModel(interfaceConfig.getScopeModel())
                            .build();
                        // provider delay register state will be checked in RegistryProtocol#export
                        if (provider || url.getParameter(SUBSCRIBE_KEY, true)) {
                            registryList.add(url);
                        }
                    }
                }
            }
        }

        return genCompatibleRegistries(interfaceConfig.getScopeModel(), registryList, provider);
    }

接下来在doExportUrlsFor1Protocol中构建url,设置url参数。

url是dubbo中很重要的一部分,通过url可让 Dubbo 的各种配置在各个模块之间传递。

java 复制代码
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs, RegisterTypeEnum registerType) {
        //参数准备
        Map<String, String> map = buildAttributes(protocolConfig);

        // remove null key and null value
        map.keySet().removeIf(key -> StringUtils.isEmpty(key) || StringUtils.isEmpty(map.get(key)));
        // init serviceMetadata attachments
        serviceMetadata.getAttachments().putAll(map);
        //构建url
        URL url = buildUrl(protocolConfig, map);

        processServiceExecutor(url);

        exportUrl(url, registryURLs, registerType);

        initServiceMethodMetrics(url);
    }

二、服务导出&服务注册

下面来到导出服务。在这个阶段,Dubbo将服务暴露出来,以使其能够被其他应用程序或服务发现和调用。这个阶段也分为两大部分

  • 第一部分是导出到本地,对应下方的exportLocal
  • 第二部分是导出到远程,对应下方的exportRemote
java 复制代码
private void exportUrl(URL url, List<URL> registryURLs, RegisterTypeEnum registerType) {
        String scope = url.getParameter(SCOPE_KEY);
        // don't export when none is configured
        if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

            // export to local if the config is not remote (export to remote only when config is remote)
            // 如果配置不是远程则导出到本地(仅当配置是远程时导出到远程)
            if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                exportLocal(url);
            }

            // export to remote if the config is not local (export to local only when config is local)
            //如果配置不是本地,则导出到远程(仅当配置是本地时导出到本地)
            if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
                // export to extra protocol is used in remote export
                String extProtocol = url.getParameter("ext.protocol", "");
                List<String> protocols = new ArrayList<>();

                if (StringUtils.isNotBlank(extProtocol)) {
                    // export original url
                    url = URLBuilder.from(url).
                        addParameter(IS_PU_SERVER_KEY, Boolean.TRUE.toString()).
                        removeParameter("ext.protocol").
                        build();
                }

                url = exportRemote(url, registryURLs, registerType);
                //...略

                
                
            }
        }
        this.urls.add(url);
    }

导出到本地|exportLocal

本地导出InjvmProtocol主要是调用远程服务时,远程服务并没有开发完成,使用 injvm 协议在本地实现类似服务,调用此服务时可以调用我们本地的实现服务。 具体可以参考(Dubbo官方文档

此处就是创建InjvmExporter并缓存,无特殊逻辑

swift 复制代码
    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
    }

proxyFactoryprotocolSPI采用的是DubboSPI机制,之前文章已经有所介绍,如果您想了解SPI的工作原理和详细信息,可以点击以下之前的文章SPI

java 复制代码
    private void doExportUrl(URL url, boolean withMetaData) {
        //生成Invoker
        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
        if (withMetaData) {
            invoker = new DelegateProviderMetaDataInvoker(invoker, this);
        }
        //封装成Exporter,此处是InjvmExporter
        Exporter<?> exporter = protocolSPI.export(invoker);
        exporters.add(exporter);
    }

导出到远程|exportRemote

目前情报:

目前已有是两个url,一个是注册中心url,一个是接口url,本地导出已经完成,其他什么都没干

接下来需要将这个服务注册到注册中心,让其他服务能够发现和访问它。接下来,将跟踪服务连接到注册中心的过程。

大体流程分为以下三步

  1. 整合接口URL并向下传递,此处可以看到是把接口url整合进注册中心url中向下传递

  2. 获取注册中心并注册,getRegistry(registryUrl)就是获取对应的注册中心。register(registry, registeredProviderUrl)就是导出到注册中心,在zookeeper中就是创建对应的节点

  1. 创建Netty服务,等待RPC通信。本地服务导出doLocalExport(originInvoker, providerUrl)底层就是创建对应的Netty服务
java 复制代码
  @Override
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        URL registryUrl = getRegistryUrl(originInvoker);
        // url to export locally
        URL providerUrl = getProviderUrl(originInvoker);
        // Subscribe the override data
        //获取订阅 URL
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
        //创建监听器
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        Map<URL, Set<NotifyListener>> overrideListeners = getProviderConfigurationListener(overrideSubscribeUrl).getOverrideListeners();
        overrideListeners.computeIfAbsent(overrideSubscribeUrl, k -> new ConcurrentHashSet<>())
            .add(overrideSubscribeListener);
        providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
        //创建Exporter
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

        // 根据 URL 加载 Registry 实现类,比如 ZookeeperRegistry
        final Registry registry = getRegistry(registryUrl);
        final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);

        // decide if we need to delay publish (provider itself and registry should both need to register)
        boolean register = providerUrl.getParameter(REGISTER_KEY, true) && registryUrl.getParameter(REGISTER_KEY, true);
        if (register) {
            //向注册中心注册
            register(registry, registeredProviderUrl);
        }

        // register stated url on provider model
        registerStatedUrl(registryUrl, registeredProviderUrl, register);


        exporter.setRegisterUrl(registeredProviderUrl);
        exporter.setSubscribeUrl(overrideSubscribeUrl);
        exporter.setNotifyListener(overrideSubscribeListener);
        exporter.setRegistered(register);

        ApplicationModel applicationModel = getApplicationModel(providerUrl.getScopeModel());
        if (applicationModel.modelEnvironment().getConfiguration().convert(Boolean.class, ENABLE_26X_CONFIGURATION_LISTEN, true)) {
            if (!registry.isServiceDiscovery()) {
                // Deprecated! Subscribe to override rules in 2.6.x or before.
                registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
            }
        }

        notifyExport(exporter);
        //Ensure that a new exporter instance is returned every time export
        return new DestroyableExporter<>(exporter);
    }

其中originInvoker也是代理对象,内部wrapper class在文末有

开启本地服务

承接上文的远程导出,其中的一步就是本地导出,就是本地额外开启一个服务,用于RPC通信,这样,可以在不影响原服务的基础上,对外进行RPC通信。此外,本地导出还为您提供了自定义通信协议等灵活性,以满足特定需求和场景的定制化通信要求。

java 复制代码
   private void openServer(URL url) {
        checkDestroyed();
        // find server.
        String key = url.getAddress();
        // client can export a service which only for server to invoke
        boolean isServer = url.getParameter(IS_SERVER_KEY, true);

        if (isServer) {
            ProtocolServer server = serverMap.get(key);
            if (server == null) {
                synchronized (this) {
                    server = serverMap.get(key);
                    if (server == null) {
                        serverMap.put(key, createServer(url));
                        return;
                    }
                }
            }

            // server supports reset, use together with override
            server.reset(url);
        }
    }
        private ProtocolServer createServer(URL url) {
        url = URLBuilder.from(url)
            // send readonly event when server closes, it's enabled by default
            .addParameterIfAbsent(CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString())
            // enable heartbeat by default
            .addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT))
            .addParameter(CODEC_KEY, DubboCodec.NAME)
            .build();

        String transporter = url.getParameter(SERVER_KEY, DEFAULT_REMOTING_SERVER);
        if (StringUtils.isNotEmpty(transporter) && !url.getOrDefaultFrameworkModel().getExtensionLoader(Transporter.class).hasExtension(transporter)) {
            throw new RpcException("Unsupported server type: " + transporter + ", url: " + url);
        }

        ExchangeServer server;
        try {
            server = Exchangers.bind(url, requestHandler);
        } catch (RemotingException e) {
            throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
        }

        transporter = url.getParameter(CLIENT_KEY);
        if (StringUtils.isNotEmpty(transporter) && !url.getOrDefaultFrameworkModel().getExtensionLoader(Transporter.class).hasExtension(transporter)) {
            throw new RpcException("Unsupported client type: " + transporter);
        }

        DubboProtocolServer protocolServer = new DubboProtocolServer(server);
        loadServerProperties(protocolServer);
        return protocolServer;
    }

反编译的文件

以下是反编译后的一些class文件,可略过

  • ProxyFactory
java 复制代码
public class ProxyFactory$Adaptive
implements ProxyFactory {
    //**
    public Invoker getInvoker(Object object, Class clazz, URL uRL) throws RpcException {
        if (uRL == null) {
            throw new IllegalArgumentException("url == null");
        }
        URL uRL2 = uRL;
        String string = uRL2.getParameter("proxy", "javassist");
        if (string == null) {
            throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (").append(uRL2.toString()).append(") use keys([proxy])").toString());
        }
        ScopeModel scopeModel = ScopeModelUtil.getOrDefault(uRL2.getScopeModel(), ProxyFactory.class);
        ProxyFactory proxyFactory = scopeModel.getExtensionLoader(ProxyFactory.class).getExtension(string);
        return proxyFactory.getInvoker(object, clazz, uRL);
    }

    public Object getProxy(Invoker invoker, boolean bl) throws RpcException {
        if (invoker == null) {
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        }
        if (invoker.getUrl() == null) {
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        }
        URL uRL = invoker.getUrl();
        String string = uRL.getParameter("proxy", "javassist");
        if (string == null) {
            throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (").append(uRL.toString()).append(") use keys([proxy])").toString());
        }
        ScopeModel scopeModel = ScopeModelUtil.getOrDefault(uRL.getScopeModel(), ProxyFactory.class);
        ProxyFactory proxyFactory = scopeModel.getExtensionLoader(ProxyFactory.class).getExtension(string);
        return proxyFactory.getProxy(invoker, bl);
    }

    public Object getProxy(Invoker invoker) throws RpcException {
        if (invoker == null) {
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        }
        if (invoker.getUrl() == null) {
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        }
        URL uRL = invoker.getUrl();
        String string = uRL.getParameter("proxy", "javassist");
        if (string == null) {
            throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (").append(uRL.toString()).append(") use keys([proxy])").toString());
        }
        ScopeModel scopeModel = ScopeModelUtil.getOrDefault(uRL.getScopeModel(), ProxyFactory.class);
        ProxyFactory proxyFactory = scopeModel.getExtensionLoader(ProxyFactory.class).getExtension(string);
        return proxyFactory.getProxy(invoker);
    }
}
  • protocolSPI
java 复制代码
public class Protocol$Adaptive
implements Protocol {
    public List getServers() {
        throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    public Invoker refer(Class clazz, URL uRL) throws RpcException {
        String string;
        if (uRL == null) {
            throw new IllegalArgumentException("url == null");
        }
        URL uRL2 = uRL;
        String string2 = string = uRL2.getProtocol() == null ? "dubbo" : uRL2.getProtocol();
        if (string == null) {
            throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (").append(uRL2.toString()).append(") use keys([protocol])").toString());
        }
        ScopeModel scopeModel = ScopeModelUtil.getOrDefault(uRL2.getScopeModel(), Protocol.class);
        Protocol protocol = scopeModel.getExtensionLoader(Protocol.class).getExtension(string);
        return protocol.refer(clazz, uRL);
    }

    @Override
    public void destroy() {
        throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    @Override
    public int getDefaultPort() {
        throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    //**
    public Exporter export(Invoker invoker) throws RpcException {
        String string;
        if (invoker == null) {
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        }
        if (invoker.getUrl() == null) {
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        }
        URL uRL = invoker.getUrl();
        String string2 = string = uRL.getProtocol() == null ? "dubbo" : uRL.getProtocol();
        if (string == null) {
            throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (").append(uRL.toString()).append(") use keys([protocol])").toString());
        }
        ScopeModel scopeModel = ScopeModelUtil.getOrDefault(uRL.getScopeModel(), Protocol.class);
        Protocol protocol = scopeModel.getExtensionLoader(Protocol.class).getExtension(string);
        return protocol.export(invoker);
    }
}
  • wrapper
java 复制代码
public class MemberServiceImplDubboWrap0
extends Wrapper
implements ClassGenerator.DC {
    public static String[] pns;
    public static Map pts;
    public static String[] mns;
    public static String[] dmns;
    public static Class[] mts0;

    @Override
    public String[] getPropertyNames() {
        return pns;
    }

    @Override
    public boolean hasProperty(String string) {
        return pts.containsKey(string);
    }

    public Class getPropertyType(String string) {
        return (Class)pts.get(string);
    }

    @Override
    public String[] getMethodNames() {
        return mns;
    }

    @Override
    public String[] getDeclaredMethodNames() {
        return dmns;
    }

    @Override
    public void setPropertyValue(Object object, String string, Object object2) {
        try {
            MemberServiceImpl memberServiceImpl = (MemberServiceImpl)object;
        }
        catch (Throwable throwable) {
            throw new IllegalArgumentException(throwable);
        }
        throw new NoSuchPropertyException(new StringBuffer().append("Not found property \"").append(string).append("\" field or setter method in class com.mountain.monk.service.impl.MemberServiceImpl.").toString());
    }

    @Override
    public Object getPropertyValue(Object object, String string) {
        MemberServiceImpl memberServiceImpl;
        try {
            memberServiceImpl = (MemberServiceImpl)object;
        }
        catch (Throwable throwable) {
            throw new IllegalArgumentException(throwable);
        }
        if (string.equals("user")) {
            return memberServiceImpl.getUser();
        }
        throw new NoSuchPropertyException(new StringBuffer().append("Not found property \"").append(string).append("\" field or getter method in class com.mountain.monk.service.impl.MemberServiceImpl.").toString());
    }

    public Object invokeMethod(Object object, String string, Class[] classArray, Object[] objectArray) throws InvocationTargetException {
        MemberServiceImpl memberServiceImpl;
        try {
            memberServiceImpl = (MemberServiceImpl)object;
        }
        catch (Throwable throwable) {
            throw new IllegalArgumentException(throwable);
        }
        try {
            if ("getUser".equals(string) && classArray.length == 0) {
                return memberServiceImpl.getUser();
            }
        }
        catch (Throwable throwable) {
            throw new InvocationTargetException(throwable);
        }
        throw new NoSuchMethodException(new StringBuffer().append("Not found method \"").append(string).append("\" in class com.mountain.monk.service.impl.MemberServiceImpl.").toString());
    }
}
相关推荐
用户298698530143 分钟前
.NET 文档自动化:Spire.Doc 设置奇偶页页眉/页脚的最佳实践
后端·c#·.net
序安InToo34 分钟前
第6课|注释与代码风格
后端·操作系统·嵌入式
xyy12334 分钟前
C#: Newtonsoft.Json 到 System.Text.Json 迁移避坑指南
后端
洋洋技术笔记37 分钟前
Spring Boot Web MVC配置详解
spring boot·后端
JxWang0537 分钟前
VS Code 配置 Markdown 环境
后端
navms40 分钟前
搞懂线程池,先把 Worker 机制啃明白
后端
JxWang0540 分钟前
离线数仓的优化及重构
后端
Nyarlathotep011341 分钟前
gin01:初探gin的启动
后端·go
JxWang0542 分钟前
安卓手机配置通用多屏协同及自动化脚本
后端
JxWang0543 分钟前
Windows Terminal 配置 oh-my-posh
后端