dubbo之服务提供者配置

号外号外:麻烦点击左侧我正在参加博客之星评选,请您投票,给个五星好评,谢谢大家了!!!

1:写在前面

这篇文章 我们可以知道对于服务提供者,不管是xml方式,还是注解方式,抑或是API方式最直接的方式,直接创建对应的配置类,都会创建对应的配置类,我们以API配置方式来看下具体都需要哪些类:

public class FakeCls {
    public void fakeMethod() {
        // 服务实现
        ProviderService providerService = new ProviderServiceImpl();
        
        // 当前应用配置
        // 相当于xml配置:<dubbo:application name="dongshidaddy-provider" owner="dongshidaddy">
        ApplicationConfig application = new ApplicationConfig();
        application.setName("dongshidaddy-provider");
        application.setOwner("dongshidaddy");
        
        // 连接注册中心配置
        // 相当于xml配置:<dubbo:registry address="zookeeper://192.168.10.119:2181" />
        RegistryConfig registry = new RegistryConfig();
        registry.setAddress("zookeeper://192.168.10.119:2181");
        
        // 服务提供者协议配置
        // 相当于xml配置:<dubbo:protocol name="dubbo" port="20823"/>
        ProtocolConfig protocol = new ProtocolConfig();
        protocol.setName("dubbo");
        protocol.setPort(20824);
        
        // 注意:ServiceConfig为重对象,内部封装了与注册中心的连接,以及开启服务端口
        
        // 服务提供者暴露服务配置
        // 相当于xml配置:
        /*
        <dubbo:service
        interface="dongshi.daddy.service.ProviderService"
        ref="providerService"/>
         */
        ServiceConfig<ProviderService> service = new ServiceConfig<ProviderService>(); // 此实例很重,封装了与注册中心的连接,请自行缓存,否则可能造成内存和连接泄漏
        service.setApplication(application);
        service.setRegistry(registry); // 多个注册中心可以用setRegistries()
        service.setProtocol(protocol); // 多个协议可以用setProtocols()
        service.setInterface(ProviderService.class);
        service.setRef(providerService);
        //        service.setVersion("1.0.0");
        
        // 暴露及注册服务
        service.export();
    }
}

其中的ApplicationConfig,RegistryConfig,ProtocolConfig等还有其他一些的配置类如MethodConfig,ArgumentConfig用来封装对应的信息,还有一个类ServiceConfig,该类比较重要,通过上述指定的各种配置,将相关的信息注册到注册中心,并维护和注册中心的连接等,因此该类较重,需要考虑如何维护,下面我们分别来看下。

2:ApplicationConfig

该配置类是配置服务提供者应用本身的信息(当然也可以配置服务消费者),主要作用就是来唯一标识一个应用,进行用户服务治理和性能优化,对应的xml标签是dubbo:application ,如配置<dubbo:application name="dongshidaddy-provider" owner="dongshidaddy">生成的dubbo url是dubbo://192.168.10.119:20823/dongshi.daddy.service.ProviderService?...&application=dongshidaddy-provider&...&owner=dongshidaddy&...(注意只保留了和示例配置相关的参数值)。

3:ProtocolConfig

用于配置服务暴露使用的协议,如配置<dubbo:protocol name="dubbo" port="20823" accesslog="true"/>生成的dubbo url为dubbo://192.168.10.119:20823/dongshi.daddy.service.ProviderService?SayHello.timeout=2000&accesslog=true&anyhost=true&application=dongshidaddy-provider&bean.name=dongshi.daddy.service.ProviderService&dubbo=2.0.2&generic=false&interface=dongshi.daddy.service.ProviderService&methods=SayHello&owner=dongshidaddy&pid=27040&side=provider×tamp=1637827716422,其中和配置相关的信息为dubbo://192.169.10.119:20823/...?...accesslog=true&...

4:AbstractMethodConfig

用来配置服务类的方法,对应的xml配置是dubbo:method ,如下可能配置:

<fakeRoot>
    <dubbo:service
            interface="dongshi.daddy.service.ProviderService"
            ref="providerService">
        <!-- https://dubbo.apache.org/zh/docs/references/xml/dubbo-method/ -->
        <!-- 2000毫秒方法调用超时 -->
        <dubbo:method name="SayHello" timeout="2000"/>
    </dubbo:service>
</fakeRoot>

意思就是设置方法SayHello的超时时间是2000毫秒,服务消费者获取到这些信息后,如果是在2000毫秒内没有获取到返回结果的话则会抛出异常,生成的dubbo url为dubbo://192.168.10.119:20823/dongshi.daddy.service.ProviderService?SayHello.timeout=2000&...&interface=dongshi.daddy.service.ProviderService&methods=SayHello,可以看到其中多了SayHello.timeout=2000参数,如下是调用超时的异常信息,错误非常详细,值得学习借鉴:

