1. 程式人生 > >Dubbo 擴充套件點載入機制:從 Java SPI 到 Dubbo SPI

Dubbo 擴充套件點載入機制:從 Java SPI 到 Dubbo SPI

SPI 全稱為 Service Provider Interface,是一種服務發現機制。當程式執行呼叫介面時,會根據配置檔案或預設規則資訊載入對應的實現類。所以在程式中並沒有直接指定使用介面的哪個實現,而是在外部進行裝配。
要想了解 Dubbo 的設計與實現,其中 Dubbo SPI 載入機制是必須瞭解的,在 Dubbo 中有大量功能的實現都是基於 Dubbo SPI 實現解耦,同時也使得 Dubbo 獲得如此好的可擴充套件性。

Java SPI

通過完成一個 Java SPI 的操作來了解它的機制。

  • 建立一個 AnimalService 介面及 category 方法
  • 建立一個實現類 Cat
  • 建立 META-INF/services 目錄,並在該目錄下建立一個檔案,檔名為 AnimalService 的全限定名作為檔名
  • 在檔案中新增實現類 Cat 的全限定名

Animal 介面

public interface AnimalService {
    void category();
}

Cat 實現類

public class Cat implements AnimalService {

    @Override
    public void category() {
        System.out.println("cat: Meow ~");
    }
}

在 META-INF/services 目錄下的 top.ytao.demo.spi.AnimalService 檔案中新增:

top.ytao.demo.spi.Cat

載入 SPI 的實現:

public class JavaSPITest {

    @Test
    public void javaSPI() throws Exception {
        ServiceLoader<AnimalService> serviceLoader = ServiceLoader.load(AnimalService.class);
        // 遍歷在配置檔案中已配置的 AnimalService 的所有實現類
        for (AnimalService animalService : serviceLoader) {
            animalService.category();
        }
    }

}

執行結果:

就這樣,一個 Java SPI 就實現完成了,通過 ServiceLoader.load 獲取載入所有介面已配置的介面實現類,然後可以遍歷找出需要的實現。

Dubbo SPI

本文 Dubbo 版本為2.7.5
Dubbo SPI 相較於 Java SPI 更為強大,並且都是由自己實現的一套 SPI 機制。其中主要的改進和優化:

  • 相對於 Java SPI 一次性載入所有實現,Dubbo SPI 是按需載入,只加載需要使用的實現類。同時帶有快取支援。
  • 更為詳細的擴充套件載入失敗資訊。
  • 增加了對擴充套件 IOC 和 AOP的支援。

Dubbo SPI 示例

Dubbo SPI 的配置檔案放在 META-INF/dubbo 下面,並且實現類的配置方式採用 K-V 的方式,key 為例項化物件傳入的引數,value 為擴充套件點實現類全限定名。例如 Cat 的配置檔案內容:

cat = top.ytao.demo.spi.Cat

Dubbo SPI 載入過程中,對 Java SPI 的目錄也是可以被相容的。

同時需要在介面上增加 @SPI 註解,@SPI 中可以指定 key 值,載入 SPI 如下:

public class DubboSPITest {

    @Test
    public void dubboSPI(){
        ExtensionLoader<AnimalService> extensionLoader = ExtensionLoader.getExtensionLoader(AnimalService.class);
        // 獲取擴充套件類實現
        AnimalService cat = extensionLoader.getExtension("cat");
        System.out.println("Dubbo SPI");
        cat.category();
    }

}

執行結果如下:

獲取 ExtensionLoader 例項

獲取 ExtensionLoader 例項是通過上面 getExtensionLoader 方法,具體實現程式碼:

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    if (type == null) {
        throw new IllegalArgumentException("Extension type == null");
    }
    // 檢查 type 必須為介面
    if (!type.isInterface()) {
        throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
    }
    // 檢查介面是否有 SPI 註解
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type (" + type +
                ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
    }
    // 快取中獲取 ExtensionLoader 例項
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        // 載入 ExtensionLoader 例項到快取中
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

