有了第三篇SPI机制的学习,我们再来看接着第二篇服务的暴露继续学习有些地方可以很容易的掌握。
在网上找了一下发布的过程图,觉得下图解析的不错,顺手贴过来:
<dubbo:service interface="com.api.ITestService" ref="testService" version="${dubbo.version}" group="${dubbo.group}"/>
结合项目本身的dubbo服务发布配置项,我们可以去做些整体的理解。
从图中可以看出是两部分,第一部分是ref到Invoke的转化,然后是Invoke到Exporter的转化
大致的过程就是上图,具体的细节我们需要跟一遍源码,话不多说,开始:
1. 首先在第二篇中,我们说到暴露前的准备工作中最后一步就是执行export()操作。
public void onApplicationEvent(ApplicationEvent event) { if (ContextRefreshedEvent.class.getName().equals(event.getClass().getName())) { if (isDelay() && ! isExported() && ! isUnexported()) { if (logger.isInfoEnabled()) { logger.info("The service ready on spring started. service: " + getInterface()); } export(); } } }
2.export(),就是在ServiceConfig中
没什么细节可以说的,不过看到此倒是突然会心一笑,,dubbo里提供延迟暴露,参数是delay,内部是用的子线程睡眠的方式去实现的。如果没有配置,那么会立马暴露出去。
传送门1:dubbo的延迟暴露
public synchronized void export() { if (provider != null) { if (export == null) { export = provider.getExport(); } if (delay == null) { delay = provider.getDelay(); } } if (export != null && ! export.booleanValue()) { return; } if (delay != null && delay > 0) { Thread thread = new Thread(new Runnable() { public void run() { try { Thread.sleep(delay); } catch (Throwable e) { } doExport(); } }); thread.setDaemon(true); thread.setName("DelayExportServiceThread"); thread.start(); } else { doExport(); } }3. doExport()
这个接口不想去分析,因为只是一些值的校验啊什么的,没什么重点。关注最后一句doExportUrls()。
4.doExportUrls()
由于dubbo支持的是多协议,因此需要在每个协议中都去进行暴露。
传送门2:dubbo多协议
private void doExportUrls() { List<URL> registryURLs = loadRegistries(true); for (ProtocolConfig protocolConfig : protocols) { doExportUrlsFor1Protocol(protocolConfig, registryURLs); } }4.doExportUrlsFor1Protocol()具体暴露过程
缺省掉一些干扰代码,关注两个地方:
一个是URL,URL就是我们常见的需要暴露出去的地址,样例如下:
dubbo://192.168.1.11:20880/com.api.IDubboTestService?anyhost=true&application=dubbo&default.loadbalance=roundrobin&default.retries=0
另一个就是暴露过程:分成本地暴露和远程暴露两部分。这里我思考一下为什么会有本地暴露的过程呢?自己猜想应该如果项目本身需要用到这个接口,难不成还要通过远程的方式去访问?这不是傻逼了么,不如直接通过本地访问,更高效。
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) { //省略不关注代码.... //封装URL地址 URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map); //.... 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")); 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<?> 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); } }
5. 不继续深入本地和远程,只讲述两种方式的后续相同点。
可以看见这边是通过proxyFactory获取到的Invoke,然后将Invoke转化成Export发布出去。
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); Exporter<?> exporter = protocol.export(invoker); exporters.add(exporter);6.ProxyFactory
有了第三篇关于SPI机制的学习,这边更好理解了,ProxyFactory默认使用的是javassist作为其扩展实现类。并且它拥有@Adaptive注解,就意味着会生成对应方法的代理类。(这个注解写在类上和写在方法上是有区别的)
@SPI("javassist") public interface ProxyFactory { /** * create proxy. * * @param invoker * @return proxy */ @Adaptive({Constants.PROXY_KEY}) <T> T getProxy(Invoker<T> invoker) throws RpcException; /** * create invoker. * * @param <T> * @param proxy * @param type * @param url * @return invoker */ @Adaptive({Constants.PROXY_KEY}) <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException; }
7. 还记得怎么获取代理类源码的?
SPI篇有介绍,这个code可以获取到。
8.本地新建调试
在自己本地里新建一个相同的,用于调试。这是我们自己新建出来的。
9.当执行到ProxyFactory.getInvoker()时,代码会进入生成的代理类中,此处我们先看下本地暴露时的情况
顺便说下url中的获取,这边extName就是在获取当前应该执行哪一个实现类,因为在SPI中默认了带有"javassist",因此这边获取到的还是它的扩展类,同时我们也能看见对于本地暴露的时候url是一个特别的开头“injvm”。
10.本地暴露时获取Invoke
public class JavassistProxyFactory extends AbstractProxyFactory { @SuppressWarnings("unchecked") public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) { return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker)); } 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); } }; } }11.当执行到ProxyFactory.getInvoker()时,代码会进入生成的代理类中,此处我们先看下 远程暴露时的情况