Dubbo源碼之服務端暴露流程

我們從AnnotationBean#postProcessAfterInitialization爲入口分析
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        if (! isMatchPackage(bean)) {
            return bean;
        }
        Service service = bean.getClass().getAnnotation(Service.class);
        if (service != null) {
            ServiceBean<Object> serviceConfig = new ServiceBean<Object>(service);
            if (void.class.equals(service.interfaceClass())
                    && "".equals(service.interfaceName())) {
                if (bean.getClass().getInterfaces().length > 0) {
                    serviceConfig.setInterface(bean.getClass().getInterfaces()[0]);
                } else {
                    throw new IllegalStateException("Failed to export remote service class " + bean.getClass().getName() + ", cause: The @Service undefined interfaceClass or interfaceName, and the service class unimplemented any interfaces.");
                }
            }
            if (applicationContext != null) {
                serviceConfig.setApplicationContext(applicationContext);
                if (service.registry() != null && service.registry().length > 0) {
                    List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();
                    for (String registryId : service.registry()) {
                        if (registryId != null && registryId.length() > 0) {
                            registryConfigs.add((RegistryConfig)applicationContext.getBean(registryId, RegistryConfig.class));
                        }
                    }
                    serviceConfig.setRegistries(registryConfigs);
                }
                if (service.provider() != null && service.provider().length() > 0) {
                    serviceConfig.setProvider((ProviderConfig)applicationContext.getBean(service.provider(),ProviderConfig.class));
                }
                if (service.monitor() != null && service.monitor().length() > 0) {
                    serviceConfig.setMonitor((MonitorConfig)applicationContext.getBean(service.monitor(), MonitorConfig.class));
                }
                if (service.application() != null && service.application().length() > 0) {
                    serviceConfig.setApplication((ApplicationConfig)applicationContext.getBean(service.application(), ApplicationConfig.class));
                }
                if (service.module() != null && service.module().length() > 0) {
                    serviceConfig.setModule((ModuleConfig)applicationContext.getBean(service.module(), ModuleConfig.class));
                }
                if (service.provider() != null && service.provider().length() > 0) {
                    serviceConfig.setProvider((ProviderConfig)applicationContext.getBean(service.provider(), ProviderConfig.class));
                } else {
                    
                }
                if (service.protocol() != null && service.protocol().length > 0) {
                    List<ProtocolConfig> protocolConfigs = new ArrayList<ProtocolConfig>();
                    for (String protocolId : service.registry()) {
                        if (protocolId != null && protocolId.length() > 0) {
                            protocolConfigs.add((ProtocolConfig)applicationContext.getBean(protocolId, ProtocolConfig.class));
                        }
                    }
                    serviceConfig.setProtocols(protocolConfigs);
                }
                try {
                    serviceConfig.afterPropertiesSet();
                } catch (RuntimeException e) {
                    throw (RuntimeException) e;
                } catch (Exception e) {
                    throw new IllegalStateException(e.getMessage(), e);
                }
            }
            serviceConfig.setRef(bean);
            serviceConfigs.add(serviceConfig);
            serviceConfig.export();
        }
        return bean;
    }

首先得到服務提供類的Service註解,再封裝成ServiceConfig,再注入類的接口等一些配置信息,其實setRef(bean)就是把真正服務實現類的實例傳入,再把ServiceConfig添加到本地的ServiceConfig集合中,最後再調用ServiceConfig的export()暴露服務。

