Spring Cloud Alibaba 教程 | Dubbo(四):獲取擴展類

獲取自適應擴展實例

通過調用ExtensionLoader類的getAdaptiveExtension()方法可以獲取到自適應擴展類實例對象。

public T getAdaptiveExtension() {
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
    	......//省略部分代碼
	    instance = createAdaptiveExtension();
        cachedAdaptiveInstance.set(instance);
    }
    return (T) instance;
}

前面的文章已經介紹過ExtensionLoader的私有緩存,其中cachedAdaptiveInstance就是用來緩存自適應擴展類實例的。
若緩存沒有則執行createAdaptiveExtension()方法。

private T createAdaptiveExtension() {
    try {
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
    }
}

創建自適應擴展類實例通過getAdaptiveExtensionClass()獲取到Class去newInstance()創建實例對象,接着執行injectExtension(T instance)方法,該方法實現了Dubbo SPI的IOC特性,我們稍後解析。

private Class<?> getAdaptiveExtensionClass() {
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

獲取Class是通過調用getExtensionClasses(),獲取到之後會將其緩存到cachedAdaptiveClass,從代碼看到getExtensionClasses()是沒有返回值的,不過在該方法的內部執行過程中會給cachedAdaptiveClass賦值。

private Map<String, Class<?>> getExtensionClasses() {
    Map<String, Class<?>> classes = cachedClasses.get();
    if (classes == null) {
     classes = loadExtensionClasses();
        cachedClasses.set(classes);
    }
    return classes;
}

getExtensionClasses()方法只要是處理普通擴展類的加載和緩存,並沒有直接處理自適應擴展類,在執行loadExtensionClasses()加載擴展類的時候會將擴展配置文件中的所有類型的擴展實現類都加載出來,包括普通擴展類、自適應擴展類和包裝擴展類。

private Map<String, Class<?>> loadExtensionClasses() {
	......//代碼省略
   	Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    loadDirectory(extensionClasses, DUBBO_DIRECTORY);
    loadDirectory(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}

加載所有的擴展實現類是通過加載DUBBO_INTERNAL_DIRECTORY目錄下的擴展配置文件,該目錄值就是META-INF/dubbo/internal/,另外兩個目錄在Dubbo框架中沒有用上。
加載的過程會順序執行下面三個方法:

  • loadDirectory()
  • loadResource()
  • loadClass()
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
        String fileName = dir + type.getName();
        try {
            Enumeration<java.net.URL> urls;
            ClassLoader classLoader = findClassLoader();
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

loadDirectory()方法首先通過dir + type.getName()組成filename,然後通過ClassLoader獲取到資源文件,獲取到資源文件後執行loadResource()方法加載這些資源文件。

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
            try {
                String line;
                while ((line = reader.readLine()) != null) {
                    final int ci = line.indexOf('#');
                    if (ci >= 0) line = line.substring(0, ci);
                    line = line.trim();
                    if (line.length() > 0) {
                        try {
                            String name = null;
                            int i = line.indexOf('=');
                            if (i > 0) {
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                            }
                        } catch (Throwable t) {
                            IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                            exceptions.put(line, e);
                        }
                    }
                }
            } finally {
                reader.close();
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }

加載的過程就是將擴展配置一行行讀取出來,讀取之後賦值給line,最後按照“=”分割成name和line。例如讀取到配置
printService1=com.alibaba.dubbo.demo.spi.impl.PrintServiceImpl1後name就是printService1,line就是com.alibaba.dubbo.demo.spi.impl.PrintServiceImpl1。
每讀取到一行擴展配置,就執行一次loadClass()方法。

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
	......//省略部分代碼
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        if (cachedAdaptiveClass == null) {
            cachedAdaptiveClass = clazz;
        }
    } else if (isWrapperClass(clazz)) {
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }
        wrappers.add(clazz);
    } else {
        for (String n : names) {
            if (!cachedNames.containsKey(clazz)) {
                cachedNames.put(clazz, n);
            }
            Class<?> c = extensionClasses.get(n);
            if (c == null) {
                extensionClasses.put(n, clazz);
            }
        }
   }
}

loadClass()方法代碼很多,我提取了最重要的代碼,如果Clazz是一個包含@Adaptive註解的類就賦值給cachedAdaptiveClass,即記載了自適應配置類。如果isWrapperClass(clazz)是true,則該Clazz是一個包裝類,檢測方法就是看該擴展類是否存在一個參數爲該擴展接口類型的構造方法,有就是一個包裝擴展類,包裝擴展類的Class緩存在cachedWrapperClasses。最後就是處理普通擴展類,分別賦值給cachedNames和extensionClasses。

