dubbo源码分析12——服务暴露3_doExportUrls()方法分析
本文紧接上文,doExportUrls()方法位于ServiceConfig类中,代码入口如下:
private void doExportUrls() { List<URL> registryURLs = loadRegistries(true); // 获取注册中心的配置 for (ProtocolConfig protocolConfig : protocols) { //获取配置的服务暴露协义 doExportUrlsFor1Protocol(protocolConfig, registryURLs); //真正的暴露,通过协义和注册中心去完成暴露 } }
这样在一个比较宏观的粒度已经看明白了服务暴露,如果想深入请进入下一级分析。
下面以默认的dubbo协议,注册中心为zookeeper来分析 doExportUrlsFor1Protocol(protocolConfig, registryURLs) 方法,方法比较长大约200行,我们分成几段来看
第一段:
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 = 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(); } } }
这段的主要作用就是获取host,四种方式的获取:
protocolConfig.getHost() //从配置中获取
InetAddress.getLocalHost().getHostAddress() //返回本机的ip地址
registryURL.getHost() //获取注册中心的主机名
NetUtils.getLocalHost() //遍历本地网卡,返回第一个合理的IP
为了确保获得的主机有效,还有相应的验证:
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()); // LOCAL_IP_PATTERN对应的正则式 Pattern.compile("127(\\.\\d{1,3}){3}$")
}
第二段:
这段主要是获取各个协义需要暴露的端口, 按照这样的优先级去获取 :
Protocol的实现类的默认端口 ——> Protocol的配置端口 ——> 随机端口
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); }
第三段
代码量很多,收集各类参数,放入map中,在为服务暴露做参数收集准备工作
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())); } 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(); //获取包装类,包装类实质上就是通过javassist生成的类,当我们通过包装类对角调dubbo接口时实际调的还是接口对象的原有方法。此处包装起到的作用无非是可以以统一的代码的去调用用户提供的不同的dubbo接口 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"); }
第四段:
这段主要是拼接URL,Dubbo框架是以URL为总线的模式,即运行过程中所有的状态数据信息都可以通过URL来获取,比如当前系统采用什么序列化,采用什么通信,采用什么负载均衡等信息,都是通过URL的参数来呈现的,所以在框架运行过程中,运行到某个阶段需要相应的数据,都可以通过对应的Key从URL的参数列表中获取。
URL示例:dubbo://192.168.10.247:10080/org.huxin.dubbo.test.user.service.UserInterface?anyhost=true&application=dubbo-provider&default.retries=0&default.timeout=5000&dubbo=2.8.4&generic=false&getUserById.0.callback=true&interface=org.huxin.dubbo.test.user.service.UserInterface&methods=getUserById,getUserList,updateUsers&organization=huxin&owner=programmer&pid=6020&retries=0&serialization=kryo&side=provider×tamp=1512955717318
// 导出服务 String contextPath = protocolConfig.getContextpath(); //获取协议的上下文路径
if ((contextPath == null || contextPath.length() == 0) && provider != null) { contextPath = provider.getContextpath(); }
//拼接url URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
//这里的ConfiguratorFactory是个扩展,可以用来设计自己的URL组成规则 if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class) .hasExtension(url.getProtocol())) { url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class) .getExtension(url.getProtocol()).getConfigurator(url).configure(url); }
第五段:
根据scope的配置决定是作本地暴露还是远程暴露,做服务暴露从结果上看就是产生了一个特定服务的 Exporter 类,并将其存储在对应的ServiceBean实例的 exporters属性中。//private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();
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); }
//只有远程暴露才需要用到注册中心url if (registryURLs != null && registryURLs.size() > 0 && url.getParameter("register", true)) { for (URL registryURL : registryURLs) { url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
//获取监控中心的URL,此处先暂不关心生成的细节,待到服务治理时才详细分析 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); }
//这里对参数做个介绍:
参数1:ref就是接口实现类
参数2:interfaceClass:接口类
参数3:在registryURL上添加参数,key为"export",value就是前面产生的服务协义的url
//proxyFactory的来历
ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension()
这个类实例最终就是com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory类的实例,这是由于ProxyFactory类上
的注解决定的 Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); Exporter<?> exporter = protocol.export(invoker); exporters.add(exporter); } } else { //这个是用来没有注册中心情况下的暴露,应该是用于直连的场景 Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url); Exporter<?> exporter = protocol.export(invoker); exporters.add(exporter); } } } this.urls.add(url);
按代码的逻辑,一般情况下,一个dubbo服务接口会先作本地暴露,然后再作远程暴露,也即一个服务接口两次暴露。
下面来张图总结一下:服务提供者暴露一个服务的详细过程
由于篇幅太长,准备另开两篇对本地暴露和远程暴露进行详细分析。