我們先看下封裝成serverConfig的過程。即ServiceBean<Object> serviceConfig = new ServiceBean<Object>(service);具體邏輯在父類ServerConfig的構造方法中。調用了appendAnnotation()方法。

    protected void appendAnnotation(Class<?> annotationClass, Object annotation) {
        Method[] methods = annotationClass.getMethods();
        for (Method method : methods) {
            if (method.getDeclaringClass() != Object.class
                    && method.getReturnType() != void.class
                    && method.getParameterTypes().length == 0
                    && Modifier.isPublic(method.getModifiers())
                    && ! Modifier.isStatic(method.getModifiers())) {
                try {
                    String property = method.getName();
                    if ("interfaceClass".equals(property) || "interfaceName".equals(property)) {
                        property = "interface";
                    }
                    String setter = "set" + property.substring(0, 1).toUpperCase() + property.substring(1);
                    Object value = method.invoke(annotation, new Object[0]);
                    if (value != null && ! value.equals(method.getDefaultValue())) {
                        Class<?> parameterType = ReflectUtils.getBoxedClass(method.getReturnType());
                        if ("filter".equals(property) || "listener".equals(property)) {
                            parameterType = String.class;
                            value = StringUtils.join((String[]) value, ",");
                        } else if ("parameters".equals(property)) {
                            parameterType = Map.class;
                            value = CollectionUtils.toStringMap((String[]) value);
                        }
                        try {
                            Method setterMethod = getClass().getMethod(setter, new Class<?>[] { parameterType });
                            setterMethod.invoke(this, new Object[] { value });
                        } catch (NoSuchMethodException e) {
                            // ignore
                        }
                    }
                } catch (Throwable e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

通過反射調用setter方法,將Service註解上的值注入到新生成的ServiceConfig實例中。(實現很巧妙,讓人不禁多看幾眼)

繼續看ServiceConfig的export()方法。判斷是否有export,並是否延遲暴露,最後調用doExport();又是一堆的檢查,在這最後調用doExportUrls();

    private void doExportUrls() {
        //將註冊的所有url匹配上對應的協議在服務端暴露出來 
       /**
       *  將註冊協議轉化成url 
       *  registry://45.119.68.23:2181/com.alibaba.dubbo.registry.RegistryService? 
       *  application=test-dubbo&dubbo=2.5.3&pid=7648&registry=zookeeper×tamp=1462349748801
       */
        List<URL> registryURLs = loadRegistries(true);
        //配置多通信協議時,都進行暴露  
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

先調用loadRegistries()方法,對應每個註冊中心生成匹配的URL的數據結構(URL即protocol+map形式)

registry://224.5.6.7:1234/com.alibaba.dubbo.registry.RegistryService?application=test-protocol-random-port&dubbo=2.0.0
&pid=4324&registry=dubbo×tamp=1527428955474
上面是registryURLs的一個例子
    protected List<URL> loadRegistries(boolean provider) {
        checkRegistry();
        List<URL> registryList = new ArrayList<URL>();
        if (registries != null && registries.size() > 0) {
            for (RegistryConfig config : registries) {
                String address = config.getAddress();
                if (address == null || address.length() == 0) {
                	address = Constants.ANYHOST_VALUE;
                }
                String sysaddress = System.getProperty("dubbo.registry.address");
                if (sysaddress != null && sysaddress.length() > 0) {
                    address = sysaddress;
                }
                if (address != null && address.length() > 0 
                        && ! RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
                    Map<String, String> map = new HashMap<String, String>();
                    appendParameters(map, application);
                    appendParameters(map, config);
                    map.put("path", RegistryService.class.getName());
                    map.put("dubbo", Version.getVersion());
                    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");
                        }
                    }
                    List<URL> urls = UrlUtils.parseURLs(address, map);
                    for (URL url : urls) {
                        url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
                        url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
                        if ((provider && url.getParameter(Constants.REGISTER_KEY, true))
                                || (! provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {
                            registryList.add(url);
                        }
                    }
                }
            }
        }
        return registryList;
    }

既然有多種通信協議,那麼遍歷所有不同的協議,都進行服務暴露(因爲dubbo支持多通信協議時,都進行暴露)。

    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        //如果沒配置protocol則默認使用dubbo協議 
        String name = protocolConfig.getName();
        if (name == null || name.length() == 0) {
            name = "dubbo";
        }
        //獲取主機地址  
        String host = protocolConfig.getHost();
        if (provider != null && (host == null || host.length() == 0)) {
            host = provider.getHost();
        }
        boolean anyhost = false;
        if (NetUtils.isInvalidLocalHost(host)) {
            anyhost = true;
            try {
                host = InetAddress.getLocalHost().getHostAddress();
            } catch (UnknownHostException e) {
                logger.warn(e.getMessage(), e);
            }
            if (NetUtils.isInvalidLocalHost(host)) {
                if (registryURLs != null && registryURLs.size() > 0) {
                    for (URL registryURL : registryURLs) {
                        try {
                            //創建socket,連接到註冊中心  
                            Socket socket = new Socket();
                            try {
                                SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
                                socket.connect(addr, 1000);
                                //獲取服務所在主機地址 
                                host = socket.getLocalAddress().getHostAddress();
                                break;
                            } finally {
                                try {
                                    socket.close();
                                } catch (Throwable e) {}
                            }
                        } catch (Exception e) {
                            logger.warn(e.getMessage(), e);
                        }
                    }
                }
                if (NetUtils.isInvalidLocalHost(host)) {
                    host = NetUtils.getLocalHost();
                }
            }
        }

        //獲取協議接口號 
        Integer port = protocolConfig.getPort();
        if (provider != null && (port == null || port == 0)) {
            port = provider.getPort();
        }
        final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();
        if (port == null || port == 0) {
            port = defaultPort;
        }
        if (port == null || port <= 0) {
            port = getRandomPort(name);
            if (port == null || port < 0) {
                port = NetUtils.getAvailablePort(defaultPort);
                putRandomPort(name, port);
            }
            logger.warn("Use random available port(" + port + ") for protocol " + name);
        }
        //獲取application、module、provider、protocol、exporter、registries、monitor所有屬性
        Map<String, String> map = new HashMap<String, String>();
        if (anyhost) {
            map.put(Constants.ANYHOST_KEY, "true");
        }
        map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
        map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
        map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
        if (ConfigUtils.getPid() > 0) {
            map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
        }
        /**
         * 將不爲null的配置對象中的屬性設置到 map 中 
         * 即將 xml 配置文件中的配置設置的值全轉化成爲map 
         * {side=provider, application=alijk-dubbo, accepts=1000, 
         * dubbo=2.5.3, threads=100, pid=7236, interface=cn.eoncloud.account.sdk.export.AccountService, 
         * threadpool=fixed, version=1.0.0, timeout=500, anyhost=true, timestamp=1462347843960}
         */
        appendParameters(map, application);
        appendParameters(map, module);
        appendParameters(map, provider, Constants.DEFAULT_KEY);
        appendParameters(map, protocolConfig);
        appendParameters(map, this);
        if (methods != null && methods.size() > 0) {
            for (MethodConfig method : methods) {
                appendParameters(map, method, method.getName());
                String retryKey = method.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
                List<ArgumentConfig> arguments = method.getArguments();
                if (arguments != null && arguments.size() > 0) {
                    for (ArgumentConfig argument : arguments) {
                        //類型自動轉換.
                        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();
                                        //一個方法中單個callback
                                        if (argument.getIndex() != -1 ){
                                            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 attirbute not match :index :"+argument.getIndex() + ", type:" + argument.getType());
                                            }
                                        } else {
                                            //一個方法中多個callback
                                            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 attirbute not match :index :"+argument.getIndex() + ", type:" + argument.getType());
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }else if(argument.getIndex() != -1){
                            appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                        }else {
                            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
        }

        if (ProtocolUtils.isGeneric(generic)) {
            map.put("generic", generic);
            map.put("methods", Constants.ANY_VALUE);
        } else {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put("revision", revision);
            }

            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if(methods.length == 0) {
                logger.warn("NO method found in service interface " + interfaceClass.getName());
                map.put("methods", Constants.ANY_VALUE);
            }
            else {
                map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }
        }
        if (! ConfigUtils.isEmpty(token)) {
            if (ConfigUtils.isDefault(token)) {
                map.put("token", UUID.randomUUID().toString());
            } else {
                map.put("token", token);
            }
        }
        if ("injvm".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();
        }
        /**
         *  將配置信息轉化成 url ,主要根據之前map裏的數據組裝成url 
         *  調用 URL#buildString方法 
         *  dubbo://10.6.13.137:9998/cn.eoncloud.account.sdk.export.AccountService 
         *  ?accepts=1000&anyhost=true&application=test-dubbo&dubbo=2.5.3 
         *  &interface=cn.eoncloud.account.sdk.export.AccountService 
         *  &methods=getAccountName,getAllTest&pid=7236&revision=1.0.0&side=provider 
         *  &threadpool=fixed&threads=100&timeout=500×tamp=1462347843960&version=1.0.0 
         */
        URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);

        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .hasExtension(url.getProtocol())) {
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }

        String scope = url.getParameter(Constants.SCOPE_KEY);
        //配置爲none不暴露
        if (! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {

            //配置不是remote的情況下做本地暴露 (配置爲remote,則表示只暴露遠程服務)
            if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            //如果配置不是local則暴露爲遠程服務.(配置爲local,則表示只暴露本地服務)
            if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) ){
                if (logger.isInfoEnabled()) {
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                }
                if (registryURLs != null && registryURLs.size() > 0
                        && url.getParameter("register", true)) {
                    for (URL registryURL : registryURLs) {
                        url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
                        // 配置的了monitor加載monitor,並給URL設置MONITOR_KEY
                        URL monitorUrl = loadMonitor(registryURL);
                        if (monitorUrl != null) {
                            url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                        }
                        if (logger.isInfoEnabled()) {
                            logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                        }
                        //獲取invoker;根據服務具體實現,實現接口以及regitryUrl從代理工廠ProxyFactory獲取代理Invoker(繼承於AbstractProxyInvoker),它是對具體實現的一種代理
                        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                        //根據協議將invoker暴露成exporter,具體過程是創建一個ExchangeServer,它會綁定一個ServerSocket到配置端口  
                        //protocol.export(invoker); 這個protocol的值爲:RegistryProtocol,也就是暴露會跳到:RegistryProtocol.exprot中去處理
                        Exporter<?> exporter = protocol.export(invoker);
                        //將創建的exporter放進鏈表便於管理  
                        exporters.add(exporter);
                    }
                } else {
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);

                    Exporter<?> exporter = protocol.export(invoker);
                    exporters.add(exporter);
                }
            }
        }
        this.urls.add(url);
    }

這裏主要將相關的配置轉化成map,然後將map跟不同的protocol生成URL(上文介紹過,這是Dubbo特有的數據結構)。最終暴露的dubbo服務也就是這個統一的url,這個url也會註冊到zookeeper的節點上。

dubbo://192.168.67.174:20880/com.alibaba.dubbo.config.api.DemoService?anyhost=true&application=test-protocol-random-port
&bind.ip=192.168.67.174&bind.port=20880&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.config.api.DemoService
&methods=sayName,getUsers,echo,getBox,throwDemoException&pid=12732&side=provider×tamp=1527429609471
上面是拉出來的一個URL的例子,可以看到dubbo爲Protocol,後面跟着host地址,再後面是所需要實現的服務端的接口類型,後面是一個map。
//獲取invoker;根據服務具體實現,實現接口以及regitryUrl從代理工廠ProxyFactory獲取代理Invoker(繼承於AbstractProxyInvoker),它是對具體實現的一種代理
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
//根據協議將invoker暴露成exporter,具體過程是創建一個ExchangeServer,它會綁定一個ServerSocket到配置端口  
//protocol.export(invoker); 這個protocol的值爲:RegistryProtocol,也就是暴露會跳到:RegistryProtocol.exprot中去處理
Exporter<?> exporter = protocol.export(invoker);
//將創建的exporter放進鏈表便於管理  
exporters.add(exporter);

然後根據不同的註冊中心,不同的協議調用,先不同的protocol的export();配置有filter或者listener的情況下,會在這裏產生關於具體服務暴露操作的過濾與監聽。值得一提的是,如果採用的是registry協議,那麼並不會經過ProtocolListenerWrapper的監聽,而是直接進入export()方法開始服務的暴露。這裏protocol是registryProtocol,我們來看下RegistryProtocol.exprot()。註釋強調了,主要做兩件事:1、開啓netty服務端  。2、創建zookeeper服務節點。

    /**
     * 主要做兩件事情:1、開啓netty服務端  。2、創建zookeeper服務節點
     */
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        //export invoker  doLocalExport調用dubboProtocol.export開啓netty服務監聽  
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
        //registry provider
        final Registry registry = getRegistry(originInvoker);
        final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
        // 調用zodoRegister的doRegister 創建zookeeper的服務節點
        registry.register(registedProviderUrl);
        // 訂閱override數據
        // FIXME 提供者訂閱時,會影響同一JVM即暴露服務,又引用同一服務的的場景,因爲subscribed以服務名爲緩存的key,導致訂閱信息覆蓋。
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
        //訂閱
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
        //保證每次export都返回一個新的exporter實例
        return new Exporter<T>() {
            public Invoker<T> getInvoker() {
                return exporter.getInvoker();
            }
            public void unexport() {
            	try {
            		exporter.unexport();
            	} catch (Throwable t) {
                	logger.warn(t.getMessage(), t);
                }
                try {
                	registry.unregister(registedProviderUrl);
                } catch (Throwable t) {
                	logger.warn(t.getMessage(), t);
                }
                try {
                	overrideListeners.remove(overrideSubscribeUrl);
                	registry.unsubscribe(overrideSubscribeUrl, overrideSubscribeListener);
                } catch (Throwable t) {
                	logger.warn(t.getMessage(), t);
                }
            }
        };
    }
首先通過doLocalExport()方法開始本地服務的暴露。
    private <T> ExporterChangeableWrapper<T>  doLocalExport(final Invoker<T> originInvoker){
        String key = getCacheKey(originInvoker);
        ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
        if (exporter == null) {
            synchronized (bounds) {
                exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
                if (exporter == null) {
                    final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
                    //此處protol爲dubboProtocol 
                    // dubboProtocol的export對服務進行暴露,這個export最終目的就是開啓netty的監聽
                    exporter = new ExporterChangeableWrapper<T>((Exporter<T>)protocol.export(invokerDelegete), originInvoker);
                    bounds.put(key, exporter);
                }
            }
        }
        return (ExporterChangeableWrapper<T>) exporter;
    }
先根據invoker獲取提供方的url來得到key,然後根據key來嘗試獲得ExporterChangeableWrapper,如果沒有獲得那麼傳入invoker跟提供方的url來構造InbokerDelegete,再通過dubboProtocol的export對服務進行暴露。我們來看具體方法。
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        URL url = invoker.getUrl();
        
        // export service.
        String key = serviceKey(url);
        DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
        exporterMap.put(key, exporter);
        
        //export an stub service for dispaching event
        Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY,Constants.DEFAULT_STUB_EVENT);
        Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
        if (isStubSupportEvent && !isCallbackservice){
            String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
            if (stubServiceMethods == null || stubServiceMethods.length() == 0 ){
                if (logger.isWarnEnabled()){
                    logger.warn(new IllegalStateException("consumer [" +url.getParameter(Constants.INTERFACE_KEY) +
                            "], has set stubproxy support event ,but no stub methods founded."));
                }
            } else {
                stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
            }
        }

        openServer(url);
        
        return exporter;
    }