Exception in thread "main" com.alibaba.dubbo.rpc.RpcException: Failed to invoke the method SayHello in the service dongshi.daddy.service.ProviderService. 
Tried 3 times of the providers [192.168.10.119:20823] (1/1) from the registry 192.168.10.119:2181 on the consumer 192.168.10.119 using the dubbo version 2.6.6. 
Last error is: Invoke remote method timeout. method: SayHello, provider: dubbo://192.168.10.119:20823/dongshi.daddy.service.ProviderService?SayHello.timeout=2000&accesslog=true&anyhost=true&application=dongshidaddy-consumer&bean.name=dongshi.daddy.service.ProviderService&check=false&dubbo=2.0.2&generic=false&interface=dongshi.daddy.service.ProviderService&methods=SayHello&owner=dongshidaddy&pid=24212&register.ip=192.168.10.119&remote.timestamp=1637827716422&side=consumer&timestamp=1637828015569,
cause: Waiting server-side response timeout. start time: 2021-11-25 16:13:41.328, end time: 2021-11-25 16:13:43.335, client elapsed: 1 ms, server elapsed: 2006 ms, timeout: 2000 ms, request: Request [id=2, version=2.0.2, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=SayHello, parameterTypes=[class java.lang.String], arguments=[hello], attachments={path=dongshi.daddy.service.ProviderService, interface=dongshi.daddy.service.ProviderService, version=0.0.0}]], channel: /192.168.10.119:40835 -> /192.168.10.119:20823

5:ProviderConfig

对应的xml标签是dubbo:provider ,因为其属性都是有默认值,并没有必填属性,所以可以不配置,如果是在机器多网卡需要设置IP地址,或者需要调整服务线程大小优化性能,限制请求和响应包大小优化网络时可以进行设置,如下是一个可能的配置:<dubbo:provider host="192.168.10.119" threads="50" payload="7388608">,此时生成的dubbo url为dubbo://192.168.10.119:20826/dongshi.daddy.service.ProviderService?...&default.payload=7388608&default.threads=50...

6:ServiceConfig

对应的xml标签是dubbo:service ,我们知道,服务提供者可以有很多的service,每个service都需要提供一套对应的配置,多个service在服务提供者端可能配置如下:

<fakeRoot>
    <dubbo:service interface="dongshi.daddy.service.ProviderService" ref="providerService"/>
    <dubbo:service interface="dongshi.daddy.service.ProviderAnotherService" ref="anotherService"/>
</fakeRoot>

此时在服务消费者的引用配置可能如下:

<fakeRoot>
    <dubbo:reference id="providerService" interface="dongshi.daddy.service.ProviderService"/>
    <dubbo:reference id="anotherService" interface="dongshi.daddy.service.ProviderAnotherService"/>
</fakeRoot>

然后就可以通过如下方式使用了:

public class ConsumerWithMultiServiceMain {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context
                = new ClassPathXmlApplicationContext("consumer-with-multi-service.xml");
        context.start();
        ProviderService providerService = (ProviderService) context.getBean("providerService");
        System.out.println(providerService.SayHello("hello"));
        ProviderAnotherService anotherService = context.getBean("anotherService", ProviderAnotherService.class);
        System.out.println(anotherService.SayHelloAnother("hellooo"));
        System.in.read();

    }
}

同样是从spring的容器中获取注册的spring bean就可以了。

不管是使用API配置方式还是注解配置方式最终都会调用com.alibaba.dubbo.config.ServiceConfig.export方法完成暴漏,开启服务端口,并将信息注册到注册中心,下面我们重点来看下这个过程,是如何通过各种配置来完成暴漏工作的,源码如下:

// com.alibaba.dubbo.config.ServiceConfig.export
public class FakeCls {
    public synchronized void export() {
        // 当配置了<dubbo:provider/>时provider不为null
        if (provider != null) {
            // 定义为:protected Boolean export;
            // 如果是是否暴漏布尔对象为null,则从provider中获取
            // 比如配置<dubbo:provider export="false"/>获取的结果就是false,即服务不会暴漏,不会注册到注册中心,服务消费者也就无法使用
            if (export == null) {
                export = provider.getExport();
            }
            // 定义为:protected Integer delay;
            // 是否延迟发布服务
            if (delay == null) {
                delay = provider.getDelay();
            }
        }
        // 不暴露,直接return
        if (export != null && !export) {
            return;
        }
        // 如果是延迟则使用java.util.concurrent.ScheduledExecutorService类执行延迟加载,否则直接调用doExport方法暴漏服务
        if (delay != null && delay > 0) {
            delayExportExecutor.schedule(new Runnable() {
                @Override
                public void run() {
                    doExport();
                }
            }, delay, TimeUnit.MILLISECONDS);
        } else {
            // 2021-11-26 13:08:09
            doExport();
        }
    }
}

2021-11-26 13:08:09处是暴漏服务,具体参考7:暴漏服务

7:暴漏服务

暴漏服务最终调用的方法为com.alibaba.dubbo.config.ServiceConfig.doExport,源码如下:

每个dubbo:service都会单独调用完成自身服务的暴漏。