上面獲取擴充套件類載入器過程主要是檢查傳入的 type 是否合法,以及從擴充套件類載入器快取中是否存在當前型別的介面,如果不存在則添加當前介面至快取中。
ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS 是擴充套件類載入器的快取,它是以介面作為 key, 擴充套件類載入器作為 value 進行快取。

獲取擴充套件類物件

獲取擴充套件類物件的方法ExtensionLoader#getExtension,在這裡完成擴充套件物件的快取及建立工作:

public T getExtension(String name) {
    if (StringUtils.isEmpty(name)) {
        throw new IllegalArgumentException("Extension name == null");
    }
    // 如果傳入的引數為 true ,則獲取預設擴充套件類物件操作
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    // 獲取擴充套件物件,Holder 裡的 value 屬性儲存著擴充套件物件例項
    final Holder<Object> holder = getOrCreateHolder(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;
}

獲取 holder 物件是從快取ConcurrentMap<String, Holder<Object>> cachedInstances中獲取,如果不存在,則以副檔名 key,建立一個 Holder 物件作為 value,設定到擴充套件物件快取。
如果是新建立的擴充套件物件例項,那麼 holder.get() 一定是 null ,擴充套件物件為空時,經過雙重檢查鎖,建立擴充套件物件。

建立擴充套件物件

建立擴充套件物件過程:

private T createExtension(String name) {
    // 從全部擴充套件類中,獲取當前副檔名對應的擴充套件類
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        // 從快取中獲取擴充套件例項,及設定擴充套件例項快取
        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 (CollectionUtils.isNotEmpty(wrapperClasses)) {
            for (Class<?> wrapperClass : wrapperClasses) {
                // 建立包裝擴充套件類例項,並向其注入依賴
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        // 初始化擴充套件物件
        initExtension(instance);
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                type + ") couldn't be instantiated: " + t.getMessage(), t);
    }
}

上面建立擴充套件過程中,裡面有個 Wrapper 類,這裡使用到裝飾器模式,該類是沒有具體的實現,而是把通用邏輯進行抽象。
建立這個過程是從所有擴充套件類中獲取當前副檔名對應對映關係的擴充套件類,以及向當前擴充套件物件注入依賴。

獲取所有擴充套件類:

private Map<String, Class<?>> getExtensionClasses() {
    // 獲取普通擴充套件類快取
    Map<String, Class<?>> classes = cachedClasses.get();
    // 如果快取中沒有,通過雙重檢查鎖後進行載入
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                // 載入全部擴充套件類
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

檢查普通擴充套件類快取是否為空,如果不為空則重新載入,真正載入擴充套件類在loadExtensionClasses中:


private static final String SERVICES_DIRECTORY = "META-INF/services/";

private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";

private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

private Map<String, Class<?>> loadExtensionClasses() {
    // 獲取 @SPI 上的預設副檔名
    cacheDefaultExtensionName();

    Map<String, Class<?>> extensionClasses = new HashMap<>();
    // 先載入 Dubbo 內部的擴充套件類, 通過 Boolean 值控制
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName(), true);
    // 由於 Dubbo 遷到 apache ,所以包名有變化,會替換之前的 alibaba 為 apache
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"), true);
    
    loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    return extensionClasses;
}

上面獲取 @SPI 副檔名,以及指定要載入的檔案。從上面靜態常量中,我們可以看到,Dubbo SPI 也是支援載入 Java SPI 的目錄,同時還載入 META-INF/dubbo/internal (該目錄為 Dubbo 的內部擴充套件類目錄),在 loadDirectory 載入目錄配置檔案。

    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst) {
        // 獲取檔案在專案中的路徑,如:META-INF/dubbo/top.ytao.demo.spi.AnimalService
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls = null;
            ClassLoader classLoader = findClassLoader();
            
            // 載入內部擴充套件類
            if (extensionLoaderClassLoaderFirst) {
                ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
                if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
                    urls = extensionLoaderClassLoader.getResources(fileName);
                }
            }
            
            // 載入當前 fileName 檔案
            if(urls == null || !urls.hasMoreElements()) {
                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 occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

這裡獲取檔名後加載所有同名檔案,然後迭代各個檔案,逐個載入檔案內容。

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
    try {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
            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('=');
                        // 如果當前行存在 "=",將 "=" 左右的值分開復制給 name 和 line
                        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);
                    }
                }
            }
        }
    } catch (Throwable t) {
        logger.error("Exception occurred when loading extension class (interface: " +
                type + ", class file: " + resourceURL + ") in " + resourceURL, t);
    }
}