這樣整個擴展配置就加載完成了,loadExtensionClasses()方法負責了加載指定type類型所有的擴展配置。

private T createAdaptiveExtension() {
    try {
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
    }
}

回到開始的createAdaptiveExtension()方法,通過getAdaptiveExtensionClass().newInstance()創建出了自適應擴展配置實例,緊接着調用injectExtension()方法,該方法注入該實例依賴的其他擴展類。

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) : "";
	                     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;
}

該方法先獲取該擴展類裏面所有以set開頭只有一個參數且是public的方法,接着通過執行objectFactory.getExtension(Class type, String name)獲取到普通擴展類實例,最後執行method.invoke(instance, object)注入該實例。關於objectFactory.getExtension(Class type, String name)內容下面會詳細分析。

獲取動態自適應擴展實例

當一個擴展接口的方法包含了@Adaptive註解時,可以通過參數URL動態指定使用哪個擴展配置類。

修改之前的擴展接口PrintService,在方法printInfo()添加@Adaptive註解

@SPI("printService1")
public interface PrintService {
    @Adaptive
    public void printInfo(URL url,String message);
}

修改兩個擴展實現類PrintServiceImpl1和PrintServiceImpl2

public class PrintServiceImpl1 implements PrintService {
    public PrintServiceImpl1() {
        System.out.println("PrintServiceImpl1 construct");
    }
    @Override
    public void printInfo(URL url, String message) {
        System.out.println("PrintServiceImpl1 message:"+message);
    }
}
public class PrintServiceImpl2 implements PrintService {
    public PrintServiceImpl2() {
        System.out.println("PrintServiceImpl2 construct");
    }
    @Override
    public void printInfo(URL url, String message) {
        System.out.println("PrintServiceImpl2 message:"+message);
    }
}

修改測試代碼,通過URL參數控制選擇的擴展實現類:map.put(“print.service”,“printService1”)

public class Test {
    public static void main(String[] args) {
        PrintService printService =
                ExtensionLoader.getExtensionLoader(PrintService.class)
                        .getAdaptiveExtension();
        Map<String, String> map = new HashMap<String, String>();
        map.put("print.service","printService1");//通過URL指定使用的擴展實現類
        URL url = new URL("protocol", "1.2.3.4", 12345, "path", map);
        printService.printInfo(url,"hello dubbo");
    }
}

斷點執行該代碼,你會發現獲取到的PrintService實例類型是PrintService$Adaptive,而這個類我們並沒有創建,是Dubbo框架自己在運行時動態創建編譯的。

跟蹤代碼發現該類是通過createAdaptiveExtensionClass()方法生成的

    private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

因爲此時cachedAdaptiveClass值是null(PrintService沒有自適應擴展類,如果有那麼URL將不起作用),所以會執行到createAdaptiveExtensionClass()方法

private Class<?> createAdaptiveExtensionClass() {
    String code = createAdaptiveExtensionClassCode();
    ClassLoader classLoader = findClassLoader();
    com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    return compiler.compile(code, classLoader);
}

createAdaptiveExtensionClassCode()負責創建PrintService$Adaptive代碼,賦值給code,將code代碼拷貝出來即可得到該類源代碼:

package com.alibaba.dubbo.demo.spi;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class PrintService$Adaptive implements com.alibaba.dubbo.demo.spi.PrintService {

	public void printInfo(com.alibaba.dubbo.common.URL arg0, java.lang.String arg1) {
		if (arg0 == null) throw new IllegalArgumentException("url == null");
		com.alibaba.dubbo.common.URL url = arg0;	
		String extName = url.getParameter("print.service", "printService1");
		if(extName == null) 
			throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.demo.spi.PrintService) name from url(" + url.toString() + ") use keys([print.service])");
			
			com.alibaba.dubbo.demo.spi.PrintService extension 
				= (com.alibaba.dubbo.demo.spi.PrintService)ExtensionLoader
					.getExtensionLoader(com.alibaba.dubbo.demo.spi.PrintService.class).getExtension(extName);
			extension.printInfo(arg0, arg1);
		}
	}
}

可以看到通過URL參數獲取到extName,接着通過ExtensionLoader獲取到對應的普通擴展類實例,通過該實例執行真正的接口實現方法extension.printInfo(arg0, arg1)。

回到剛纔的createAdaptiveExtensionClass()方法,賦值給code之後,緊接着就是通過ExtensionLoader獲取Compiler實例,通過它執行代碼編譯工作。
Compiler也是一個擴展接口,有兩個擴展實現類JavassistCompiler和JdkCompiler,默認使用JavassistCompiler,編譯的實現過程這裏就不展開討論了,感興趣的讀者可以自己去了解。