public class FakeCls {
    protected synchronized void doExport() {
        // 不可暴漏的情况,什么情况下为true???
        if (unexported) {
            throw new IllegalStateException("Already unexported!");
        }
        // 已经暴漏,直接return
        if (exported) {
            return;
        }
        // 标记暴漏标记为true
        exported = true;
        // 如果是interfaceName没有值,则抛出非法状态异常IllegalStateException
        if (interfaceName == null || interfaceName.length() == 0) {
            throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
        }
        // 2021-11-26 15:40:58
        checkDefault();
        // 获取application,module,registries,monitor,protocol配置对象
        if (provider != null) {
            if (application == null) {
                application = provider.getApplication();
            }
            if (module == null) {
                module = provider.getModule();
            }
            // 注意这里是s,即多个,当定义为:
            /*
            <dubbo:registry address="zookeeper://192.168.10.119:2181" />
            <dubbo:registry address="zookeeper://192.168.10.119:8848" />
            */
            // 则这里的结果就是:
            /*
            registries = {ArrayList@2059}  size = 2
             0 = {RegistryConfig@2451} "<dubbo:registry address="zookeeper://192.168.10.119:2181" id="com.alibaba.dubbo.config.RegistryConfig" />"
             1 = {RegistryConfig@2452} "<dubbo:registry address="zookeeper://192.168.10.119:8848" id="com.alibaba.dubbo.config.RegistryConfig2" />"
            */
            if (registries == null) {
                registries = provider.getRegistries();
            }
            if (monitor == null) {
                monitor = provider.getMonitor();
            }
            // 注意这里是s,即多个,也就是暴漏服务的协议可以有多个,默认是dubbo协议,还支持http,thrift
            if (protocols == null) {
                protocols = provider.getProtocols();
            }
        }
        // 尝试从module中获取registries,和monitor
        if (module != null) {
            if (registries == null) {
                registries = module.getRegistries();
            }
            if (monitor == null) {
                monitor = module.getMonitor();
            }
        }
        // 尝试从application中提取registries和monitor
        if (application != null) {
            if (registries == null) {
                registries = application.getRegistries();
            }
            if (monitor == null) {
                monitor = application.getMonitor();
            }
        }
        // 2021-11-27 16:49:25
        if (ref instanceof GenericService) {
            // 如果是GenericService泛化接口
            interfaceClass = GenericService.class;
            if (StringUtils.isEmpty(generic)) {
                // 将dubbo url参数中的generic设置为true
                generic = Boolean.TRUE.toString();
            }
        } else { // 普通接口的情况
            try {
                // 根据类名称获取对应的Class对象
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            // 检查接口是否有设置的方法,如设置
            /*
            <dubbo:service interface="dongshi.daddy.service.ProviderService" ref="providerService">
              <!-- 设置方法sayHello超时调用时间是2000毫秒 -->
              <dubbo:method name="sayHello" timeout="2000"/>
            </dubbo:service>
            则会检查接口ProviderService中是否有sayHello方法
            */
            checkInterfaceAndMethods(interfaceClass, methods);
            // 检测ref属性对应的spring bean是否实现了interface设置的接口,如设置<dubbo:service interface="dongshi.daddy.service.ProviderService" ref="providerService">
            // 就是检测providerService对应的spring bean是否是dongshi.daddy.service.ProviderService的子类
            checkRef();
            // 因为执行到这里说明是普通的接口,而非泛化接口,所以将dubbo url中的generic参数设置为false
            generic = Boolean.FALSE.toString();
        }
        // 不知道干什么用,忽略先,用到了在看!
        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);
            }
        }
        // 2021-11-29 10:33:45
        if (stub != null) {
            // 如果是配置的是true,即使用stub,则默认使用接口名称+Stub作为Stub实现类,所以我们提供stub实现类是最好以这个规范来命名
            // 不然这种情况是会找不到Stub实现类
            if ("true".equals(stub)) {
                stub = interfaceName + "Stub";
            }
            Class<?> stubClass;
            try {
                stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            // 如果是stub实现类没有实现服务接口,即不是服务接口interfaceClass的子类时,则抛出相关异常,告知必须实现服务接口
            if (!interfaceClass.isAssignableFrom(stubClass)) {
                throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
            }
        }
        // 2021-11-29 10:49:11
        checkApplication();
        // 2021-11-29 11:42:47
        checkRegistry();
        // 2021-11-29 13:03:02
        checkProtocol();
        // 添加外部配置的属性信息到自己
        appendProperties(this);
        // 2021-11-29 13:03:44
        checkStub(interfaceClass);
        // 2021-11-29 15:37:52
        checkMock(interfaceClass);
        // 如果时没有设置path属性,则默认使用接口名称作为接口路径,比如配置了:<dubbo:service interface="dongshi.daddy.service.MyPathService" ref="myPathService" path="toMyPath"/>
        // 则生成的dubbo url为:dubbo://192.168.10.119:20826/toMyPath?...
        // 没有配置的话生成的dubbo url为:dubbo://192.168.10.119:20826/dongshi.daddy.service.MyPathService?...
        if (path == null || path.length() == 0) {
            path = interfaceName;
        }
        // 2021-11-29 16:57:38
        doExportUrls();
        ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
        ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
    }
}