先得到服務提供者的url,再根據url構造成server key,再構造DubboExporter並根據key和export存放在exporterMap中。如果是遠程服務並且支持回調,則加入stubServiceMethodsMap中。關鍵是openServer()方法,繼續看。
    private void openServer(URL url) {
        // find server.
        String key = url.getAddress();
        //client 也可以暴露一個只有server可以調用的服務。
        boolean isServer = url.getParameter(Constants.IS_SERVER_KEY,true);
        if (isServer) {
        	ExchangeServer server = serverMap.get(key);
        	if (server == null) {
        		serverMap.put(key, createServer(url));
        	} else {
        		//server支持reset,配合override功能使用
        		server.reset(url);
        	}
        }
    }
先根據server key得到server,如果已經有server(即已經建立過於服務器的連接),那麼直接返回,否則通過createServer()與註冊中心建立網絡連接。並且加入serverMap,下次握有相同key,則直接返回。
    private ExchangeServer createServer(URL url) {
        //默認開啓server關閉時發送readonly事件
        url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
        //默認開啓heartbeat
        url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
        //默認使用netty  
        String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);

        if (str != null && str.length() > 0 && ! ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
            throw new RpcException("Unsupported server type: " + str + ", url: " + url);
        //默認使用dubbo協議編碼  
        url = url.addParameter(Constants.CODEC_KEY, Version.isCompatibleVersion() ? COMPATIBLE_CODEC_NAME : DubboCodec.NAME);
        ExchangeServer server;
        try {
            //HeaderExchangeServer 在此處已經開啓了Netty Server 進行監聽
            // Exchangers.bind(url, requestHandler)  默認爲:HeaderExchanger.bind()
            server = Exchangers.bind(url, requestHandler);
        } catch (RemotingException e) {
            throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
        }
        str = url.getParameter(Constants.CLIENT_KEY);
        if (str != null && str.length() > 0) {
            Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
            if (!supportedTypes.contains(str)) {
                throw new RpcException("Unsupported client type: " + str);
            }
        }
        return server;
    }