上面程式碼完成檔案內容載入和解析,接下來通過 loadClass 載入擴充套件類。

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    // 檢查當前實現類是否實現了 type 介面
    if (!type.isAssignableFrom(clazz)) {
        throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                type + ", class line: " + clazz.getName() + "), class "
                + clazz.getName() + " is not subtype of interface.");
    }
    
    // 當前實現類是否有 Adaptive 註解
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        cacheAdaptiveClass(clazz);
    // 當前類是否為 Wrapper 包裝擴充套件類 
    } else if (isWrapperClass(clazz)) {
        cacheWrapperClass(clazz);
    } else {
        // 嘗試當前類是否有無參構造方法
        clazz.getConstructor();
        
        if (StringUtils.isEmpty(name)) {
            // 如果 name 為空,則獲取 clazz 的 @Extension 註解的值,如果註解值也沒有,則使用小寫類名
            name = findAnnotationName(clazz);
            if (name.length() == 0) {
                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
            }
        }

        String[] names = NAME_SEPARATOR.split(name);
        if (ArrayUtils.isNotEmpty(names)) {
            // 快取 副檔名和@Activate的快取
            cacheActivateClass(clazz, names[0]);
            for (String n : names) {
                // 快取 擴充套件類和副檔名的快取
                cacheName(clazz, n);
                // 將 擴充套件類和副檔名 儲存到extensionClasses 副檔名->擴充套件類 關係對映中
                saveInExtensionClass(extensionClasses, clazz, n);
            }
        }
    }
}

至此,getExtensionClasses() 載入擴充套件類方法分析完成,接下分析注入依賴 injectExtension() 方法。

private T injectExtension(T instance) {
    // 
    if (objectFactory == null) {
        return instance;
    }

    try {
        for (Method method : instance.getClass().getMethods()) {
            // 遍歷當前擴充套件類的全部方法,如果當前方法不屬於 setter 方法,
            // 即不是以 'set'開頭的方法名,引數不是一個的,該方法訪問級別不是 public 的,則不往下執行
            if (!isSetter(method)) {
                continue;
            }
            
            // 當前方法是否添加了不要注入依賴的註解
            if (method.getAnnotation(DisableInject.class) != null) {
                continue;
            }
            Class<?> pt = method.getParameterTypes()[0];
            // 判斷當前引數是否屬於 八個基本型別或void
            if (ReflectUtils.isPrimitives(pt)) {
                continue;
            }

            try {
                // 通過屬性 setter 方法獲取屬性名
                String property = getSetterProperty(method);
                // 獲取依賴物件
                Object object = objectFactory.getExtension(pt, property);
                if (object != null) {
                    // 設定依賴
                    method.invoke(instance, object);
                }
            } catch (Exception e) {
                logger.error("Failed to inject via method " + method.getName()
                        + " of interface " + type.getName() + ": " + e.getMessage(), e);
            }

        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}

通過遍歷擴充套件類所有方法,找到相對應的依賴,然後使用反射呼叫 settter 方法來進行設定依賴。
objectFactory 物件如圖:

其中找到相應依賴是在 SpiExtensionFactory 或 SpringExtensionFactory 中,同時,這兩個 Factory 儲存在 AdaptiveExtensionFactory 中進行維護。

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        // ......
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        // 通過遍歷匹配到 type->name 的對映
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}

以上是對 Dubbo SPI 擴充套件類簡單載入過程分析完成。

自適應載入機制

為 Dubbo 更加靈活的使一個介面不通過硬編碼載入擴充套件機制,而是通過使用過程中進行載入,Dubbo 的另一載入機制——自適應載入。
自適應載入機制使用 @Adaptive 標註:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    String[] value() default {};
}