2021-11-26 15:40:58处是设置环境变量,配置文件等外部配置到ProviderConfig中,比如代码System.setProperty("dubbo.provider.owner", "two_month_before_year");,则会设置到属性com.alibaba.dubbo.config.AbstractInterfaceConfig.owner中,设置后如下图:

在这里插入图片描述

关于添加外部属性配置到相关配置类中的详细过程可以参考这篇文章

2021-11-27 16:49:25处是处理泛化接口的情况,普通的服务提供类是实现自定义的接口,而泛化接口是实现GenericService,该接口定义如下:

public interface GenericService {
    Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException;
}

其它的配置过程完全相同,只不过生成的dubbo URL的generic参数值为true,如dubbo://192.168.10.119/dongshi.daddy.MyFooService?...&generic=true&...,如下是服务提供者端和消费者端示例代码:

// 服务提供者
public class ProviderWithGenericInterfaceMain {
    public static void main(String[] args) throws Exception {
        /*
        <!--当前项目在整个分布式架构里面的唯一名称,计算依赖关系的标签-->
            <dubbo:application name="dongshidaddy-provider" owner="dongshidaddy"/>
            <dubbo:registry address="zookeeper://192.168.10.119:2181" />
            <!--当前服务发布所依赖的协议;webserovice、Thrift、Hessain、http-->
            <dubbo:protocol name="dubbo" port="20826" accesslog="true"/>
            <dubbo:service interface="com.alibaba.dubbo.rpc.service.GenericService" ref="genericService"/>
        
            <!--<dubbo:provider export="true"/>-->
            <!--Bean bean定义-->
            <bean id="genericService" class="dongshi.daddy.service.MyGenericServiceImpl"/>
        */
        ClassPathXmlApplicationContext ac
                = new ClassPathXmlApplicationContext("META-INF/spring/provider-with-generic-interface.xml");
        ac.start();
        System.in.read(); // 按任意键退出
    }
}

// 服务消费者
public class ConsumerWithGenericServiceMain {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context
                = new ClassPathXmlApplicationContext("consumer-with-call-generic-interface.xml");
        context.start();
        GenericService genericService = (GenericService) context.getBean("genericService");
        Object o = genericService.$invoke("sayHi", new String[]{"java.lang.String"}, new String[]{"generic word"});
        System.out.println(o);
        System.in.read();
    }
}

2021-11-29 10:33:45处是处理配置了stub 的情况。
2021-11-29 10:49:11处是检查<dubbo:application>的相关配置,并设置外部属性信息,详细参考7.1:checkApplication
2021-11-29 11:42:47处是检查<dubbo:registry>的相关配置,并设置外部属性信息。
2021-11-29 13:03:02处是检查<dubbo:protocol>的相关配置,并设置外部属性信息。
2021-11-29 13:03:44处是检查stub配置,具体参考7.2:checkStub
2021-11-29 15:37:52处是检查mock 配置,这是一种当服务提供者不可用,服务消费者端的一种模拟机制。
2021-11-29 16:57:38处是生成相关的url,并暴漏,详细参考8:doExportUrls

7.1:checkApplication

源码如下:

// com.alibaba.dubbo.config.AbstractInterfaceConfig.checkApplication
public class FakeCls {
    protected void checkApplication() {
        // 向后兼容代码
        if (application == null) {
            String applicationName = ConfigUtils.getProperty("dubbo.application.name");
            if (applicationName != null && applicationName.length() > 0) {
                application = new ApplicationConfig();
            }
        }
        // 没有配置的情况
        if (application == null) {
            throw new IllegalStateException(
                    "No such application config! Please add <dubbo:application name=\"...\" /> to your spring config.");
        }
        // 2021-11-29 11:00:54
        // 追加环境变量,外部配置等信息到ApplicationConfig中
        appendProperties(application);
        // 设置停止服务等待属性
        String wait = ConfigUtils.getProperty(Constants.SHUTDOWN_WAIT_KEY);
        if (wait != null && wait.trim().length() > 0) {
            System.setProperty(Constants.SHUTDOWN_WAIT_KEY, wait.trim());
        } else {
            wait = ConfigUtils.getProperty(Constants.SHUTDOWN_WAIT_SECONDS_KEY);
            if (wait != null && wait.trim().length() > 0) {
                System.setProperty(Constants.SHUTDOWN_WAIT_SECONDS_KEY, wait.trim());
            }
        }
    }
}

2021-11-29 11:00:54处是将环境变量,外部配置等设置到ApplicationConfig中,如代码System.setProperty("dubbo.application.environment", "product");执行后,会将environment属性设置为product,如下图:

在这里插入图片描述

7.2:checkStub

源码如下:

// com.alibaba.dubbo.config.AbstractInterfaceConfig.checkStub
public class FakeCls {
    void checkStub(Class<?> interfaceClass) {
        // 检查local,忽略
        if (ConfigUtils.isNotEmpty(local)) {
            Class<?> localClass = ConfigUtils.isDefault(local) ? ReflectUtils.forName(interfaceClass.getName() + "Local") : ReflectUtils.forName(local);
            if (!interfaceClass.isAssignableFrom(localClass)) {
                throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceClass.getName());
            }
            try {
                ReflectUtils.findConstructor(localClass, interfaceClass);
            } catch (NoSuchMethodException e) {
                throw new IllegalStateException("No such constructor \"public " + localClass.getSimpleName() + "(" + interfaceClass.getName() + ")\" in local implementation class " + localClass.getName());
            }
        }
        if (ConfigUtils.isNotEmpty(stub)) {
            // 如果是配置了true,default,则尝试拼接Stub后缀获取,否则直接获取Class对象
            Class<?> localClass = ConfigUtils.isDefault(stub) ? ReflectUtils.forName(interfaceClass.getName() + "Stub") : ReflectUtils.forName(stub);
            // stub实现类必须是服务接口的子类,否则抛出相关异常信息
            if (!interfaceClass.isAssignableFrom(localClass)) {
                throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceClass.getName());
            }
            try {
                // 判断stub实现类中是否有接收一个服务参数接口的构造函数
                ReflectUtils.findConstructor(localClass, interfaceClass);
            } catch (NoSuchMethodException e) {
                throw new IllegalStateException("No such constructor \"public " + localClass.getSimpleName() + "(" + interfaceClass.getName() + ")\" in local implementation class " + localClass.getName());
            }
        }
    }
}

8:doExportUrls

源码如下:

// com.alibaba.dubbo.config.ServiceConfig.doExportUrls
public class FakeCls {
    private void doExportUrls() {
        // 通过<dubbo:registry>获取注册中心的url地址,如:
        // registry://192.168.10.119:2181/com.alibaba.dubbo.registry.RegistryService?application=dongshidaddy-provider&dubbo=2.0.2&owner=dongshidaddy&pid=17048&registry=zookeeper&timestamp=1638175893723
        List<URL> registryURLs = loadRegistries(true);
        // protocols为<dubbo:protocol>的集合,即暴漏dubbo服务使用的协议集合
        // 如:<dubbo:protocol name="dubbo" port="20826" id="dubbo" />
        for (ProtocolConfig protocolConfig : protocols) {
            // 2021-11-29 17:11:23
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }
}

2021-11-29 17:11:23处是生成dubbo url,并完成暴漏,详细参考8.1:doExportUrlsFor1Protocol

8.1:doExportUrlsFor1Protocol

源码如下:

// com.alibaba.dubbo.config.ServiceConfig.doExportUrlsFor1Protocol
public class FakeCls {
    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        // 获取<dubbo:protocol>配置的协议名称,一般是dubbo,即使用dubbo协议暴漏服务
        String name = protocolConfig.getName();
        // 缺省为dubbo协议
        if (name == null || name.length() == 0) {
            name = "dubbo";
        }
        // 封装用于生成dubbo url参数信息的字典
        Map<String, String> map = new HashMap<String, String>();
        // 代表是服务提供者端,public static final String SIDE_KEY = "side"; public static final String PROVIDER_SIDE = "provider";
        map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
        // 服务提供者的协议版本信息参数,public static final String DUBBO_VERSION_KEY = "dubbo";public static final String DEFAULT_DUBBO_PROTOCOL_VERSION = "2.0.2";
        map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
        // 时间戳参数信息,public static final String TIMESTAMP_KEY = "timestamp";
        map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
        // 服务进程号参数信息(通过jps,java process status,Java进程状态工具查看到的结果)
        // public static final String PID_KEY = "pid";
        if (ConfigUtils.getPid() > 0) {
            map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
        }
        // 追加应用配置数据信息到字典中,用于生成最终的url
        appendParameters(map, application);
        // 追加mudule配置相关参数到字典中,用于生成最终的url
        appendParameters(map, module);
        // 追加provider配置相关参数到字典中,用于生成最终的url
        appendParameters(map, provider, Constants.DEFAULT_KEY);
        // 追加protocol配置相关参数到字典中,用于生成最终的url
        appendParameters(map, protocolConfig);
        // 追加自身,即ServiceConfig配置相关参数到字典中,用于生成最终的url
        appendParameters(map, this);
        // 1:获取方法配置参数信息到字典中,如<dubbo:method name="stubSayHello" timeout="20000">,会追加stubSayHello.timeout=20000,其他同理
        // 2:获取方法的<dubbo:method>的子标签信息,如<dubbo:method><dubbo:argument/></dubbo:method>
        if (methods != null && !methods.isEmpty()) {
            // 循环每一个<dubbo:method>配置获取方法参数信息
            for (MethodConfig method : methods) {
                // 追加在<dubbo:method>中配置的属性信息,如<dubbo:method name="stubSayHello" timeout="20000" retries="10">
                // 则会添加如下参数,"stubSayHello.timeout" -> "20000" "stubSayHello.retries" -> "10"
                appendParameters(map, method, method.getName());
                // 因为retry已经废弃,所以这里如果是添加了的话,移除,并且如果需要的话使用retries代替
                // 即如果是retry="1",则会移除。如果是retry="false",则替换为stubSayHello.retries=0,即不重试
                String retryKey = method.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
                // 获取所有的argument配置,如下配置:
                /*
                <dubbo:method name="stubSayHello" timeout="20000" retries="10">
                    <dubbo:argument index="0" type="java.lang.String"/>
                </dubbo:method>
                则结果是0 = {ArgumentConfig@2462} index = {Integer@2463} 0 type = "java.lang.String" callback = null
                */
                List<ArgumentConfig> arguments = method.getArguments();
                if (arguments != null && !arguments.isEmpty()) {
                    // 循环处理每一个<dubbo:argument>配置,分别都找到目标方法,判断参数类型是否匹配
                    for (ArgumentConfig argument : arguments) {
                        // 如果是配置了type属性,即设置了参数的类型
                        if (argument.getType() != null && argument.getType().length() > 0) { // <dubbo:argument>中配置了type
                            // 获取参数中的所有方法
                            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...else...其实就是处理callback的情况
                                        // 当在<dubbo:argument>中没有设置index属性时,默认值时-1,此时当前遍历的方法中,可能有多个参数都是callback参数,配置了index则只有指定的index的方法参数是callback
                                        if (argument.getIndex() != -1) { // <dubbo:argument>中配置了type,也index
                                            // 2021-11-30 16:33:32
                                            // 如果是参数和目标方法类型匹配,则追加callback等相关参数,如果是参数类型和目标方法不一致,则抛出异常
                                            if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
                                                appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                                            } else {
                                                throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
                                            }
                                        } else { // <dubbo:argument>中配置了type, 没有配置index
                                            // 没有配置index时,可能有多个callback,循环遍历每个参数和<dubbo:argument>参数的type属性设置的callback类型是否一致
                                            // 一致则添加,如方法签名void addListener(String key, CallbackListener listener, CallbackListener listener1);
                                            // dubbo url为:dubbo://192.168.10.119:20826/dongshi.daddy.service.MyCallbackService?...&addListener.1.callback=true&addListener.2.callback=true...
                                            for (int j = 0; j < argtypes.length; j++) {
                                                Class<?> argclazz = argtypes[j];
                                                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 : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        } else if (argument.getIndex() != -1) { // 没有配置type,只配置了index
                            // 添加ArgumentConfig参数到map字典中
                            appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                        } else { // 即没有配置type,也没有配置index,则抛出java.lang.IllegalArgumentException异常,告知需要二者不能同时都不配置
                            throw new IllegalArgumentException("argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
                        }
    
                    }
                }
            } // end of methods for
        }
        // 泛化接口,revision,methods信息
        if (ProtocolUtils.isGeneric(generic)) { 
            map.put(Constants.GENERIC_KEY, generic);
            map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
        } else {
            // 2021-11-30 18:16:26
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put("revision", revision);
            }
            // 拼接接口的方法信息参数,生成的dubbo url可能是:dubbo://192.168.10.119:20826/path?...&methods=hello,addListener...
            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if (methods.length == 0) {
                logger.warn("NO method found in service interface " + interfaceClass.getName());
                map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
            } else {
                map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }
        }
        // 2021-12-01 11:26:45 
        if (!ConfigUtils.isEmpty(token)) {
            // 配置为true,default时,则使用uuid生成随机字符串作为token,否则使用用户设置的值作为token
            if (ConfigUtils.isDefault(token)) {
                map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
            } else {
                map.put(Constants.TOKEN_KEY, token);
            }
        }
        // 使用injvm的情况,即<dubbo:protocol name="injvm" port="20827"/>
        if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
            protocolConfig.setRegister(false);
            map.put("notify", "false");
        }
        // 获取contextPath,类似于web中的context path,即IP端口后的内容,如配置<dubbo:protocol name="dubbo" port="20827" contextpath="studydubbo"/>
        // 则dubbo URL结果是:dubbo://192.168.10.119:20827/studydubbo/dongshi.daddy.service.MyInJvmService?...
        String contextPath = protocolConfig.getContextpath();
        if ((contextPath == null || contextPath.length() == 0) && provider != null) {
            contextPath = provider.getContextpath();
        }
        // 2021-12-01 13:23:31
        String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
        // 2021-12-01 14:16:43
        Integer port = this.findConfigedPorts(protocolConfig, name, map);
        // 调用com.alibaba.dubbo.common.URL构造函数创建Dubbo URL
        URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
        //  这段代码暂时不知道什么意思,debug也进不去
        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .hasExtension(url.getProtocol())) {
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }
        // 暴漏逻辑,暂时忽略
        // .......
    }
}

2021-11-30 16:33:32处是追加<dubbo:argument>参数到字典中,比如如下配置了callback

<dubbo:service interface="dongshi.daddy.service.MyCallbackService" ref="callbackService" connections="1" callbacks="1000">
    <dubbo:method name="addListener">
        <!-- 设置index=1,即第二个参数为回调参数 -->
        <dubbo:argument index="1" callback="true" />
        <!--也可以通过指定类型的方式-->
        <!--<dubbo:argument type="com.demo.CallbackListener" callback="true" />-->
    </dubbo:method>