一些默認的處理,關鍵是Exchangers.bind()方法,返回了ExchangeServer(介個貌似已經是netty server的監聽服務器,也就是說這裏完成了對網絡監聽的配置與開啓)我們來看下具體的流程。跟進去:

    url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
    return getExchanger(url).bind(url, handler);
    public static Exchanger getExchanger(URL url) {
        String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER);//header
        return getExchanger(type);
    }

可以看到這裏默認是headerExchange,那麼繼續去看它的bind方法

    public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
        //Transporters默認爲NettyTransporter 
        return new HeaderExchangeServer(Transporters.bind(url, 
                new DecodeHandler(new HeaderExchangeHandler(handler))));
    }
我們再看看Transports的bind方法
public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        if (handlers == null || handlers.length == 0) {
            throw new IllegalArgumentException("handlers == null");
        }
        ChannelHandler handler;
        if (handlers.length == 1) {
            handler = handlers[0];
        } else {
            handler = new ChannelHandlerDispatcher(handlers);
        }
        return getTransporter().bind(url, handler);
    }

    public static Transporter getTransporter() {
        return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
    }
通過SPI的方法,獲得transport的適配類,再調用其bind方法,具體默認實現類是NettyTansporter。
    public static final String NAME = "netty";
    
    public Server bind(URL url, ChannelHandler listener) throws RemotingException {
        return new NettyServer(url, listener);
    }

