dubbo之服务本地暴露
寫在前面
源碼 。
服務提供者是標記了@Service注解的類,想要被服務消費者使用,必須將服務暴露出去,即讓服務消費者拿到封裝服務信息的com.alibaba.dubbo.common.URL對象字符串,當前有三種服務暴露方式:
遠程暴露:即將服務信息注冊到遠端注冊中心,如配置<dubbo:service scope="remote" />。 本地暴露:JVM內部調用,因為信息已經在內存中,通過內存可以直接獲取調用信息,因此叫做本地暴露,如配置<dubbo:service scope="local">。 不暴露:不暴露服務,可以忽略這種方式,如配置<dubbo:service scope="none">。本文來分享的是本地暴露,相關的源碼在模塊dubbo-rpc-injmv中,如下圖:
在dubbo之服務提供者配置 一文中,我們其實分析了部分服務暴露的內容,大家可以看下,本文為了承接,會有部分內容的重疊,就從方法com.alibaba.dubbo.config.ServiceConfig.doExportUrls來開始分析。
1:doExportUrls
源碼如下:
class FakeCls {private void doExportUrls() {// 2022-01-21 18:25:43List<URL> registryURLs = loadRegistries(true);// 循環所有的協議暴露服務到所有的注冊中心地址// 協議:protocols,即<dubbo:protocol>設置// 服務:通過<dubbo:service>設置// 注冊中心地址:registryURLs,通過<dubbo:registry>指定for (ProtocolConfig protocolConfig : protocols) {// 2022-01-21 18:34:22doExportUrlsFor1Protocol(protocolConfig, registryURLs);}} }2022-01-21 18:25:43處獲取配置的所有注冊中心地址,具體參考1.1:loadRegistries。2022-01-21 18:34:22處是將服務按照指定的協議注冊到注冊中心,具體參考1.2:doExportUrlsFor1Protocol。
1.1:loadRegistries
本文講解的時本地服務暴漏,不會使用到這里的信息,但是為了完整性,放在這里,對這部分感興趣的朋友可以參考dubbo之服務遠程暴露 文章分析。
1.2:doExportUrlsFor1Protocol
將服務按照指定的協議注冊到注冊中心,分為遠程暴漏和本地暴漏,其中本地暴漏不會注冊服務到注冊中心,因為是同一個JVM,信息可以直接從JVM中獲取到,因為本文重點分析的是本地服務暴漏,所以關于遠程暴漏的相關源碼會選擇性忽略,關于這部分的分析,可以才參考dubbo之服務遠程暴露 文章分析。源碼如下:
class FakeCls {private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {// 協議的名稱,如<dubbo:protocol name="dubbo" port="20826">,這里就是dubbo// 協議:就是暴漏自己的方法String name = protocolConfig.getName();// 沒有則默認使用dubboif (name == null || name.length() == 0) {name = "dubbo";}//*** 省略構建URL相關代碼 ***//// 獲取scope,如果是本地暴漏的話配置如:<dubbo:service interface="dongshi.daddy.service.scopelocal.ScopeLocalService" ref="scopeLocalService" scope="local"/>// 在文章開頭也提到了可配置為remote,代表遠程暴漏,none代表不爆露String scope = url.getParameter(Constants.SCOPE_KEY);// 如果是配置scope="none",不進行任何操作,此時不進行暴漏,即不對外使用if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {// 如果是scope不是remote則使用本地服務暴漏if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {// 2022-01-22 12:25:51exportLocal(url);}// 如果是scope不是local則使用遠程服務暴漏if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {// *** 省略遠程服務暴漏邏輯 *** //}}// 添加暴漏服務urlthis.urls.add(url);} }2022-01-22 12:25:51處是本地服務暴漏,具體參考1.3:exportLocal。
1.3:exportLocal
源碼如下:
class FakeCls {private void exportLocal(URL url) {// url.getProtocol():一般是dubbo // Constants.LOCAL_PROTOCOL:injvm// 為什么做這個判斷???if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {// 構建local的URL,如// injvm://127.0.0.1/dongshi.daddy.service.scopelocal.ScopeLocalService?accesslog=true&anyhost=true&application=dongshidaddy-provider&bean.name=dongshi.daddy.service.scopelocal.ScopeLocalService&bind.ip=192.168.2.107&bind.port=20826&dubbo=2.0.2&generic=false&interface=dongshi.daddy.service.scopelocal.ScopeLocalService&methods=sayHi&owner=dongshidaddy&pid=6324&scope=local&side=provider×tamp=1642823993714URL local = URL.valueOf(url.toFullString()).setProtocol(Constants.LOCAL_PROTOCOL).setHost(LOCALHOST).setPort(0);// public static final String SERVICE_IMPL_CLASS = "service.classimpl";// url.getServiceKey():dongshi.daddy.service.scopelocal.ScopeLocalService// getServiceClass(ref:class dongshi.daddy.service.scopelocal.ScopeLocalServiceImpl// 將服務類的信息存儲到StaticContext中StaticContext.getContext(Constants.SERVICE_IMPL_CLASS).put(url.getServiceKey(), getServiceClass(ref));// 2022-01-22 17:21:22Exporter<?> exporter = protocol.export(proxyFactory.getInvoker(ref, (Class) interfaceClass, local));exporters.add(exporter);logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");}} }該小節以下部分稍微有點繞,大家吃耐心,不懂的話,多看幾遍!!!
2022-01-22 17:21:22處protocol定義為private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();,可以看到是獲取自適應擴展類 ,其中從Protocol接口也可以看出來,源碼如下:
/*** Protocol. (API/SPI, Singleton, ThreadSafe)*/ @SPI("dubbo") public interface Protocol {int getDefaultPort();@Adaptive<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;@Adaptive<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;void destroy(); }可以看到export方法是標注了@Adaptive 注解的,此處protocol
是Protocol#Adaptive,這個很好理解,因為獲取就是動態生成的自適應子類,通過其調用真正的擴展實現類,那么想要知道調用的到底是誰就需要知道生成的代碼到是什么樣子的,我們可以通過如下的步驟來獲取其內容:
如下是我獲取的內容:
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {public void destroy() {throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");}public int getDefaultPort() {throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");}public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {if (arg1 == null) throw new IllegalArgumentException("url == null");com.alibaba.dubbo.common.URL url = arg1;String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());if (extName == null)throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);return extension.refer(arg0, arg1);}public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");if (arg0.getUrl() == null)throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());if (extName == null)throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);return extension.export(arg0);} }我們重點關注其中的export方法,可以看到是調用url.getProtocol()作為目標擴展類的名稱,那么是什么值呢?我們的url為injvm://127.0.0.1/...可以看到協議是injvm,那么對應的擴展類是誰呢,可以從文件META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol中找到答案,其中key為injvm的的配置項內容是injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol,因此最終調用的擴展類類是com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol,但是真的是這樣嗎?我們來debug看一下,如下圖:
從圖中可以看出,還分別調用了QosProtocolWrapper,ProtocolListenerWrapper,ProtocolFilterWrapper,這是Protocol的Wrapper類,我們從META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol中可以看出來,如下圖:
關于Wrapper詳細可以參考dubbo之SPI Wrapper分析 。
最終調用過程為Protocol$Adaptive->QosProtocolWrapper->ProtocolListenerWrapper->ProtocolFilterWrapper->InjvmProtocol。具體的我們在2:Protocol分析。
2:Protocol
源碼如下:
@SPI("dubbo") public interface Protocol {// 獲取當前協議在沒有配置端口時的默認端口號int getDefaultPort();// 暴漏service供遠程調用// 1:協議對象需要在收到一個請求后記錄遠程源的的地址,通過API RpcContext.getContext().setRemoteAddress()// 2:該方法必須具備冪等性(idempotent [a?'demp?t?nt]),即通過該方法調用一次或者是多次來暴漏一個URL沒有任何差別// 3:Invoker實例需要被框架傳入進來,protoco擴展類需要用到,如自適應時使用// 返回值:Exporter<T>,引用的是被暴漏的service,之后如果是需要取消暴漏的話需要用到@Adaptive<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;@Adaptive<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;void destroy(); }2.1:Protocol$Adaptive
如何獲取該類信息可以參考1.3:exportLocal。
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {public void destroy() {// 因為沒有標注@Adaptive注解,所以直接拋出java.lang.UnsupportedOperationExceptionthrow new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");}// 因為沒有標注@Adaptive注解,所以直接拋出java.lang.UnsupportedOperationExceptionpublic int getDefaultPort() {throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");}public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {if (arg1 == null) throw new IllegalArgumentException("url == null");com.alibaba.dubbo.common.URL url = arg1;String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());if (extName == null)throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);return extension.refer(arg0, arg1);}public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");if (arg0.getUrl() == null)throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());if (extName == null)throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);return extension.export(arg0);} }2.2:ProtocolListenerWrapper
源碼如下:
class FakeCls {@Overridepublic <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {// 以下遠端暴漏才會執行,這里可以忽略,因為url是injvm://打頭if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {return protocol.export(invoker);}// 2022-01-23 19:29:28return new ListenerExporterWrapper<T>(protocol.export(invoker),Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class).getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));} }2022-01-23 19:29:28處protocol.export(invoker)繼續調用裝飾的protocol類,這里調用的就是ProtocolFilterWrapper,關于該類參考2.3:ProtocolFilterWrapper。Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class).getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY))處是使用keyexporter.listener,從url獲取值從而獲取要激活的ExporterListener擴展類。ListenerExporterWrapper構造函數源碼如下:
class FakeCls {public ListenerExporterWrapper(Exporter<T> exporter, List<ExporterListener> listeners) {} }該監聽器的作用是用來監聽Exporter暴漏完畢和取消暴漏完畢。
2.3:ProtocolFilterWrapper
主要用于給Invoker增加Filter過濾器鏈,在調用真正的服務方法之前會調用過濾器Filter的邏輯,具體參考2.3.1:export。
2.3.1:export
源碼如下:
class FakeCls {@Overridepublic <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {// 此處要求協議是registry://,即遠程暴露,這里是injvm://,所以可以忽略if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {return protocol.export(invoker);}// 2022-01-24 16:02:53// 這里的protocol就是InJvmProtocolreturn protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));}}2022-01-24 16:02:53處buildInvokerChain添加Filter鏈,具體參考2.3.2:buildInvokerChain。
2.3.2:buildInvokerChain
源碼如下:
class FakeCls {private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {Invoker<T> last = invoker;// 獲取激活的Filter擴展類集合List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);if (!filters.isEmpty()) {for (int i = filters.size() - 1; i >= 0; i--) {final Filter filter = filters.get(i);final Invoker<T> next = last;// 將Filter封裝成Invoker,調用invoke方法時,內部鏈式調用下一個Invoker,最后一個Invoker就是目標服務類方法的Invokerlast = new Invoker<T>() {@Overridepublic Class<T> getInterface() {return invoker.getInterface();}@Overridepublic URL getUrl() {return invoker.getUrl();}@Overridepublic boolean isAvailable() {return invoker.isAvailable();}@Overridepublic Result invoke(Invocation invocation) throws RpcException {// 這行代碼比較關鍵,將next作為參數調用Filter類方法,在Filter類方法內部我們就可以通過invoker.invoke來繼續向下調用了,最終調用到真正服務類方法return filter.invoke(next, invocation);}@Overridepublic void destroy() {invoker.destroy();}@Overridepublic String toString() {return invoker.toString();}};}}return last;} }越靠后的Filter越先執行先執行,執行順序如filter1->filte2->filter3->...->服務類方法。
2.4:InjvmProtocol
該類是Injvm協議的實現類,我們還是從入口方法export開始。
2.4.1:export
源碼如下:
class FakeCls {@Overridepublic <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);} }主要是創建了InjvmExporter對象。關于該對象,具體參考3:Exporter。
3:Exporter
該接口用于基于相關協議來暴露服務,接口源碼如下:
public interface Exporter<T> {// 獲取內部的InvokerInvoker<T> getInvoker();// 取消暴露void unexport(); }主要類圖如下:
接下來我們從類AbstractExporter開始來看以下。
3.1:AbstractExporter
源碼如下:
public abstract class AbstractExporter<T> implements Exporter<T> {protected final Logger logger = LoggerFactory.getLogger(getClass());// 內部的Invokerprivate final Invoker<T> invoker;// 是否沒有暴露的標記private volatile boolean unexported = false;public AbstractExporter(Invoker<T> invoker) {if (invoker == null)throw new IllegalStateException("service invoker == null");// 必須是接口if (invoker.getInterface() == null)throw new IllegalStateException("service type == null");// 必須有暴露的URLif (invoker.getUrl() == null)throw new IllegalStateException("service url == null");this.invoker = invoker;}@Overridepublic Invoker<T> getInvoker() {return invoker;}// 取消暴露,其實就是調用getInvoker().destroy();@Overridepublic void unexport() {if (unexported) {return;}unexported = true;getInvoker().destroy();}@Overridepublic String toString() {return getInvoker().toString();} }3.2:InjvmExporter
AbstractExporter的子類,源碼如下:
class InjvmExporter<T> extends AbstractExporter<T> {// 服務鍵,一般是服務接口的全限定類名稱private final String key;// 已經暴露的Exporterprivate final Map<String, Exporter<?>> exporterMap;InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) {super(invoker);this.key = key;this.exporterMap = exporterMap;exporterMap.put(key, this);}// 取消暴露@Overridepublic void unexport() {super.unexport();exporterMap.remove(key);} }3.3 ListenerExporterWrapper
具有監聽功能的Exporter的Wrapper類,源碼如下:
public class ListenerExporterWrapper<T> implements Exporter<T> {private static final Logger logger = LoggerFactory.getLogger(ListenerExporterWrapper.class);private final Exporter<T> exporter;// 注冊的暴露監聽器private final List<ExporterListener> listeners;public ListenerExporterWrapper(Exporter<T> exporter, List<ExporterListener> listeners) {if (exporter == null) {throw new IllegalArgumentException("exporter == null");}this.exporter = exporter;this.listeners = listeners;// 構造函數執行,代表服務暴露了,執行對應的監聽器的暴露方法exportedif (listeners != null && !listeners.isEmpty()) {RuntimeException exception = null;for (ExporterListener listener : listeners) {if (listener != null) {try {listener.exported(this);} catch (RuntimeException t) {logger.error(t.getMessage(), t);exception = t;}}}if (exception != null) {throw exception;}}}@Overridepublic Invoker<T> getInvoker() {return exporter.getInvoker();}@Overridepublic void unexport() {// 取消暴露,執行監聽器的unexported方法try {exporter.unexport();} finally {if (listeners != null && !listeners.isEmpty()) {RuntimeException exception = null;for (ExporterListener listener : listeners) {if (listener != null) {try {listener.unexported(this);} catch (RuntimeException t) {logger.error(t.getMessage(), t);exception = t;}}}if (exception != null) {throw exception;}}}} }ExporterListener參考4:ExporterListener。
4:ExporterListener
源碼如下:
@SPI public interface ExporterListener {// 服務暴露調用的方法void exported(Exporter<?> exporter) throws RpcException;// 服務取消暴露調用的方法void unexported(Exporter<?> exporter); }類圖如下:
接下來看下這個唯一的實現類ExporterListenerAdapter,如下:
public abstract class ExporterListenerAdapter implements ExporterListener {@Overridepublic void exported(Exporter<?> exporter) throws RpcException {}@Overridepublic void unexported(Exporter<?> exporter) throws RpcException {}}也僅僅是個空實現,沒有實際的邏輯。
總結
以上是生活随笔為你收集整理的dubbo之服务本地暴露的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 联想服务器x3250 m5文档,【联想x
- 下一篇: 芯洲SCT2230TVBR为用户小型化设