一 概述

    上个章节已经描述了dubbo发布一个服务,但具体是如何发布服务只是粗略的描述了下,这里将深入描述服务发布时怎么样开启socket监听,即启动netty服务。

二 开启netty服务


上一节发布服务的重点入口代码如下

[java] view plain copy




1. //通过proxyFactory对象生成接口实现类代理对象Invoker  
2. Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));  
3. //将Invoker对象封装到protocol协议对象中,同时开启socket服务监听端口,这里socket通信是使用netty框架来处理的  
4. Exporter<?> exporter = protocol.export(invoker);

先看第一行代码,这里proxyFactory对象为JavassistProxyFactory的实例对象,进入到getInvoker方法。第一个参数是接口的实现对象,第二个参数是即将发布的接口Class,第三个参数是发布协议的URL。查看方法

[java] view plain copy




1. public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {  
2. return new AbstractProxyInvoker<T>(proxy, type, url) {  
3. @Override  
4. protected Object doInvoke(T proxy, String methodName,   
5.                                   Class<?>[] parameterTypes,   
6. throws Throwable {  
7.             Method method = proxy.getClass().getMethod(methodName, parameterTypes);  
8. return method.invoke(proxy, arguments);  
9.         }  
10.     };  
11. }


细看方法内代码,新建了一个抽象类AbstractProxyInvoker,并实现了抽象方法doInvoke。doInvoke中通过反射机制执行要调用的方法。有点基础的一看就知道是getInvoker方法其实反馈的是接口实现类对象的代理对象。

再细看Exporter<?> exporter = protocol.export(invoker);代码 。这里protocol默认当做是DubboProtocol类。

[java] view plain copy




1. /**
2.      *
3.      * @param invoker 服务的执行体
4.      * @param <T>
5.      * @return
6.      * @throws RpcException
7.      */  
8. public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {  
9.         URL url = invoker.getUrl();  
10.           
11. // export service.  
12. //根据URL生成一个唯一key值  
13. new DubboExporter<T>(invoker, key, exporterMap);  
14. //通过map保存当前发布的invoker对象,key为拼装的唯一字符串  
15.           
16. //export an stub service for dispaching event  
17.         Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY,Constants.DEFAULT_STUB_EVENT);  
18. false);  
19. if (isStubSupportEvent && !isCallbackservice){  
20.             String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);  
21. if (stubServiceMethods == null || stubServiceMethods.length() == 0 ){  
22. if (logger.isWarnEnabled()){  
23. new IllegalStateException("consumer [" +url.getParameter(Constants.INTERFACE_KEY) +  
24. "], has set stubproxy support event ,but no stub methods founded."));  
25.                 }  
26. else {  
27.                 stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);  
28.             }  
29.         }  
30.   
31. //开启netty服务  
32.   
33. // modified by lishen  
34.         optimizeSerialization(url);  
35.   
36. return exporter;  
37.     }

这个方法将传入的Invoker对象封装到DubboExporter对象中,并生成了唯一的key值。同时将key与DubboExporter对象关联保存进入exporterMap中,它是一个支持高并发的ConcurrentHashMap类。当客户端做远程请求服务时,就是根据key值从这个MAP中取出的真正接口实现对象来响应客户端的请求。在后面的代码分析中会体现出来。我们跟进到openServer(url);代码中

[java] view plain copy