在這裏我們看到其根據url建立NettyServer,並設置了回調listener,就是之前的requestHandler。

NettyServer的父類構造中調用了NettyServer的doOpen()

    protected void doOpen() throws Throwable {
        NettyHelper.setNettyLoggerFactory();
        ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));
        ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));
        //最後一個參數爲 NIO 最大工作線程數  
        ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));
      //netty server 啓動器  
        bootstrap = new ServerBootstrap(channelFactory);
        
        final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
        channels = nettyHandler.getChannels();
        // https://issues.jboss.org/browse/NETTY-365
        // https://issues.jboss.org/browse/NETTY-379
        // final Timer timer = new HashedWheelTimer(new NamedThreadFactory("NettyIdleTimer", true));
        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            public ChannelPipeline getPipeline() {
                NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec() ,getUrl(), NettyServer.this);
                ChannelPipeline pipeline = Channels.pipeline();
                /*int idleTimeout = getIdleTimeout();
                if (idleTimeout > 10000) {
                    pipeline.addLast("timer", new IdleStateHandler(timer, idleTimeout / 1000, 0, 0));
                }*/
                pipeline.addLast("decoder", adapter.getDecoder());
                pipeline.addLast("encoder", adapter.getEncoder());
                pipeline.addLast("handler", nettyHandler);
                return pipeline;
            }
        });
        // bind ; 創建一個綁定到指定地址的新通道,也就是綁定IP、端口供客戶端連接  
        channel = bootstrap.bind(getBindAddress());
    }