Adaptive 的值是一個數組,可以配置多個 key。初始化時,遍歷所有 key 進行匹配,如果沒有則匹配 @SPI 的值。
當 Adaptive 註解標註在類上時,則簡單對應該實現。如果註解標註在介面方法上時,則會根據引數動態生成程式碼來獲取擴充套件點的實現。
類上註解處理還是比較好理解,方法上的註解載入相對比較有研讀性。通過呼叫ExtensionLoader#getAdaptiveExtension來進行獲取擴充套件實現。

public T getAdaptiveExtension() {
    // 獲取例項化物件快取
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
        if (createAdaptiveInstanceError != null) {
            throw new IllegalStateException("Failed to create adaptive instance: " +
                    createAdaptiveInstanceError.toString(),
                    createAdaptiveInstanceError);
        }
        // 雙重檢查鎖後建立自適應擴充套件
        synchronized (cachedAdaptiveInstance) {
            instance = cachedAdaptiveInstance.get();
            if (instance == null) {
                try {
                    // 建立自適應擴充套件
                    instance = createAdaptiveExtension();
                    cachedAdaptiveInstance.set(instance);
                } catch (Throwable t) {
                    createAdaptiveInstanceError = t;
                    throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                }
            }
        }
    }

    return (T) instance;
}

private T createAdaptiveExtension() {
    try {
        // 獲取自適應擴充套件後,注入依賴
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
    }
}

上面程式碼完成了擴充套件類物件是否存在快取中,如果不存在,則通過建立自適應擴充套件,並將例項注入依賴後,設定在例項化後的自適應擴充套件物件中。
其中getAdaptiveExtensionClass是比較核心的流程。