1. /**
2.     * 开启netty服务器监听,并保存netty服务对象到map中
3.     * @param url
4.     */  
5. private void openServer(URL url) {  
6. // find server.  
7.        String key = url.getAddress();  
8. //client 也可以暴露一个只有server可以调用的服务。  
9. boolean isServer = url.getParameter(Constants.IS_SERVER_KEY,true);  
10. if (isServer) {  
11.         ExchangeServer server = serverMap.get(key);  
12. if (server == null) {  
13.             serverMap.put(key, createServer(url));  
14. else {  
15. //server支持reset,配合override功能使用  
16.             server.reset(url);  
17.         }  
18.        }  
19.    }


首先判断serverMap中是否已经包含了当前服务的ExchangeServer对象,如果没有调用createServer(url)创建一个并保存到serverMap中。继续跟进到createServer中,在这里调用了Exchangers类的静态方法bind创建了一个ExchangeServer对象,并返回出去了。注意bind方法的两个参数,第一个是URL很熟悉对吧!就不细说了,关键是第二个参数requestHandler,它是ExchangeHandlerAdapter类。它重写了很多父接口中的方法。里面重写了一个received方法,这个就是netty框架在接收到客户端请求以后响应处理的入口。具体处理细节在后面分析。这里继续往下看是怎么启动netty服务的。


[java] view plain copy




1. private ExchangeServer createServer(URL url) {  
2. //默认开启server关闭时发送readonly事件  
3.       url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());  
4. //默认开启heartbeat,最近心跳机制,根据代码应该是设置心跳时间吧  
5.       url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));  
6.       String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);  
7.   
8. if (str != null && str.length() > 0 && ! ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))  
9. throw new RpcException("Unsupported server type: " + str + ", url: " + url);  
10.   
11.       url = url.addParameter(Constants.CODEC_KEY, Version.isCompatibleVersion() ? COMPATIBLE_CODEC_NAME : DubboCodec.NAME);  
12.       ExchangeServer server;  
13. try {  
14.           server = Exchangers.bind(url, requestHandler);  
15. catch (RemotingException e) {  
16. throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);  
17.       }  
18.       str = url.getParameter(Constants.CLIENT_KEY);  
19. if (str != null && str.length() > 0) {  
20. class).getSupportedExtensions();  
21. if (!supportedTypes.contains(str)) {  
22. throw new RpcException("Unsupported client type: " + str);  
23.           }  
24.       }  
25. return server;  
26.   }


以下是Exchangers类的静态方法bind的所有处理,getExchanger方法最终返回了HeaderExchanger对象。