這裏就是很熟悉的netty服務端的開啓,其中nettyHandler是處理業務邏輯層,它是處理客戶端連接,消息收發等。

netty服務端開啓後,我們回到HeaderExchangeServer的構造中,得到netty服務器後,開啓心跳監測機制。

        final Registry registry = getRegistry(originInvoker);
        final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
        // 調用zodoRegister的doRegister 創建zookeeper的服務節點
        registry.register(registedProviderUrl);
        // 訂閱override數據
        // FIXME 提供者訂閱時,會影響同一JVM即暴露服務,又引用同一服務的的場景,因爲subscribed以服務名爲緩存的key,
                導致訂閱信息覆蓋。
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
        //訂閱
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);

回到RegistryProtocol.exprot()中繼續,通過getRegistry()獲得註冊中心的實例,會根據配置的registryFactory生成對應的registry實例(這裏是zookeeperRegister)。得到zookeeper註冊中心的URL,通過register()的方式向註冊創建zk節點,之後,向註冊中心訂閱這一服務,以保證註冊數據變動時的自動推送。

getRegistry()使用RegistryFactory的getRegistry(),實際上是調用Zookeeper的ceateRegistry()方法(根據配置,使用zookeeper註冊中心爲例子)。

	public Registry createRegistry(URL url) {
        return new ZookeeperRegistry(url, zookeeperTransporter);
    }