</dubbo:service>

则生成的dubbo url体现该部分的内容为dubbo://192.168.10.119:20826/dongshi.daddy.service.MyCallbackService?...&addListener.1.callback=true...2021-11-30 18:16:26处是获取revision,该参数有什么作用,目前我也还不知道,详细参考8.2:getVersion2021-12-01 11:26:45处是是判断在<dubbo:service>中是否配置了token ,如果是配置了则设置token信息到map中。2021-12-01 13:23:31处是获取ip地址,详细参考8.3:findConfigedHosts2021-12-01 14:16:43获取端口号,详细参考8.4:findConfigedPorts

8.2:getVersion

源码如下:

// com.alibaba.dubbo.common.Version.getVersion(java.lang.Class<?>, java.lang.String)
public class FakeCls {
    public static String getVersion(Class<?> cls, String defaultVersion) {
        try {
            // 从META-INF的MENIFEST.MF文件中获取Implementation-Version值作为结果
            String version = cls.getPackage().getImplementationVersion();
            // 没有获取到从META-INF的MENIFEST.MF文件中获取Specification-Version值作为结果
            if (version == null || version.length() == 0) {
                version = cls.getPackage().getSpecificationVersion();
            }
            // 2021-11-30 18:58:02
            if (version == null || version.length() == 0) {
                // guess version fro jar file name if nothing's found from MANIFEST.MF
                CodeSource codeSource = cls.getProtectionDomain().getCodeSource();
                if (codeSource == null) {
                    logger.info("No codeSource for class " + cls.getName() + " when getVersion, use default version " + defaultVersion);
                } else {
                    String file = codeSource.getLocation().getFile();
                    if (file != null && file.length() > 0 && file.endsWith(".jar")) {
                        file = file.substring(0, file.length() - 4);
                        int i = file.lastIndexOf('/');
                        if (i >= 0) {
                            file = file.substring(i + 1);
                        }
                        i = file.indexOf("-");
                        if (i >= 0) {
                            file = file.substring(i + 1);
                        }
                        while (file.length() > 0 && !Character.isDigit(file.charAt(0))) {
                            i = file.indexOf("-");
                            if (i >= 0) {
                                file = file.substring(i + 1);
                            } else {
                                break;
                            }
                        }
                        version = file;
                    }
                }
            }
            // return default version if no version info is found
            return version == null || version.length() == 0 ? defaultVersion : version;
        } catch (Throwable e) {
            // return default version when any exception is thrown
            logger.error("return default version, ignore exception " + e.getMessage(), e);
            return defaultVersion;
        }
    }
}

2021-11-30 18:58:02是尝试通过所在jar包的名称解析版本号,如"dubbo-provoder-1.0-SNAPSHOT.jar",生成的dubbo url为:dubbo://192.168.10.119:20826/path?...&revision=1.0-SNAPSHOT&...

8.3:findConfigedHosts

源码如下:

// com.alibaba.dubbo.config.ServiceConfig.findConfigedHosts
public class FakeCls {
    private String findConfigedHosts(ProtocolConfig protocolConfig, List<URL> registryURLs, Map<String, String> map) {
        boolean anyhost = false;
        // 该方法通过如下系统变量获取,首先DUBBO_DUBBO_IP_TO_BIND,获取不到通过DUBBO_DUBBO_IP_TO_BIND获取,最好不设置,让dubbo自动获取
        // 如代码:System.setProperty("DUBBO_DUBBO_IP_TO_BIND", "192.168.11.12");
        String hostToBind = getValueFromConfig(protocolConfig, Constants.DUBBO_IP_TO_BIND);
        // 从相关环境变量中获取了ip地址,则校验其合法性, isInvalidLocalHost代码如下:
        /*
        public static boolean isInvalidLocalHost(String host) {
                return host == null
                        || host.length() == 0
                        || host.equalsIgnoreCase("localhost")
                        || host.equals("0.0.0.0")
                        || (LOCAL_IP_PATTERN.matcher(host).matches()); // private static final Pattern LOCAL_IP_PATTERN = Pattern.compile("127(\\.\\d{1,3}){3}$");
            }
        */
        if (hostToBind != null && hostToBind.length() > 0 && isInvalidLocalHost(hostToBind)) {
            throw new IllegalArgumentException("Specified invalid bind ip from property:" + Constants.DUBBO_IP_TO_BIND + ", value:" + hostToBind);
        }
        // 没有从DUBBO_DUBBO_IP_TO_BIND,DUBBO_IP_TO_BIND环境变量中获取到,则继续查找IP
        if (hostToBind == null || hostToBind.length() == 0) {
            // 从<dubbo:protocol>,即ProtocolConfig中获取
            hostToBind = protocolConfig.getHost();
            // 如果从ProtocolConfig中没有获取到,且配置了<dubbo:provider>则从provider中获取
            if (provider != null && (hostToBind == null || hostToBind.length() == 0)) {
                hostToBind = provider.getHost();
            }
            // 如果是在<dubbo:protocol>,或者是<dubbo:provider>中配置了,则检查其有效性,无效则继续自动获取,如配置为回环地址<dubbo:protocol host="127.0.0.1">就是非法的
            if (isInvalidLocalHost(hostToBind)) {
                // 到这里就根据系统的网卡自动获取了,将使用任意IP地址标记为true
                anyhost = true;
                try {
                    hostToBind = InetAddress.getLocalHost().getHostAddress();
                } catch (UnknownHostException e) {
                    logger.warn(e.getMessage(), e);
                }
                if (isInvalidLocalHost(hostToBind)) {
                    if (registryURLs != null && !registryURLs.isEmpty()) {
                        for (URL registryURL : registryURLs) {
                            if (Constants.MULTICAST.equalsIgnoreCase(registryURL.getParameter("registry"))) {
                                // skip multicast registry since we cannot connect to it via Socket
                                continue;
                            }
                            try {
                                // 使用socket校验IP端口的合法性,可绑定,则是合法的,即使用
                                Socket socket = new Socket();
                                try {
                                    SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
                                    socket.connect(addr, 1000);
                                    hostToBind = socket.getLocalAddress().getHostAddress();
                                    break;
                                } finally {
                                    try {
                                        socket.close();
                                    } catch (Throwable e) {
                                    }
                                }
                            } catch (Exception e) {
                                logger.warn(e.getMessage(), e);
                            }
                        }
                    }
                    if (isInvalidLocalHost(hostToBind)) {
                        hostToBind = getLocalHost();
                    }
                }
            }
        }
        // 将IP放到map参数字典中,public static final String BIND_IP_KEY = "bind.ip";
        map.put(Constants.BIND_IP_KEY, hostToBind);
    
        // 获取注册到注册中心的地址,绑定本机的IP地址和注册到注册中心的地址可能不是同一个
        String hostToRegistry = getValueFromConfig(protocolConfig, Constants.DUBBO_IP_TO_REGISTRY);
        if (hostToRegistry != null && hostToRegistry.length() > 0 && isInvalidLocalHost(hostToRegistry)) {
            throw new IllegalArgumentException("Specified invalid registry ip from property:" + Constants.DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
        } else if (hostToRegistry == null || hostToRegistry.length() == 0) {
            // bind ip is used as registry ip by default
            hostToRegistry = hostToBind;
        }
        // 添加是否为任意IP地址标记到map字典中,public static final String ANYHOST_KEY = "anyhost";,标记IP是否为自动获取的,生成的dubbo url如下:
        // dubbo://192.168.10.119:20826/dongshi.daddy.service.MyStubService?anyhost=true
        map.put(Constants.ANYHOST_KEY, String.valueOf(anyhost));
        return hostToRegistry;
    }
}

8.4:findConfigedPorts

源码如下:

// com.alibaba.dubbo.config.ServiceConfig.findConfigedPorts
class FakeCls {
    private Integer findConfigedPorts(ProtocolConfig protocolConfig, String name, Map<String, String> map) {
        Integer portToBind = null;

        // 从环境变量中获取配置public static final String DUBBO_PORT_TO_BIND = "DUBBO_PORT_TO_BIND";
        // 获取DUBBO_DUBBO_PORT_TO_BIND,后获取DUBBO_PORT_TO_BIND
        String port = getValueFromConfig(protocolConfig, Constants.DUBBO_PORT_TO_BIND);
        // 转int,判断合法性,合法性判断如下:
        /*
        public static boolean isInvalidPort(int port) {
            // private static final int MIN_PORT = 0; private static final int MAX_PORT = 65535;
            return port <= MIN_PORT || port > MAX_PORT;
        }
        */
        portToBind = parsePort(port);

        // 从环境中没有获取到,则继续查找
        if (portToBind == null) {
            // 依次从协议配置和服务提供配置中获取
            portToBind = protocolConfig.getPort();
            if (provider != null && (portToBind == null || portToBind == 0)) {
                portToBind = provider.getPort();
            }
            // 获得默认端口号,如dubbo协议为20880
            final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();
            // 没获取到则使用默认的
            if (portToBind == null || portToBind == 0) {
                portToBind = defaultPort;
            }
            // 默认的都没有
            if (portToBind == null || portToBind <= 0) {
                // 获取随机端口,一般不会执行到这里
                portToBind = getRandomPort(name);
                if (portToBind == null || portToBind < 0) {
                    // 在65535范围内获取一个可用的,一般不会执行到这里
                    portToBind = getAvailablePort(defaultPort);
                    putRandomPort(name, portToBind);
                }
                logger.warn("Use random available port(" + portToBind + ") for protocol " + name);
            }
        }

        // 添加端口信息到map字典中,public static final String BIND_PORT_KEY = "bind.port";
        map.put(Constants.BIND_PORT_KEY, String.valueOf(portToBind));
        // 获取注册到注册中心的端口号
        String portToRegistryStr = getValueFromConfig(protocolConfig, Constants.DUBBO_PORT_TO_REGISTRY);
        Integer portToRegistry = parsePort(portToRegistryStr);
        if (portToRegistry == null) {
            portToRegistry = portToBind;
        }
        return portToRegistry;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值