-
概述
服务导出就是将服务暴露出去,暴露出去就必须支持网络调用的,也就是启动一个端点,供别的服务来调用。那么这就是我们要讨论的,dubo是如何将一个普通接口实现远程网络调用的,这个原理是啥,让我们带这个疑问去一步步揭开吧。 -
Demo
//xml方式配置,暴露一个服务 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> //这会解析为ApplicationConfig对象 <dubbo:application name="hello-word-app"/> //应用基本信息 //这会解析为ApplicationConfig对象 <dubbo:registry address="zookeeper://localhost:2181"/> //注册中心 //这会解析为ProtocolConfig对象 <dubbo:protocol name="dubbo" port="20880"/> //抓换协议 //暴露服务 //这会解析为ServiceBean对象,ApplicationConfig,ProtocolConfig都会注入到ServiceBean中 <dubbo:service interface="com.example.dubbo.service.GreetingService" ref="greetingService"/> //服务载体bean <bean id="greetingService" class="com.example.dubbo.service.GreetingServiceImpl"/> </beans> //编码方式暴露服务 public class ProviderTest { public static void main(String[] args) throws IOException { //相当于<dubbo:service/> ServiceConfig<GreetingService> serviceServiceConfig = new ServiceConfig<GreetingService>(); //ApplicationConfig相当于 <dubbo:application/> serviceServiceConfig.setApplication(new ApplicationConfig("first-dubbo-provider")); //设置配置中心----RegistryConfig对象相当于<dubbo:registry/> serviceServiceConfig.setRegistry(new RegistryConfig("zookeeper://localhost:2181")); //设置接口与实现 serviceServiceConfig.setInterface(GreetingService.class); //GreetingServiceImpl相当于<bean /> serviceServiceConfig.setRef(new GreetingServiceImpl()); serviceServiceConfig.setVersion("1.0.0"); serviceServiceConfig.setGroup("dubbo1"); //导出服务----这一步由spring容器发生容器刷新完成的事件回调对每一个实现了 //ApplicationListener接口回调方法去自动调用的 serviceServiceConfig.export(); System.out.println("service is started"); System.in.read(); } } //服务暴露接口定义 public interface GreetingService { String sayHello(String name); } //说明 1. 不管哪种配置服务方式,其实内部原理都是一样的,只是表现形式不同 2. 目前为止,我们可能在xml中没有发现触发export的动作,在编码实现是手动调了, 那么xml方式是怎么来触发这个动作呢?那就是采用容器事件触发,发生的回调,如下 //ServiceBean的onApplicationEvent,实现了ApplicationListener接口 public void onApplicationEvent(ContextRefreshedEvent event) { if (!isExported() && !isUnexported()) { if (logger.isInfoEnabled()) { logger.info("The service ready on spring started. service: " + getInterface()); } //容器创建完成,发布事件,对实现ApplicationListener的bean发生回调 export(); } }
-
源码之流程
//ServiceBean的事件回调方法,也是暴露服务的入口 public void onApplicationEvent(ContextRefreshedEvent event) { if (!isExported() && !isUnexported()) { if (logger.isInfoEnabled()) { logger.info("The service ready on spring started. service: " + getInterface()); } //服务导出 export(); } } //ServiceBean的export方法 @Override public void export() { //调用父类的export方法,也就是ServiceConfig类型,这就与我们使用api调用对应上了 //其实ServiceBean也就是spring和dubbo的一个桥梁,利用spring的事件特性来自动在容器初始化完成 //自动调用暴露服务 super.export(); // Publish ServiceBeanExportedEvent publishExportEvent(); } //ServiceConfig的export方法 public synchronized void export() { //做一些配置检查,比如 应用名称,配置中心等 checkAndUpdateSubConfigs(); //这里判断是否配置应该暴露,根据ProviderConfig的export属性 //对应标签<dubbo:provider export="false" />注入的ProviderConfig类型Bean if (!shouldExport()) { return; } if (shouldDelay()) { delayExportExecutor.schedule(this::doExport, delay, TimeUnit.MILLISECONDS); } else { doExport(); } } //对当前服务接口是否暴露过做个校验,继续做暴露操作(一个服务接口一个ServiceConfig类型Bean) protected synchronized void doExport() { if (unexported) { throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!"); } if (exported) { return; } exported = true; if (StringUtils.isEmpty(path)) { path = interfaceName; } doExportUrls(); } //对多注册中心多协议暴露一个服务 private void doExportUrls() { //加载所有注册中心 List<URL> registryURLs = loadRegistries(true); //对一个服务接口进行在多个注册中心,每个注册中心暴露支持多种协议的服务 for (ProtocolConfig protocolConfig : protocols) { //生成标识一终协议服务的key 协议类型+/+暴露接口的全路径名称+服务版本 //比如当前要生成的是dubbo协议的服务,那么就是 dubb/...... String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version); //将一个要暴露的服务封装为一个ProviderModel对象,ProviderModel对象有详细关于 //这个暴露服务实例相关的信息 比如pathKey,服务实例bean,bean的所有接口名称和参数 ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass); //ApplicationModel里面存的是所有暴露的服务集合 //ConcurrentMap<String, ProviderModel> providedServices //providedServices是所有暴露服务的Map集合,key是pathKey ApplicationModel.initProviderModel(pathKey, providerModel); doExportUrlsFor1Protocol(protocolConfig, registryURLs); } } private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) { String name = protocolConfig.getName(); if (StringUtils.isEmpty(name)) { name = Constants.DUBBO; } //使用一个Map保存当前需要暴露服务的所有相关参数,后面要拼装为一个类型协议的字符串 Map<String, String> map = new HashMap<String, String>(); map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE); appendRuntimeParameters(map); appendParameters(map, application); appendParameters(map, module); appendParameters(map, provider, Constants.DEFAULT_KEY); appendParameters(map, protocolConfig); appendParameters(map, this); // export service //获取要暴露服务的主机ip,在protocolConfig中可配,否则默认用本机ip String host = this.findConfigedHosts(protocolConfig, registryURLs, map); //获取要暴露的主机端口,在protocolConfig中可配 Integer port = this.findConfigedPorts(protocolConfig, name, map); //生成对应协议的url,使用当前暴露服务的参数进行拼装,如下示例下我生成的,总之包含所有要的数据 //dubbo://192.168.1.7:20880/com.example.dubbo.service.GreetingService? //anyhost=true&application=hello-word-app&bean.name=com.example.dubbo.service.GreetingService&........ URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map); if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class) .hasExtension(url.getProtocol())) { url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class) .getExtension(url.getProtocol()).getConfigurator(url).configure(url); } //获取scope参数,是一个作用域的意思,就是服务暴露的范围 //none 表示不暴露 //remote 表示暴露在远程 //local表示暴露在本地 //null 默认 表示本地和远程都暴露 String scope = url.getParameter(Constants.SCOPE_KEY); // don't export when none is configured if (!Constants.SCOPE_NONE.equalsIgnoreCase(scope)) { //暴露在本地 // export to local if the config is not remote (export to remote only when config is remote) if (!Constants.SCOPE_REMOTE.equalsIgnoreCase(scope)) { exportLocal(url); } //暴露给远程 // export to remote if the config is not local (export to local only when config is local) if (!Constants.SCOPE_LOCAL.equalsIgnoreCase(scope)) { if (logger.isInfoEnabled()) { logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url); } if (CollectionUtils.isNotEmpty(registryURLs)) { for (URL registryURL : registryURLs) { url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_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); } // For providers, this is used to enable custom proxy to generate invoker String proxy = url.getParameter(Constants.PROXY_KEY); if (StringUtils.isNotEmpty(proxy)) { registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy); } Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); Exporter<?> exporter = protocol.export(wrapperInvoker); exporters.add(exporter); } } else { Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); Exporter<?> exporter = protocol.export(wrapperInvoker); exporters.add(exporter); } /** * @since 2.7.0 * ServiceData Store */ MetadataReportService metadataReportService = null; if ((metadataReportService = getMetadataReportService()) != null) { metadataReportService.publishProvider(url); } } } //添加当前服务的一种类型协议的url this.urls.add(url); }
-
源码之远程暴露
if (!Constants.SCOPE_LOCAL.equalsIgnoreCase(scope)) { if (logger.isInfoEnabled()) { logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url); } if (CollectionUtils.isNotEmpty(registryURLs)) { for (URL registryURL : registryURLs) { url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_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); } // For providers, this is used to enable custom proxy to generate invoker String proxy = url.getParameter(Constants.PROXY_KEY); if (StringUtils.isNotEmpty(proxy)) { registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy); } //1.proxyFactory是一个扩展点的实现,是ServiceConfig的一个属性,默认赋值 //拓展点适配器实现ProxyFactory$Adaptive(自动生成,后面给出代码) //2.proxyFactory.getInvoker实际上是先使用适配器的实现去获取实际需要的拓展点实现 //默认获取到的是JavassistProxyFactory //3.在转发调用JavassistProxyFactory的getInvoker方法获取Invoker对象 Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); //暴露服务到zookeeper(注册中心) //protocol也是一个拓展点,具体也和proxyFactory差不多, //1.先调用适配器实现的export,获取到需要的具体协议的实现 //2.比如dubbo协议,那么调用DubboProtocol的export进行注册服务 Exporter<?> exporter = protocol.export(wrapperInvoker); exporters.add(exporter); } }
功能:生成发生远程调用需要执行的执行载体---Invoker(服务之间交互的核心) ProxyFactory$Adaptive源码 class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory { public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException { if (arg2 == null) throw new IllegalArgumentException("url == null"); org.apache.dubbo.common.URL url = arg2; //默认使用javassist String extName = url.getParameter("proxy", "javassist"); if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])"); //根据extName选取ProxyFactory的全部实现中key为javassist-----JavassistProxyFactory org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName); //调用getInvoker方法真正去获取Invoker对象 return extension.getInvoker(arg0, arg1, arg2); } JavassistProxyFactory中获取Invoker public class JavassistProxyFactory extends AbstractProxyFactory { @Override @SuppressWarnings("unchecked") public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) { return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker)); } @Override public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) { // 生成Wrapper对象,远程发生调用调用的就是Wrapper的invokeMethod去执行 //Wrapper对象包装了proxy,是为了减少反射,后面给出Wrapper代码你就明白了 final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type); //使用匿名内部类创建Invoker对象,发生远程调用就是调用Invoker对象的doInvoke方法, //doInvoke方法内部实现采用的Wrapper的invokeMethod,所以最终发生远程调用, //执行的是Wrapper的invokeMethod 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源码的invokeMethod public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws java.lang.reflect.InvocationTargetException{ com.example.dubbo.service.GreetingService w; try{ //很明显,这是GreetingService实现的Wrapper的invokeMethod //可以直接强转,直接调用,就不需要再使用反射去调用了 w = ((com.example.dubbo.service.GreetingService)o); }catch(Throwable e){ throw new IllegalArgumentException(e); } //1.直接根据GreetingService接口生成的代码 //2.发生调用直接根据方法名称直接调用,直接做个匹配检测就好 //3.参数 a.o是被代理的真正实现的对象实例 b.n表示方法名称 c.v表示方法参数列表 try{ if( "sayHello".equals( n ) && v.length == 1 ) { return w.sayHello((java.lang.String)v[0]); } } catch(Throwable e) { throw new java.lang.reflect.InvocationTargetException(e); } throw new org.apache.dubbo.common.bytecode.NoSuchMethodException("Not found method \""+n+"\" in class com.example.dubbo.service.GreetingService."); }
功能:发布服务启动netty, 将服务注册到注册中心 //比如我们用的是dubbo协议进行发布服务---DubboProtocol @Override public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { URL url = invoker.getUrl(); // export service. //key是一个服务的多个参数拼接的唯一标识,服务调用使用这个key来找到DubboExporter //比如 dubboGroup/com.example.dubbo.service.GreetingService:1.0.0:20880 String key = serviceKey(url); //将invoker转换为DubboExporter DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap); //将DubboExporter存储在DubboProtocol中,并且和key做了映射关系 exporterMap.put(key, exporter); //export an stub service for dispatching 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); } } //开启默认netty作为服务传输层,使用zookeeper作为默认的注册中心 //这里使用到了两个拓展点 Transporter((传输),RegistryFactory(注册中心) openServer(url); optimizeSerialization(url); return e
说明:注册后,zookeeper显示的内容
(二)dubo源码分析之服务导出
猜你喜欢
转载自blog.csdn.net/weixin_38312719/article/details/106065541
今日推荐
周排行