我們繼續來看

    public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
        super(url);
        if (url.isAnyHost()) {
    		throw new IllegalStateException("registry address == null");
    	}
        String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
        if (! group.startsWith(Constants.PATH_SEPARATOR)) {
            group = Constants.PATH_SEPARATOR + group;
        }
        this.root = group;
        zkClient = zookeeperTransporter.connect(url);
        zkClient.addStateListener(new StateListener() {
            public void stateChanged(int state) {
            	if (state == RECONNECTED) {
	            	try {
						recover();
					} catch (Exception e) {
						logger.error(e.getMessage(), e);
					}
            	}
            }
        });
    }

建立了zkClient。

我們再來看registry.register()方法的具體邏輯

    @Override
    public void register(URL url) {
        super.register(url);
        failedRegistered.remove(url);
        failedUnregistered.remove(url);
        try {
            // 向服務器端發送註冊請求
            doRegister(url);
        } catch (Exception e) {
            Throwable t = e;

            // 如果開啓了啓動時檢測,則直接拋出異常
            boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
                    && url.getParameter(Constants.CHECK_KEY, true)
                    && ! Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());
            boolean skipFailback = t instanceof SkipFailbackWrapperException;
            if (check || skipFailback) {
                if(skipFailback) {
                    t = t.getCause();
                }
                throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);
            } else {
                logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);
            }

            // 將失敗的註冊請求記錄到失敗列表,定時重試
            failedRegistered.add(url);
        }
    }
    //zookeeper類
    protected void doRegister(URL url) {
        try {
            zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
        } catch (Throwable e) {
            throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }

可以看到這裏創建了zk節點。


我們繼續回到前面的doExportUrlsFor1Protocol中

//獲取invoker;根據服務具體實現,實現接口以及regitryUrl從代理工廠ProxyFactory獲取代理Invoker(繼承於AbstractProxyInvoker),它是對具體實現的一種代理
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
//根據協議將invoker暴露成exporter,具體過程是創建一個ExchangeServer,它會綁定一個ServerSocket到配置端口  
//protocol.export(invoker); 這個protocol的值爲:RegistryProtocol,也就是暴露會跳到:RegistryProtocol.exprot中去處理
Exporter<?> exporter = protocol.export(invoker);
//將創建的exporter放進鏈表便於管理  
exporters.add(exporter);

我們分析了Protocol.export這個方法,現在本地建立netty server監聽,再將invoker服務以URL的數據格式,在zookeeper註冊中心新建一個zk節點,記錄其中。

那麼具體的服務是如何封裝在invoker中?我們從Invoker<?> invoker = proxyFactory.getInvoker();中入手。默認的是javassistProxyFactory

    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {  
        // TODO Wrapper類不能正確處理帶$的類名  
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);  
        return new AbstractProxyInvoker<T>(proxy, type, url) {  
            @Override  
            protected Object doInvoke(T proxy, String methodName,   
                                      Class<?>[] parameterTypes,   
                                      Object[] arguments) throws Throwable {  
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);  
            }  
        };  
    }  
