dubbo原始碼解析之ExtensionLoader類(二)

鄧橋發表於2018-10-14

看懂dubbo原始碼的第一大攔路虎就是ExtensionLoader類,否則dubbo原始碼根本閱讀不下去。

ExtensionLoader作為SPI介面的物件建立工廠實現了以下功能:

  1. 從類路徑classpath下META-INF/dubbo/SPI介面全限定名稱檔案載入SPI實現類(dubbo定義的可擴充套件介面),方便使用者擴充套件dubbo的功能
  2. 實現類似spring ioc的功能,實現依賴物件的自動注入。
  3. 通過Wrapper包裝器模式擴充套件SPI實現類功能,實現類似aop的功能,參考ProtocolFilterWrapper類
  4. 建立SPI介面的自適應實現類,自適應實現類能夠根據介面方法的入參URL屬性呼叫具體實現類。

獲取指定型別的擴充套件點載入器例項方法如下

private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
 //返回SPI介面對應的ExtensionLoader<T>單例
 public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        if(!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        if(!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type + 
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }
        
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
    
    //只能載入宣告瞭SPI的擴充套件介面
    private static <T> boolean withExtensionAnnotation(Class<T> type) {
        return type.isAnnotationPresent(SPI.class);
    }
     
    private final ConcurrentMap<String, Holder<Object>> cachedInstances    = new ConcurrentHashMap<String, Holder<Object>>();
    //擴充套件載入器例項方法獲取指定名稱例項,使用cachedInstances
     做快取,使用Holder包裝例項物件避免重複建立例項。
    public T getExtension(String name) {
		if (name == null || name.length() == 0)
		    throw new IllegalArgumentException("Extension name == null");
		if ("true".equals(name)) {
		    return getDefaultExtension();
		}
		//通過獲取名稱對應的單例實現類
		Holder<Object> holder = cachedInstances.get(name);
		if (holder == null) {
		    cachedInstances.putIfAbsent(name, new Holder<Object>());
		    holder = cachedInstances.get(name);
		}
		Object instance = holder.get();
		if (instance == null) {
		    //雙重檢查模式避免重複建立物件
		    synchronized (holder) {
	            instance = holder.get();
	            if (instance == null) {
	                instance = createExtension(name);
	                holder.set(instance);
	            }
	        }
		}
		return (T) instance;
	}
	
	public class Holder<T> {
        private volatile T value;
        public void set(T value) {
            this.value = value;
        }
        public T get() {
            return value;
        }
    }
    private T createExtension(String name) {
        //getExtensionClasses從classpath的讀取META-INF/services/,
        //META-INF/dubbo/,META-INF/dubbo/internal/目錄 +
        //T.class.getName()檔案獲取讀取配置資訊,getExtensionClasses程式碼省略,感興趣自行檢視對應程式碼
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            //對應指定spi介面實現類的快取,使用ConcurrentMap實現
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            //實現spi例項的屬性依賴自動注入自適應實現類
            injectExtension(instance);
            //判斷該SPI介面有沒有包裝器實現類,如果存在建立包裝器鏈實現類似AOP方法攔截的功能,具體可以參考ProtocolFilterWrapper類
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && wrapperClasses.size() > 0) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                    type + ")  could not be instantiated: " + t.getMessage(), t);
        }
    }
    //迴圈呼叫物件的setter方法注入依賴物件
    private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            String property = method.getName().length() > 3 ? 
                            method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
    //這裡是重點,預設會使用SpiExtensionFactory獲取對應屬性的實現類,實際上返回的是自適應實現類
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }
    
public class SpiExtensionFactory implements ExtensionFactory {

    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            //如果指定spi有多個擴充套件實現,會返回一個自適應的spi實現類
            if (loader.getSupportedExtensions().size() > 0) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

}
//返回自適應的介面T的實現類
public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if(createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            instance = createAdaptiveExtension();
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            }
            else {
                throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }

        return (T) instance;
    }
    
private T createAdaptiveExtension() {
    try {
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
    }
}    
複製程式碼

getAdaptiveExtensionClass()方法運用了程式碼生成技術,非常晦澀,來看下com.alibaba.dubbo.rpc.Protocol SPI介面生成的自適應實現類程式碼如下 Protocol的介面定義如下,SPI介面都要使用@SPI註解宣告,值為預設實現類對應的名稱

@SPI("dubbo")
public interface Protocol {
    
    /**
     * 獲取預設埠,當使用者沒有配置埠時使用。
     * 
     * @return 預設埠
     */
    int getDefaultPort();

    /**
     * 暴露遠端服務:<br>
     * 1. 協議在接收請求時,應記錄請求來源方地址資訊:RpcContext.getContext().setRemoteAddress();<br>
     * 2. export()必須是冪等的,也就是暴露同一個URL的Invoker兩次,和暴露一次沒有區別。<br>
     * 3. export()傳入的Invoker由框架實現並傳入,協議不需要關心。<br>
     * 
     * @param <T> 服務的型別
     * @param invoker 服務的執行體
     * @return exporter 暴露服務的引用,用於取消暴露
     * @throws RpcException 當暴露服務出錯時丟擲,比如埠已佔用
     */
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    /**
     * 引用遠端服務:<br>
     * 1. 當使用者呼叫refer()所返回的Invoker物件的invoke()方法時,協議需相應執行同URL遠端export()傳入的Invoker物件的invoke()方法。<br>
     * 2. refer()返回的Invoker由協議實現,協議通常需要在此Invoker中傳送遠端請求。<br>
     * 3. 當url中有設定check=false時,連線失敗不能丟擲異常,並內部自動恢復。<br>
     * 
     * @param <T> 服務的型別
     * @param type 服務的型別
     * @param url 遠端服務的URL地址
     * @return invoker 服務的本地代理
     * @throws RpcException 當連線服務提供方失敗時丟擲
     */
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

    /**
     * 釋放協議:<br>
     * 1. 取消該協議所有已經暴露和引用的服務。<br>
     * 2. 釋放協議所佔用的所有資源,比如連線和埠。<br>
     * 3. 協議在釋放後,依然能暴露和引用新的服務。<br>
     */
    void destroy();

}
複製程式碼

使用程式碼生成技術生成的自適應動態代理類

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adpative 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.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
		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);
	}
	public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class {
		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);
	}
}
複製程式碼

這個代理的作用是從方法入參獲取url物件(第一篇文章介紹該物件用於呼叫承載配置資訊)獲取Protocol請求協議資訊,然後代理到對應協議的實現類去處理請求。

綜上所述,ExtensionLoader實現了從類載入路徑發現指定SPI介面實現類列表, 運用反射建立介面實現類以及依賴注入實現類對應屬性。對外暴露獲取指定名稱例項方法和基於代理建立的自適應例項,其中自適應例項實現非常巧妙,能夠根據請求配置資訊URL動態載入實現類,實現按需載入。 dubbo原始碼比較晦澀難懂的一大原因就是使用了比較多的程式碼生成,然後自動編譯技術,程式碼可讀性不高,完全可以自己實現一個預設的代理類,缺點是每個SPI介面可能都需要實現這樣一個沒有任何邏輯的代理類,程式碼重複性較高。

相關文章