[java] view plain copy





    1. public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {  
    2. if (url == null) {  
    3. throw new IllegalArgumentException("url == null");  
    4.        }  
    5. if (handler == null) {  
    6. throw new IllegalArgumentException("handler == null");  
    7.        }  
    8. "exchange");  
    9. return getExchanger(url).bind(url, handler);  
    10.    }  
    11.   
    12. public static Exchanger getExchanger(URL url) {  
    13.        String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER);  
    14. return getExchanger(type);  
    15.    }  
    16.   
    17. public static Exchanger getExchanger(String type) {  
    18. return ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type);  
    19.    }


    HeaderExchanger类中的bind方法代码如下,细读感觉有点复杂哦!不是太好理解。这不就是装饰器模式么!说实话刚开始看的时候确实很懵逼!感觉乱糟糟的,方法调用就这么跳来跳去,头都晕乎了。将dubbo协议的handler对象最终包装成了DecodeHandler对象,并传入到了Transporters类的bind方法中。

    [java] view plain copy





    1. public class HeaderExchanger implements Exchanger {     
    2.   
    3. public static final String NAME = "header";  
    4. public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {  
    5. return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))));  
    6.     }  
    7.   
    8. public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {  
    9. return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));//装饰器模式处理  
    10.     }  
    11.   
    12. }


    继续跟进Transporters类的bind方法如下。总体思路就是获取Transporter接口的具体实现类,然后调用该实现的bind方法。它有MinaTransporter,NettyTransporter,GrizzlyTransporter三种实现类,这里我就默认使用实现类NettyTransporter了。

    [java] view plain copy




    1. public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {  
    2. if (url == null) {  
    3. throw new IllegalArgumentException("url == null");  
    4.         }  
    5. if (handlers == null || handlers.length == 0) {  
    6. throw new IllegalArgumentException("handlers == null");  
    7.         }  
    8.         ChannelHandler handler;  
    9. if (handlers.length == 1) {  
    10. 0];  
    11. else {  
    12. new ChannelHandlerDispatcher(handlers);  
    13.         }  
    14. return getTransporter().bind(url, handler);  
    15.     }  
    16.       
    17. public static Transporter getTransporter() {  
    18. return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();  
    19.     }

    NettyTransporter类的源码如下。关注下bind方法,新建了一个NettyServer对象。是不是感觉离netty越来越近了。

    [java] view plain copy




    1. public class NettyTransporter implements Transporter {  
    2.   
    3. public static final String NAME = "netty";  
    4.       
    5. public Server bind(URL url, ChannelHandler listener) throws RemotingException {  
    6. return new NettyServer(url, listener);  
    7.     }  
    8.   
    9. public Client connect(URL url, ChannelHandler listener) throws RemotingException {  
    10. return new NettyClient(url, listener);  
    11.     }  
    12.   
    13. }

    在NettyServer类中重点关注两个方法doOpen(它重写了抽象类父类的AbstractServer中的doOpen抽象方法)和它的构造函数。

    [java] view plain copy




    1. public NettyServer(URL url, ChannelHandler handler) throws RemotingException{  
    2. super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));  
    3.     }  
    4.   
    5. @Override  
    6. protected void doOpen() throws Throwable {  
    7.         NettyHelper.setNettyLoggerFactory();  
    8. //启动一个netty服务对象,netty的固定写法不理解可以查查netty资料  
    9. new NamedThreadFactory("NettyServerBoss", true));  
    10. new NamedThreadFactory("NettyServerWorker", true));  
    11. new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));  
    12. new ServerBootstrap(channelFactory);  
    13.   
    14. //这里有点难理解,新建了一个NettyHandler对象,传入了一个this。这个this不就是NettyServer对象么?有点奇怪哈,注意当前类的构造函数调用了super方法,  
    15. // 将dubbo协议的专用handler对象赋值给了当前类的父类AbstractPeer的handler属性,不太理解为何要这么写。但最终将NettyServer这个对象设置为netty的处理器了  
    16. final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);  
    17.         channels = nettyHandler.getChannels();  
    18. // https://issues.jboss.org/browse/NETTY-365  
    19. // https://issues.jboss.org/browse/NETTY-379  
    20. // final Timer timer = new HashedWheelTimer(new NamedThreadFactory("NettyIdleTimer", true));  
    21. new ChannelPipelineFactory() {  
    22. public ChannelPipeline getPipeline() {  
    23. new NettyCodecAdapter(getCodec() ,getUrl(), NettyServer.this);  
    24.                 ChannelPipeline pipeline = Channels.pipeline();  
    25. /*int idleTimeout = getIdleTimeout();
    26.                 if (idleTimeout > 10000) {
    27.                     pipeline.addLast("timer", new IdleStateHandler(timer, idleTimeout / 1000, 0, 0));
    28.                 }*/  
    29. "decoder", adapter.getDecoder());  
    30. "encoder", adapter.getEncoder());  
    31. "handler", nettyHandler);//设置netty接受客户端请求后的处理器  
    32. return pipeline;  
    33.             }  
    34.         });  
    35. // bind  
    36.         channel = bootstrap.bind(getBindAddress());  
    37.     }


    看到这里是不是在想是什么时候调用doOpen方法开始启动netty服务的呢。我们查看构造函数跟进super方法的处理。查看父类AbstractServer中的构造函数,细看里面执行了doOpen方法,根据抽象模版方法模式,其实调用的是子类的doOpen方法。到此已经将netty服务开启啦!

    [java] view plain copy





      1. public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {  
      2. super(url, handler);  
      3.         localAddress = getUrl().toInetSocketAddress();  
      4. false)   
      5.                         || NetUtils.isInvalidLocalHost(getUrl().getHost())   
      6.                         ? NetUtils.ANYHOST : getUrl().getHost();  
      7. new InetSocketAddress(host, getUrl().getPort());  
      8. this.accepts = url.getParameter(Constants.ACCEPTS_KEY, Constants.DEFAULT_ACCEPTS);  
      9. this.idleTimeout = url.getParameter(Constants.IDLE_TIMEOUT_KEY, Constants.DEFAULT_IDLE_TIMEOUT);  
      10. try {  
      11.             doOpen();  
      12. if (logger.isInfoEnabled()) {  
      13. "Start " + getClass().getSimpleName() + " bind " + getBindAddress() + ", export " + getLocalAddress());  
      14.             }  
      15. catch (Throwable t) {  
      16. throw new RemotingException(url.toInetSocketAddress(), null, "Failed to bind " + getClass().getSimpleName()   
      17. " on " + getLocalAddress() + ", cause: " + t.getMessage(), t);  
      18.         }  
      19.   
      20. class)  
      21.                 .getDefaultExtension().get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort()));  
      22.     }




      三 响应请求

      前面段落已经讲解过设置netty响应客户端的处理器是在NettyServer类的doOpen方法中设置的,如代码:


      [java] view plain copy



      1. final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);

      查看NettyHandler类它继承了netty框架的SimpleChannelHandler类,重写了messageReceived方法。这里messageReceived方法就是接收到客户端请求后参数后处理请求入口处。代码如下:


      [java] view plain copy



      1. @Override  
      2. public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {  
      3.         NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);  
      4. try {  
      5.             handler.received(channel, e.getMessage());  
      6. finally {  
      7.             NettyChannel.removeChannelIfDisconnected(ctx.getChannel());  
      8.         }  
      9.     }

      执行了handler的received方法,这里执行的received方法其实就是DubboProtocol类中创建的requestHandler对象,它针对dubbo协议的处理器都封装在了DubboProtocol类中。是不是感觉很乱received方法的最终执行怎么跑到这里面去了。

      因为在启动netty服务的时候,就将requestHandler对象经过层层包装传递给了NettyServer,再通过NettyServer类的构造函数将它保存到了NettyServer类的终极父类AbstractPeer的handler属性上,AbstractPeer类又实现了ChannelHandler接口,重写了received方法。

      所以当netty框架接收到请求时执行messageReceived方法里面的handler.received(channel, e.getMessage()); ,其实执行的是AbstractPeer类的received方法,received然后里面又执行了handler.received(ch, msg); 这里的handler就是DubboProtocol类中创建的requestHandler对象。

      感觉特别绕对不对,刚开始也是整理好了几遍才搞清楚里面的流程。


      接着继续看duobbo协议类DubboProtocol中的requestHandler是怎么处理请求的。我们重点看它重写的方法received入口,调用了reply方法,首先将参数message参数转换成了Invocation对象,Invocation封装了客户端的请求信息。

      1.根据客户端的请求信息找打响应客户端的Invoker对象

      2.检测客户端请求的方法,服务端是否存在

      3.执行invoker对象的invoke方法,执行完成返回数据给客户端


      [java] view plain copy



      1. //netty响应客户端请求处理对象  
      2. private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {  
      3.          
      4. public Object reply(ExchangeChannel channel, Object message) throws RemotingException {  
      5. if (message instanceof Invocation) {  
      6.                Invocation inv = (Invocation) message;  
      7. //根据客户端请求信息组装key值从exporterMap取出真正的接口实现类对象  
      8. //如果是callback 需要处理高版本调用低版本的问题  
      9. if (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))){  
      10. "methods");  
      11. boolean hasMethod = false;  
      12. if (methodsStr == null || methodsStr.indexOf(",") == -1){  
      13.                        hasMethod = inv.getMethodName().equals(methodsStr);  
      14. else {  
      15. //循环验证客户端请求的方法,服务端是否存在对应的实现  
      16. ",");  
      17. for (String method : methods){  
      18. if (inv.getMethodName().equals(method)){  
      19. true;  
      20. break;  
      21.                            }  
      22.                        }  
      23.                    }  
      24. //不存在请求方法实现,返回空或抛出异常  
      25. if (!hasMethod){  
      26. new IllegalStateException("The methodName "+inv.getMethodName()+" not found in callback service interface ,invoke will be ignored. please update the api interface. url is:" + invoker.getUrl()) +" ,invocation is :"+inv );  
      27. return null;  
      28.                    }  
      29.                }  
      30.                RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());  
      31. return invoker.invoke(inv);//执行invoer响应客户端请求  
      32.            }  
      33. throw new RemotingException(channel, "Unsupported request: " + message == null ? null : (message.getClass().getName() + ": " + message) + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());  
      34.        }  
      35.   
      36. /**
      37.         * netty接受客户端请求参数入口
      38.         * @param channel
      39.         * @param message
      40.         * @throws RemotingException
      41.         */  
      42. @Override  
      43. public void received(Channel channel, Object message) throws RemotingException {  
      44. if (message instanceof Invocation) {  
      45. //执行服务端处理并返回响应消息  
      46. else {  
      47. super.received(channel, message);  
      48.            }  
      49.        }  
      50.   
      51. @Override  
      52. public void connected(Channel channel) throws RemotingException {  
      53.            invoke(channel, Constants.ON_CONNECT_KEY);  
      54.        }  
      55.   
      56. @Override  
      57. public void disconnected(Channel channel) throws RemotingException {  
      58. if(logger.isInfoEnabled()){  
      59. "disconected from "+ channel.getRemoteAddress() + ",url:" + channel.getUrl());  
      60.            }  
      61.            invoke(channel, Constants.ON_DISCONNECT_KEY);  
      62.        }  
      63.          
      64. private void invoke(Channel channel, String methodKey) {  
      65.            Invocation invocation = createInvocation(channel, channel.getUrl(), methodKey);  
      66. if (invocation != null) {  
      67. try {  
      68.                    received(channel, invocation);  
      69. catch (Throwable t) {  
      70. "Failed to invoke event method " + invocation.getMethodName() + "(), cause: " + t.getMessage(), t);  
      71.                }  
      72.            }  
      73.        }  
      74.          
      75. private Invocation createInvocation(Channel channel, URL url, String methodKey) {  
      76.            String method = url.getParameter(methodKey);  
      77. if (method == null || method.length() == 0) {  
      78. return null;  
      79.            }  
      80. new RpcInvocation(method, new Class<?>[0], new Object[0]);  
      81.            invocation.setAttachment(Constants.PATH_KEY, url.getPath());  
      82.            invocation.setAttachment(Constants.GROUP_KEY, url.getParameter(Constants.GROUP_KEY));  
      83.            invocation.setAttachment(Constants.INTERFACE_KEY, url.getParameter(Constants.INTERFACE_KEY));  
      84.            invocation.setAttachment(Constants.VERSION_KEY, url.getParameter(Constants.VERSION_KEY));  
      85. if (url.getParameter(Constants.STUB_EVENT_KEY, false)){  
      86.                invocation.setAttachment(Constants.STUB_EVENT_KEY, Boolean.TRUE.toString());  
      87.            }  
      88. return invocation;  
      89.        }  
      90.    };

      是不是在想invoker对象是个什么东西,它就是我们通过以下代码导出的接口实现对象的代理对象

      Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));

      前面说了proxyFactory我们默认就当做是JdkProxyFactory类,getInvoker方法的实现如下,它重写了doInvoke方法,通过反射机制执行了接口实现类的, getInvoker方法反馈的是AbstractProxyInvoker对象。



      [java] view plain copy




      1. public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {  
      2. return new AbstractProxyInvoker<T>(proxy, type, url) {  
      3. //重写了AbstractProxyInvoker类的抽象doInvoke方法  
      4. @Override  
      5. protected Object doInvoke(T proxy, String methodName,   
      6.                                       Class<?>[] parameterTypes,   
      7. throws Throwable {  
      8.                 Method method = proxy.getClass().getMethod(methodName, parameterTypes);  
      9. return method.invoke(proxy, arguments);  
      10.             }  
      11.         };  
      12.     }


      细读AbstractProxyInvoker类代码就发现invoke方法中new了一个RpcResult对象,同时执行了doInvoke方法,因为当前类的doInvoke方法是抽象的,所以最终执行了外面重写的doInvoke方法。好了执行doInvoke方法以后服务端就响应了接口的调用,将执行的结果封装到了RpcResult中,经过序列化将对象传输到客户端服务器,客户端只需要接收数据再反序列化就可以拿到数据了。到此响应客户端的请求已经走完。


      [java] view plain copy




      1. public abstract class AbstractProxyInvoker<T> implements Invoker<T> {  
      2.       
      3. private final T proxy;  
      4.       
      5. private final Class<T> type;  
      6.       
      7. private final URL url;  
      8.   
      9. public AbstractProxyInvoker(T proxy, Class<T> type, URL url){  
      10. if (proxy == null) {  
      11. throw new IllegalArgumentException("proxy == null");  
      12.         }  
      13. if (type == null) {  
      14. throw new IllegalArgumentException("interface == null");  
      15.         }  
      16. if (! type.isInstance(proxy)) {  
      17. throw new IllegalArgumentException(proxy.getClass().getName() + " not implement interface " + type);  
      18.         }  
      19. this.proxy = proxy;  
      20. this.type = type;  
      21. this.url = url;  
      22.     }  
      23.   
      24. public Class<T> getInterface() {  
      25. return type;  
      26.     }  
      27.   
      28. public URL getUrl() {  
      29. return url;  
      30.     }  
      31.   
      32. public boolean isAvailable() {  
      33. return true;  
      34.     }  
      35.   
      36. public void destroy() {  
      37.     }  
      38.   
      39. public Result invoke(Invocation invocation) throws RpcException {  
      40. try {  
      41. //执行doInvoke方法,当前类的doInvoke方法是抽象的,是不是突然明白了,执行的不就是外面重写的doInvoke方法么  
      42. return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()));  
      43. catch (InvocationTargetException e) {  
      44. return new RpcResult(e.getTargetException());  
      45. catch (Throwable e) {  
      46. throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e);  
      47.         }  
      48.     }  
      49.       
      50. protected abstract Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable;  
      51.   
      52. @Override  
      53. public String toString() {  
      54. return getInterface() + " -> " + getUrl()==null?" ":getUrl().toString();  
      55.     }  
      56.   
      57.       
      58. }

      四 总结

      如果跟着文章分析代码,您已经知道dubbo框架是你怎么发布一个服务,又是怎么启动底层的netty框架了,然后怎么根据客户端请求传过来的参数定位到对应的服务实现类并执行对应的方法。看似很简单,但是涉及的知识面还是比较广的。比如装饰模式,工厂模式,抽象模版方法模式,netty框架的了解等。下个章节来看看客户端是怎么做来做请求的

      \