先通過代理的接口的類名得到wrapper類。

在getWrapper方法中,根據需要包裝的類來WRAPPER_MAP中尋找,如果沒有,則調用makeWrapper,並以需要包裝的類爲key,得到的wrapper爲value存入WRAPPER_MAP中。

makeWrapper()方法根據傳入所要包裝的類動態生成新的包裝類。之後調用了該包裝類的invokerMethod方法。我們來重點看下makeWrapper()的關於invokeMethod()方法的生成部分。

if( hasMethod ){  
    c3.append(" try{");  
}  
for( Method m : methods )  
{  
   if( m.getDeclaringClass() == Object.class ) //ignore Object's method.  
      continue;  
  
   String mn = m.getName();  
   c3.append(" if( \"").append(mn).append("\".equals( $2 ) ");  
          int len = m.getParameterTypes().length;  
          c3.append(" && ").append(" $3.length == ").append(len);  
     
   boolean override = false;  
   for( Method m2 : methods ) {  
      if (m != m2 && m.getName().equals(m2.getName())) {  
         override = true;  
         break;  
      }  
   }  
   if (override) {  
      if (len > 0) {  
         for (int l = 0; l < len; l ++) {  
            c3.append(" && ").append(" $3[").append(l).append("].getName().equals(\"")  
               .append(m.getParameterTypes()[l].getName()).append("\")");  
         }  
      }  
   }  
     
   c3.append(" ) { ");  
     
   if( m.getReturnType() == Void.TYPE )  
      c3.append(" w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");")
        .append(" return null;");  
   else  
      c3.append(" return ($w)w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");");  
  
   c3.append(" }");  
     
   mns.add(mn);  
   if( m.getDeclaringClass() == c )  
      dmns.add(mn);  
   ms.put(ReflectUtils.getDesc(m), m);  
}  
if( hasMethod ){  
    c3.append(" } catch(Throwable e) { " );  
    c3.append("     throw new java.lang.reflect.InvocationTargetException(e); " );  
       c3.append(" }");  
      }  
  
c3.append(" throw new " + NoSuchMethodException.class.getName() + "(\"Not found method \\\"\"+$2+\"\\\" in class " 
    + c.getName() + ".\"); }"); 
通過javassist的方式動態構造字符串構造的方法體,當外部調用被包裝的類的相應的方法,都會在這裏被找到方法並調用。我們來看下構造的invokerMethod()的大致樣子
public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws java.lang.reflect.InvocationTargetException {  
        dubbo.provider.hello.service.impl.HelloWorld w;  
        try {  
            w = ((dubbo.provider.hello.service.impl.HelloServiceImpl) $1);  
        } catch (Throwable e) {  
            throw new IllegalArgumentException(e);  
        }  
        try {  
            if ("helloWorld".equals($2) && $3.length == 0) {  
                w. helloWorld ();  
                return null;  
            }  
   					if ("getName".equals($2) && $3.length == 0) {  
                return ($w)w. getNanme ();  
            }
            if ("setName".equals($2) && $3.length == 1) {  
                return ($w)w. setNanme ($4);  
            }
        } catch (Throwable e) { 
            throw new java.lang.reflect.InvocationTargetException(e);  
        }  
        throw new com.alibaba.dubbo.common.bytecode.NoSuchMethodException("Not found method \"" 
                    + $2 + "\" in class dubbo.provider.hello.service.impl.HelloServiceImpl.");  
    }  
在得到了包裝類之後,我們回到javassistProxyFactory的getInvoker()方法中,返回一個新生成的AbstractProxyInvoker(),重寫了其中的doInvoke()方法,doInvoke方法中調用的是剛纔得到的wrapper的invokeMethod()方法(即通過反射調用了傳入的所要代理的實例的對應傳入的方法名、參數的指定方法)。








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章