private Class<?> getAdaptiveExtensionClass() {
    // 載入全部擴充套件類
    getExtensionClasses();
    // 載入全部擴充套件類後,如果有 @Adaptive 標註的類,cachedAdaptiveClass 則一定不會為空
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    // 建立自適應擴充套件類
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

private Class<?> createAdaptiveExtensionClass() {
    // 生成自適應擴充套件程式碼
    String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
    // 獲取擴充套件類載入器
    ClassLoader classLoader = findClassLoader();
    // 獲取編譯器型別的實現類
    org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    // 編譯程式碼,返回該物件
    return compiler.compile(code, classLoader);
}

這裡完成的工作主要是,載入全部擴充套件類,代表所有擴充套件介面類的實現類,在其載入過程中,如果有 @Adaptive 標註的類,會儲存到 cachedAdaptiveClass 中。通過自動生成自適應擴充套件程式碼,並被編譯後,獲取擴充套件類例項化物件。
上面編譯器型別是可以指定的,通過 compiler 進行指定,例如:<dubbo:application name="taomall-provider" compiler="jdk" />,該編譯器預設使用 javassist 編譯器。

在 generate 方法中動態生成程式碼:

public String generate() {
    // 檢查當前擴充套件介面的方法上是否有 Adaptive 註解
    if (!hasAdaptiveMethod()) {
        throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
    }

    // 生成程式碼
    StringBuilder code = new StringBuilder();
    // 生成類的包名
    code.append(generatePackageInfo());
    // 生成類的依賴類
    code.append(generateImports());
    // 生成類的宣告資訊
    code.append(generateClassDeclaration());

    // 生成方法
    Method[] methods = type.getMethods();
    for (Method method : methods) {
        code.append(generateMethod(method));
    }
    code.append("}");

    if (logger.isDebugEnabled()) {
        logger.debug(code.toString());
    }
    return code.toString();
}

上面是生成類資訊的方法,生成設計原理是按照已設定好的模板,進行替換操作,生成類。具體資訊不程式碼很多,但閱讀還是比較簡單。
自適應載入機制,已簡單分析完,咋一眼看,非常複雜,但是瞭解整體結構和流程,再去細研的話,相對還是好理解。

總結

從 Dubbo 設計來看,其良好的擴充套件性,比較重要的一點是得益於 Dubbo SPI 載入機制。在學習它的設計理念,對可擴充套件性方面的編碼思考也有一定的啟發。

個人部落格: https://ytao.top
關注公眾號 【ytao】,更多原創好文

相關推薦

Dubbo 擴充套件載入機制 Java SPIDubbo SPI

SPI 全稱為 Service Provider Interface,是一種服務發現機制。當程式執行呼叫介面時,會根據配置檔案或預設規則資訊載入對應的實現類。所以在程式中並沒有直接指定使用介面的哪個實現,而是在外部進行裝配。 要想了解 Dubbo 的設計與實現,其中 Dubbo SPI 載入機制是必須瞭解

Dubbo——擴充套件載入機制

一、getAdaptiveExtension()方法:獲取擴充套件點實現類的介面卡類例項。從ExtensionLoader.cachedAdaptiveInstance變數中獲取,若為空,則呼叫createAdaptiveExtension方法建立介面卡類的例項,並將介面卡類的例項快取到cachedAda

Dubbo擴充套件載入機制

概述 來源:  Dubbo的擴充套件點載入從JDK標準的SPI(Service Provider Interface)擴充套件點發現機制加強而來。 Dubbo改進了JDK標準的SPI的以下問題: JDK標準的SPI會一次性例項化擴充套件點所有實現,如果有擴充套件實現

擴充套件載入機制(ExtensionLoader)

概述 來源: Dubbo的擴充套件點載入從JDK標準的SPI(Service Provider Interface)擴充套件點發現機制加強而來。 Dubbo改進了JDK標準的SPI的以下問題: JDK標準的SPI會一次性例項化擴充套件點所有實現,如果有

dubbo擴充套件機制

spring是如何啟動容器的 常見的一種在本地使用main方法啟動spring的方法 public static void main(String[] args) throws Exception { ClassPathXmlAp

Dubbo擴充套件機制分析

一、擴充套件點配置詳見我在《Java的SPI機制分析》文章中關於Dubbo的SPI機制的介紹,在此不再贅述。二、擴充套件點流程分析之SPI    下面以Container載入的過程為例,來說明SPI擴充套件的實現流程:所有加上@SPI註解的擴充套件點可以有不同的擴充套件,Co

人工智能的造血機制Q1財報讀懂百度AI矩陣如何運作

百度財報在人工智能到來,我們在最初被驚艷到之後,接下來的問題如期而至:AI到底如何工作?如何創造真正的價值?畢竟AI不能永遠是一種表演和遊戲,作為一種底層技術,它必須開始為人類服務,必須在商業世界中證明自己的必要性。事實上,相比於AI獨立成為一種與人類相近的智能,真實的AI更接近各行各業升級自身的機遇,甚至是

虛擬機器類載入機制載入時機

虛擬機器把描述類的資料從Class檔案載入到記憶體,並對資料進行校驗、轉換解析和初始化,最終形成可以被虛擬機器直接使用的Java型別,就是虛擬機器的類載入機制。 類載入時機 類的生命週期 一個類從載入進記憶體到卸載出記憶體,經歷圖示的7個階段: 載入——>驗證——>準備——

JVM 類載入機制編譯器常量與初始化

1. 前言 最近在研究JVM虛擬機器類載入機制的時候,我們瞭解到了類載入機制的生命週期以及在準備階段,JVM虛擬機器會對類的靜態變數進行初始化,這個時候只是會將靜態變數初始化為預設的初始值。對靜態變數的定義的初始化值,將會被封裝到clinit()方法中,直到初始化階段進行初始化。但是對於

CSDN博文週刊第4期Java角度深入理解Kotlin

CSDN每週都會產生大量的部落格文章,有一些優質的乾貨文章值得被更多人閱讀,分享。CSDN博文週刊會從過去一週博文中精心挑選一些優質文章來以饗讀者,陪伴大家度過一個愉快週末。 程式語言 1、從Java角度深入理解Kotlin Java開發者跟準備入門Kotlin開發可以讀讀,

載入機制全盤負責和雙親委託

“全盤負責”是指當一個ClassLoader裝載一個類時,除非顯示地使用另一個ClassLoader,則該類所依賴及引用的類也由這個CladdLoader載入。 例如,系統類載入器AppClassLoader載入入口類(含有main方法的類)時,會把main方法所依賴的類及

深入java static關鍵字 淺析java載入機制(解答java靜態方法或變數無法訪問非靜態資料)

想要清晰理解java語法,不瞭解java和jvm的機制是不行的,以前不理解java中用static修飾方法和變數為什麼不可以訪問非靜態方法和資料,現在明瞭,如果你也有相同的困惑,這篇部落格足以解惑,原創不易,轉載請宣告出處。 本文分為3大部分 static

Flutter 初嘗 Java 無縫過渡

準備階段 下載 Flutter SDK 新建 Flutter 資料夾,克隆 Flutter SDK: git clone -b beta https://github.com/flutter/flutter.git 配置 Flutter 環境

多執行緒等待喚醒機制wait()和sleep()的差別說起

1. wait():釋放資源,釋放鎖 sleep():釋放資源,不釋放鎖 wait():Object的方法,用在同步當中,是同步鎖的方法,以鎖控制執行緒 sleep():執行緒類Thread本身的靜態方法wait(),notify(),notifyAll()方法是用在同步當

Unity3d熱更新全書-資源載入(一)AssetBundle說起

本篇unity3d教程我們來繼續探討unity熱更新全書,先從資源載入說起,Unity3D動態下載資源,有沒有解?有,AssetBundle就是通用解,任何一本書都會花大幅篇章來介紹AssetBundle. 我們也來說說AssetBundle 我們試全面的分析一下

Java面試題】之類載入面試題分析Java載入機制

 “載入”(Loading)階段是“類載入”(Class Loading)過程的第一個階段,在此階段,虛擬機器需要完成以下三件事情:        1、 通過一個類的全限定名來獲取定義此類的二進位制位元組流。        2、 將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結

阿里巴巴面試題到java載入機制

首先很經典的阿里巴巴面試題 加上我自己的一些疑惑程式碼 public class Text { public static int k = 0; public final int k1 = 3; //自己加的 public static Text t1 = new Text("

Java虛擬機器載入機制詳解

    大家知道,我們的Java程式被編譯器編譯成class檔案,在class檔案中描述的各種資訊,最終都需要載入到虛擬機器記憶體才能執行和使用,那麼虛擬機器是如何載入這些class檔案的呢?在載入class檔案的過程中虛擬機器又幹了哪些事呢?今天我們來解密虛擬機器的類載入機制。

讀書筆記 ---- 《深入理解Java虛擬機器》---- 第6篇虛擬機器類載入機制

上一篇:類檔案結構:https://blog.csdn.net/pcwl1206/article/details/84197219 第6篇:虛擬機器類載入機制 1、概述 上一篇文章中講訴了Class檔案儲存格式的具體細節,在Class檔案中的描述的各種資訊,最終都要載入到虛擬機器中之後才

DubboSPI 機制(三)(Extension 擴充套件補充)

相關部落格: Dubbo的SPI機制(一)(Java的SPI) Dubbo的SPI機制(二)(Dubbo優化後的SPI實現)   Dubbo 的 Extension 主要是基於 SPI 思想實現的自己的 SPI 的工具。  在上一篇部落格(Dubbo的SP