在上一篇文章中咱們介紹 Dubbo 自定義標籤解析相關內容,其中咱們自定義的 XML 標籤 <dubbo:service />
會被解析爲 ServiceBean 對象(傳送門:Dubbo XML 配置加載)。今天咱們講述的內容和 ServiceBean 密切相關!
細心的讀者在閱讀 ServiceBean 類時會發現 onApplicationEvent() 方法和 afterPropertiesSet() 方法調用了一個共同的方法 export()。直覺告訴咱們這個方法應該和服務的暴露有關,咱們接下來就 從 export() 方法入手分析。java
爲了解答 export() 調用時機問題,咱們須要關注 ServiceBean 類中的三個方法spring
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
SpringExtensionFactory.addApplicationContext(applicationContext);
supportedApplicationListener = addApplicationListener(applicationContext, this);
}
public void onApplicationEvent(ContextRefreshedEvent event) {
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
public void afterPropertiesSet() throws Exception {
// 省略...
if (!supportedApplicationListener) {
export();
}
}
複製代碼
代碼執行邏輯大體以下:apache
public synchronized void export() {
if (provider != null) {
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
if (export != null && !export) {
return;
}
if (delay != null && delay > 0) {
delayExportExecutor.schedule(new Runnable() {
@Override
public void run() {
doExport();
}
}, delay, TimeUnit.MILLISECONDS);
} else {
doExport();
}
}
複製代碼
export()方法比較簡單。注意這裏有個 delay 變量,咱們可使用該變量延遲執行 export() 方法。 繼續看 doExport() 方法bash
protected synchronized void doExport() {
// 省略...
doExportUrls();
ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), ref, interfaceClass);
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}
private void doExportUrls() {
List<URL> registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
複製代碼
doExport()方法省略了不少 ServiceBean 配置校驗和初始化代碼。你們有興趣能夠自行閱覽。這裏直接劃重點!!!分析 doExportUrls() 方法!!! 先看 loadRegistries() 方法:網絡
protected List<URL> loadRegistries(boolean provider) {
checkRegistry();
List<URL> registryList = new ArrayList<URL>();
// registries 在 afterPropertiesSet() 方法中初始化
if (registries != null && !registries.isEmpty()) {
for (RegistryConfig config : registries) {
String address = config.getAddress();
if (address == null || address.length() == 0) {
address = Constants.ANYHOST_VALUE;
}
String sysaddress = System.getProperty("dubbo.registry.address");
if (sysaddress != null && sysaddress.length() > 0) {
address = sysaddress;
}
if (address.length() > 0 && !RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map<String, String> map = new HashMap<String, String>();
// 將 application/config 部分屬性整合到 map 中,詳細見:
appendParameters(map, application);
appendParameters(map, config);
map.put("path", RegistryService.class.getName());
map.put("dubbo", Version.getProtocolVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
if (!map.containsKey("protocol")) {
if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) {
map.put("protocol", "remote");
} else {
map.put("protocol", "dubbo");
}
}
// 構建 url ,返回結果相似 zookeeper://192.168.0.100:2181/org.apache.dubbo.registry.RegistryService?
// application=demo-provider&dubbo=2.0.2&pid=22705&qos.port=22222×tamp=1549005672530
List<URL> urls = UrlUtils.parseURLs(address, map);
for (URL url : urls) {
// 將此時 url 的 protocol 保存到 registry 參數中
url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
// 設置 url protcol 屬性爲 registry
url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
if ((provider && url.getParameter(Constants.REGISTER_KEY, true))
|| (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}
}
}
}
return registryList;
}
複製代碼
loadRegistries() 用於加載註冊中心。歸納來講就是用於解析咱們在配置文件中定義的 <dubbo:registry />
標籤。 checkRegistry() 方法用於校驗註冊中心配置校驗,裏面有一些版本兼容的代碼。appendParameters() 方法詳見 appendParameters() 小節。app
介紹完 loadRegistries() 方法,咱們接着看 doExportUrlsFor1Protocol()。doExportUrlsFor1Protocol() 方法比較長,這裏咱們挑出和本地暴露相關的內容進行分析。jvm
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);
}
if (!Constants.SCOPE_LOCAL.equalsIgnoreCase(scope)) {
// 遠程暴露相關內容,省略...
}
}
private void exportLocal(URL url) {
if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
URL local = URL.valueOf(url.toFullString())
.setProtocol(Constants.LOCAL_PROTOCOL)
.setHost(LOCALHOST)
.setPort(0);
Exporter<?> exporter = protocol.export(
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
}
}
複製代碼
看到 exportLocal() 方法,意味着咱們已經快要直達本地服務暴露的核心了!更使人按捺不住的是!這裏又用到了 Dubbo 中的 SPI 機制(詳見系列第一篇Dubbo SPI)。讓咱們看看這裏到底作了什麼?ide
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
複製代碼
熟悉的配方熟悉的料,在這裏咱們獲取了 Protocol 和 ProxyFactory 對應的自適應擴展類。根據方法調用的嵌套邏輯,先來看 ProxyFactory 自適應擴展類 ProxyFactory$Adaptive 的 getInvoker() 方法。post
public 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;
String extName = url.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
org.apache.dubbo.rpc.ProxyFactory extension = null;
try {
extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
}catch(Exception e){
if (count.incrementAndGet() == 1) {
logger.warn("Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.", e);
}
extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension("javassist");
}
return extension.getInvoker(arg0, arg1, arg2);
}
}
複製代碼
這裏咱們實際會去調用 StubProxyFactoryWrapper 包裝類的 getInvoker() 方法,若是不明白能夠先看下 【Dubbo源碼閱讀系列】之 Dubbo SPI 機制。ui
public class StubProxyFactoryWrapper implements ProxyFactory {
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException {
return proxyFactory.getInvoker(proxy, type, url);
}
}
public class JavassistProxyFactory extends AbstractProxyFactory {
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
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);
}
};
}
}
複製代碼
結合上面的代碼咱們發現,發現最後調用的是 JavassistProxyFactory 類的 getInvoker() 方法。其中 wrapper 是動態生成的代理對象。最後返回一個 AbstractProxyInvoker 對象,doInvoke() 方法會調用 wrapper 代理類的 invokeMethod() 方法,其中 invokeMethod() 方法大概以下所示:
public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws java.lang.reflect.InvocationTargetException {
org.apache.dubbo.demo.provider.DemoServiceImpl w;
try {
w = ((org.apache.dubbo.demo.provider.DemoServiceImpl) $1);
} catch (Throwable e) {
throw new IllegalArgumentException(e);
}
try {
if ("sayHello".equals($2) && $3.length == 1) {
return ($w) w.sayHello((java.lang.String) $4[0]);
}
} catch (Throwable e) {
throw new java.lang.reflect.InvocationTargetException(e);
}
throw new org.apache.dubbo.common.bytecode.NoSuchMethodException("Not found method \"" + $2 + "\" in class org.apache.dubbo.demo.provider.DemoServiceImpl.");
}
複製代碼
稍微有一點繞,至少咱們已經看完了 proxyFactory.getInvoker() 方法了,咱們獲取到了一個包裝了動態代理類的 AbstractProxyInvoker 對象。接下來繼續看 protocol.export() 方法。
public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = null;
try {
extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
}catch(Exception e){
if (count.incrementAndGet() == 1) {
logger.warn("Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.Protocol, will use default extension dubbo instead.", e);
}
extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension("dubbo");
}
return extension.export(arg0);
}
複製代碼
因爲此時的 url 中 protocol 值爲 injvm(url 通過 setProtocol(LOCAL_PROTOCOL) 操做後 protocol 已經更新爲 injvm),所以咱們這裏得到的擴展類實際爲包裝了 InjvmProtocol 的包裝類對象,對 wrapper 類有疑問的能夠看下【Dubbo源碼閱讀系列】之 Dubbo SPI 機制。 這裏會涉及到一個方法 buildInvokerChain() 方,道它用於構建一個調用鏈。 總體調用時序簡圖以下所示:
ProtocolFilterWrapper.java
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
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;
last = new Invoker<T>() {
// 省略 Invoker 構建代碼...
@Override
public Result invoke(Invocation invocation) throws RpcException {
return filter.invoke(next, invocation);
}
// 省略 Invoker 構建代碼...
};
}
}
return last;
}
複製代碼
buildInvokerChain() 方法用於構建調用鏈,初步瀏覽下來發現調用鏈應該是由 Filter 擴展類構成。那麼這些 Filter 擴展類又從何而來呢?這行代碼很關鍵!!!
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
複製代碼
對於這段代碼咱們應該有很強的親切感,但仔細看又稍稍有所不一樣。實際上被 @Activate 註解標記的擴展類會被加載到 ExtensionLoader 類的 cachedActivates 集合中。 咱們在調用 ExtensionLoader 類的 getActivateExtension() 時,會根據咱們傳入的 key 和 group 值從 cachedActivates 集合中獲取知足當前條件的 filter 對象。
拿到 filters 集合後,會用鏈表的形式拼接 filter 調用鏈,舉個例子:
假設當前獲取到的 filters 集合中保存的 filter 對象爲 filter0、filter一、filter2。咱們對 filters 集合進行倒序遍歷。最後得到的 last 其實爲新建的 ivk2 對象。若是咱們調用 last 的 invoke 方法,調用鏈以下圖所示:
本文介紹了 Export() 方法被調用的時機以及基本流程。而且花了必定篇幅對 Dubbo 服務本地暴露進行了分析。其中摻雜了很多代碼的分析,可能沒有面面俱到吧。仍是建議你們本身本身 Debug 一下,不少東西瞬間秒懂,有助於源碼理解。下一篇文章咱們介紹 Dubbo 服務遠程暴露。
protected static void appendProperties(AbstractConfig config) {
if (config == null) {
return;
}
// getTagName:獲取去除了 Bean/Config 結尾的小寫類名(ApplicationConfig->application)
String prefix = "dubbo." + getTagName(config.getClass()) + ".";
Method[] methods = config.getClass().getMethods();
for (Method method : methods) {
try {
String name = method.getName();
// 一、方法長度大於3;二、方法以 set 開頭;三、方法修飾符類型爲 public;四、形參個數爲 1;五、形參類型爲基本類型
if (name.length() > 3 && name.startsWith("set") && Modifier.isPublic(method.getModifiers())
&& method.getParameterTypes().length == 1 && isPrimitive(method.getParameterTypes()[0])) {
// camelToSplitName: 舉個例子 ApplicationConfig——>application.config
String property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase() + name.substring(4), ".");
String value = null;
if (config.getId() != null && config.getId().length() > 0) {
// 拼接屬性名稱,並嘗試獲取對應屬性
String pn = prefix + config.getId() + "." + property;
value = System.getProperty(pn);
if (!StringUtils.isBlank(value)) {
logger.info("Use System Property " + pn + " to config dubbo");
}
}
if (value == null || value.length() == 0) {
// 好比當前 config 爲 ApplicationConfig,pn = dubbo.application.xxx
String pn = prefix + property;
value = System.getProperty(pn);
if (!StringUtils.isBlank(value)) {
logger.info("Use System Property " + pn + " to config dubbo");
}
}
if (value == null || value.length() == 0) {
Method getter;
try {
getter = config.getClass().getMethod("get" + name.substring(3));
} catch (NoSuchMethodException e) {
try {
getter = config.getClass().getMethod("is" + name.substring(3));
} catch (NoSuchMethodException e2) {
getter = null;
}
}
if (getter != null) {
if (getter.invoke(config) == null) {
// 嘗試使用 ConfigUtils.getProperty() 方法獲取屬性值
// 嘗試從 dubbo.properties.file 文件或 dubbo.properties 文件中讀取屬性
if (config.getId() != null && config.getId().length() > 0) {
value = ConfigUtils.getProperty(prefix + config.getId() + "." + property);
}
if (value == null || value.length() == 0) {
value = ConfigUtils.getProperty(prefix + property);
}
if (value == null || value.length() == 0) {
String legacyKey = legacyProperties.get(prefix + property);
if (legacyKey != null && legacyKey.length() > 0) {
value = convertLegacyValue(legacyKey, ConfigUtils.getProperty(legacyKey));
}
}
}
}
}
if (value != null && value.length() > 0) {
method.invoke(config, convertPrimitive(method.getParameterTypes()[0], value));
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
複製代碼
protected static void appendParameters(Map<String, String> parameters, Object config) {
appendParameters(parameters, config, null);
}
protected static void appendParameters(Map<String, String> parameters, Object config, String prefix) {
if (config == null) {
return;
}
Method[] methods = config.getClass().getMethods();
// 遍歷 config 類方法集合
for (Method method : methods) {
try {
String name = method.getName();
// 找到知足如下的方法:以set/is 開頭,非 getClass;方法修飾符爲 public;方法參數個數爲 0;返回類型爲基本類型
if ((name.startsWith("get") || name.startsWith("is"))
&& !"getClass".equals(name)
&& Modifier.isPublic(method.getModifiers())
&& method.getParameterTypes().length == 0
&& isPrimitive(method.getReturnType())) {
// 獲取 parameter 註解
Parameter parameter = method.getAnnotation(Parameter.class);
// @Parameter(excluded = true),直接跳過
if (method.getReturnType() == Object.class || parameter != null && parameter.excluded()) {
continue;
}
int i = name.startsWith("get") ? 3 : 2;
String prop = StringUtils.camelToSplitName(name.substring(i, i + 1).toLowerCase() + name.substring(i + 1), ".");
String key;
if (parameter != null && parameter.key().length() > 0) {
key = parameter.key();
} else {
key = prop;
}
// 利用反射調用 config 類中的 get/is 方法
Object value = method.invoke(config);
String str = String.valueOf(value).trim();
if (value != null && str.length() > 0) {
// 是否須要轉義,UTF-8
if (parameter != null && parameter.escaped()) {
str = URL.encode(str);
}
if (parameter != null && parameter.append()) {
String pre = parameters.get(Constants.DEFAULT_KEY + "." + key);
if (pre != null && pre.length() > 0) {
str = pre + "," + str;
}
pre = parameters.get(key);
if (pre != null && pre.length() > 0) {
str = pre + "," + str;
}
}
if (prefix != null && prefix.length() > 0) {
key = prefix + "." + key;
}
// key/value 添加到 parameters 集合
parameters.put(key, str);
} else if (parameter != null && parameter.required()) {
throw new IllegalStateException(config.getClass().getSimpleName() + "." + key + " == null");
}
// 方法名爲 getParameters();方法修飾符爲 public;方法形參個數爲0;返回類型爲 Map
} else if ("getParameters".equals(name)
&& Modifier.isPublic(method.getModifiers())
&& method.getParameterTypes().length == 0
&& method.getReturnType() == Map.class) {
Map<String, String> map = (Map<String, String>) method.invoke(config, new Object[0]);
if (map != null && map.size() > 0) {
String pre = (prefix != null && prefix.length() > 0 ? prefix + "." : "");
for (Map.Entry<String, String> entry : map.entrySet()) {
parameters.put(pre + entry.getKey().replace('-', '.'), entry.getValue());
}
}
}
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
}
複製代碼
該方法會調用當前類對象的 isXXX/getXXX 方法(非 getClass 方法;方法修飾符爲 public;形參個數爲 0;返回類型爲基本類型),獲取其返回值構造鍵值對添加到指定 map 集合中;同時也會解析 getParameters() 返回的結果,構造鍵值對注入到 map 集合中。
本BLOG上原創文章未經本人許可,不得用於商業用途及傳統媒體。網絡媒體轉載請註明出處,不然屬於侵權行爲。
https://juejin.im/post/5c2b7ab46fb9a049d236273b