最終執行結果:

PrintServiceImpl1 construct
PrintServiceImpl1 message:hello dubbo

通過URL的map參數動態指定擴展實現類時,設置map.put(key,value)參數值時,key的值爲擴展接口名去掉駝峯,並在除開頭首字母以外原來駝峯名稱位置間加入一個點號“.”分隔,例如擴展接口是AaaBbbCcc,那麼map的key就應該是aaa.bb.ccc,value就是目標擴展配置類名稱。

獲取普通擴展實例

public T getExtension(String name) {
   ......//省略部分代碼
   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) {
        instance = createExtension(name);
        holder.set(instance);
   }
   return (T) instance;
}

通過執行getExtension()方法傳入指定的name可以獲取到普通擴展實例,普通擴展實例緩存在cachedInstances,通過Holder包裝添加getter和setter方法,如果緩存沒有該實例,則調用createExtension(name)創建擴展實例。

private T createExtension(String name) {
    Class<?> clazz = getExtensionClasses().get(name);
    T instance = (T) EXTENSION_INSTANCES.get(clazz);
    if (instance == null) {
        EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
        instance = (T) EXTENSION_INSTANCES.get(clazz);
    }
    injectExtension(instance);
    Set<Class<?>> wrapperClasses = cachedWrapperClasses;
    if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
        for (Class<?> wrapperClass : wrapperClasses) {
            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
        }
    }
    return instance;
}

該方法首先執行getExtensionClasses()獲取所有的普通擴展Class,該方法在上面已經解析過,獲取到Class之後創建實例並緩存到EXTENSION_INSTANCES,接着執行injectExtension(instance)注入依賴的其他擴展類。
如果該擴展接口包含了包裝擴展類,那麼將循環所有的包裝類,依次創建包裝擴展類實例(將instance 作爲構造參數傳入),創建後的實例又賦值給instance,這樣最後返回的就是該擴展接口的最後一個包裝擴展類實例,這實際上是通過使用裝飾器設計模式,讓Dubbo SPI達到AOP控制特性。
當然每個包裝擴展類實例同樣需要通過injectExtension()注入依賴擴展。

ExtensionFactory

在injectExtension()方法,我們是通過執行Object object = objectFactory.getExtension(pt, property)獲取到指定的擴展配置實例的。ExtensionFactory是一個擴展接口

@SPI
public interface ExtensionFactory {
    <T> T getExtension(Class<T> type, String name);
}

每一個擴展接口(除了ExtensionFactory本身)在加載其對應的ExtensionLoader實例時都擁有一個ExtensionFactory實例對象,它真正目標實例是AdaptiveExtensionFactory。

ExtensionFactory擁有三個實現類:

  • SpiExtensionFactory
  • SpringExtensionFactory
  • AdaptiveExtensionFactory
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }
}

AdaptiveExtensionFactory負責管理 SpiExtensionFactory和SpringExtensionFactory,
在構造方法中獲取到ExtensionLoader實例後通過調用loader.getSupportedExtensions()獲取到所有的的普通擴展類class,最後執行list.add(loader.getExtension(name))獲取到SpiExtensionFactory和SpringExtensionFactory,並存儲到list。

所以執行getExtension(Class type, String name),實際上是通過SpiExtensionFactory或者SpringExtensionFactory獲取擴展實例,優先從SpiExtensionFactory獲取。

public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

}

SpiExtensionFactory實現比較簡單,不檢測name參數,直接通過調用擴展接口對應的ExtensionLoader實例獲取自適應擴展類實例。

public class SpringExtensionFactory implements ExtensionFactory {

    private static final Set<ApplicationContext> contexts = new ConcurrentHashSet<ApplicationContext>();

    public static void addApplicationContext(ApplicationContext context) {
        contexts.add(context);
    }

    public static void removeApplicationContext(ApplicationContext context) {
        contexts.remove(context);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T getExtension(Class<T> type, String name) {
        for (ApplicationContext context : contexts) {
            if (context.containsBean(name)) {
                Object bean = context.getBean(name);
                if (type.isInstance(bean)) {
                    return (T) bean;
                }
            }
        }
        return null;
    }

}

SpringExtensionFactory通過注入ApplicationContext應用上下文,從spring容器中獲取對應的實例。關於何時注入ApplicationContext的問題後面會介紹到。

關注公衆號瞭解更多原創博文

Alt

發佈了110 篇原創文章 · 獲贊 116 · 訪問量 90萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章