dubbo源碼(二)-SPI源碼詳解

上篇我們簡(jiǎn)單講了dubbo SPI的使用,沒(méi)有太多代碼,一個(gè)接口和兩個(gè)實(shí)現(xiàn)類(lèi),還有一個(gè)配置文件和測(cè)試類(lèi)。接口和實(shí)現(xiàn)類(lèi)沒(méi)有什么講的,因此入口就從測(cè)試類(lèi)開(kāi)始。

測(cè)試類(lèi)代碼如下

public static void main(String[] args) {
        ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);

        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();

        /**
         * dubbo SPI支持默認(rèn)設(shè)計(jì)
         * 配置 {@link com.alibaba.dubbo.common.extension.SPI}的value屬性即可
         */
        Robot bumblebee = extensionLoader.getDefaultExtension();
        bumblebee.sayHello();
    }

我們首先通過(guò) ExtensionLoadergetExtensionLoader 方法獲取一個(gè) ExtensionLoader 實(shí)例,然后再通過(guò) ExtensionLoadergetExtension 方法獲取拓展類(lèi)對(duì)象。

  1. 我們先看一下ExtensionLoadergetExtensionLoader方法,getExtensionLoader如下:
    @SuppressWarnings("unchecked")
    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        // 非空判斷
        if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        }

        // SPI 擴(kuò)展點(diǎn)只能是接口
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type (" + type + ") is not interface!");
        }

        // 判斷是否有 SPI 擴(kuò)展點(diǎn)注解
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type (" + type +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }

        // 從緩存中獲取對(duì)應(yīng)的ExtensionLoader
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);

        // 如果緩存中不存在,創(chuàng)建并保存到緩存
        if (loader == null) {
            // 先在緩存中創(chuàng)建
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            // 獲取
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

這里的代碼比較簡(jiǎn)單,if的判斷是check合法性;然后會(huì)先根據(jù)type在緩存EXTENSION_LOADERS中獲取ExtensionLoader,如果沒(méi)有獲取到會(huì)創(chuàng)建,這段代碼寫(xiě)的比較優(yōu)雅,簡(jiǎn)單的實(shí)現(xiàn)了線(xiàn)程安全
需要說(shuō)明的是EXTENSION_LOADERS是一個(gè)靜態(tài)變量,定義如下

    /**
     * ExtensionLoader的緩存,key為Class, value為ExtensionLoader的實(shí)例
     */
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

到這里ExtensionLoader.getExtensionLoader(Robot.class)方法已經(jīng)講解完,主要是獲取ExtensionLoader對(duì)象,關(guān)于ExtensionLoader的構(gòu)造方法的講解,放到最后講解。

  1. 接下來(lái)我們看一下測(cè)試類(lèi)代碼中的extensionLoader.getExtension("optimusPrime");方法,extensionLoader.getExtension代碼如下:
    /**
     * Find the extension with the given name. If the specified name is not found, then {@link IllegalStateException}
     * will be thrown.
     */
    @SuppressWarnings("unchecked")
    public T getExtension(String name) {
        // 參數(shù)校驗(yàn)
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }

        // name為true時(shí),返回默認(rèn)擴(kuò)展點(diǎn), getDefaultExtension為獲取默認(rèn)擴(kuò)展點(diǎn)
        if ("true".equals(name)) {
            return getDefaultExtension();
        }

        // 根據(jù)擴(kuò)展點(diǎn)name從緩存 cachedInstances 中獲取擴(kuò)展點(diǎn)的instance
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<Object>());
            holder = cachedInstances.get(name);
        }

        // 獲取holder中的instance
        Object instance = holder.get();
        // 這里用到了double-check locking
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    // 創(chuàng)建拓展實(shí)例
                    instance = createExtension(name);
                    // 設(shè)置實(shí)例到 holder 中
                    holder.set(instance);
                }
            }
        }

        // 返回對(duì)應(yīng)的實(shí)例
        return (T) instance;
    }

這段代碼不太難,不過(guò)也是一個(gè)線(xiàn)程安全的,使用了double-check locking和Holder,疑問(wèn)為啥要這樣做,使用了double-check不就行了,為啥要用Holder?
現(xiàn)在說(shuō)一下代碼,先是參數(shù)的校驗(yàn),然后判斷如果name是true,調(diào)用getDefaultExtension方法,返回默認(rèn)的擴(kuò)展點(diǎn);否則調(diào)用createExtension方法創(chuàng)建擴(kuò)展點(diǎn)示例。

現(xiàn)在我們先看一下createExtension方法:

    @SuppressWarnings("unchecked")
    private T createExtension(String name) {
        // 從配置文件中加載所有的拓展類(lèi),可得到“配置項(xiàng)名稱(chēng)”到“配置類(lèi)”的映射關(guān)系表
        Class<?> clazz = getExtensionClasses().get(name);

        // 非空判斷
        if (clazz == null) {
            throw findException(name);
        }

        // 開(kāi)始創(chuàng)建instance
        try {

            // 根據(jù)class獲取緩存 EXTENSION_INSTANCES 中對(duì)應(yīng)的實(shí)例
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                // 通過(guò)反射創(chuàng)建實(shí)例
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }

            // 向?qū)嵗凶⑷胍蕾?lài), dubbo的IOC
            injectExtension(instance);

            // wrapperClasses處理
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                // 循環(huán)創(chuàng)建 Wrapper 實(shí)例
                for (Class<?> wrapperClass : wrapperClasses) {
                    // 將當(dāng)前 instance 作為參數(shù)傳給 Wrapper 的構(gòu)造方法,并通過(guò)反射創(chuàng)建 Wrapper 實(shí)例。
                    // 然后向 Wrapper 實(shí)例中注入依賴(lài),最后將 Wrapper 實(shí)例再次賦值給 instance 變量
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }

            // 返回對(duì)應(yīng)的instance
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

createExtension方法涉及的代碼較多,包含了如下的步驟:

  1. 通過(guò) getExtensionClasses 獲取所有的拓展類(lèi)
  2. 通過(guò)反射創(chuàng)建拓展對(duì)象
  3. 向拓展對(duì)象中注入依賴(lài)
  4. 將拓展對(duì)象包裹在相應(yīng)的 Wrapper 對(duì)象中

現(xiàn)在我們先看步驟一,分析getExtensionClasses方法。我們?cè)谕ㄟ^(guò)名稱(chēng)獲取拓展類(lèi)之前,首先需要根據(jù)配置文件解析出拓展項(xiàng)名稱(chēng)到拓展類(lèi)的映射關(guān)系表(Map<名稱(chēng), 拓展類(lèi)>),之后再根據(jù)拓展項(xiàng)名稱(chēng)從映射關(guān)系表中取出相應(yīng)的拓展類(lèi)即可。getExtensionClasses代碼如下:

 private Map<String, Class<?>> getExtensionClasses() {
        // 從緩存 cachedClasses 中獲取
        Map<String, Class<?>> classes = cachedClasses.get();
        // 雙重檢查
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    // 加載擴(kuò)展點(diǎn)classes
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

這里也是先檢查緩存,若緩存未命中,則通過(guò) synchronized 加鎖。加鎖后再次檢查緩存,并判空。此時(shí)如果 classes 仍為 null,則通過(guò) loadExtensionClasses 加載拓展類(lèi)。下面分析 loadExtensionClasses 方法的邏輯。

    private Map<String, Class<?>> loadExtensionClasses() {
        // 獲取 SPI 注解,這里的 type 變量是在調(diào)用 getExtensionLoader 方法時(shí)傳入的
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            // 獲取 SPI 注解的value值
            String value = defaultAnnotation.value();
            if ((value = value.trim()).length() > 0) {
                // 對(duì) SPI 注解內(nèi)容進(jìn)行切分
                String[] names = NAME_SEPARATOR.split(value);
                // 檢測(cè) SPI 注解內(nèi)容是否合法,不合法則拋出異常
                if (names.length > 1) {
                    throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }
                // 這是默認(rèn) cachedDefaultName 名稱(chēng)
                if (names.length == 1) {
                    cachedDefaultName = names[0];
                }
            }
        }

        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        // 加載指定文件夾下的配置文件
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadDirectory(extensionClasses, DUBBO_DIRECTORY);
        loadDirectory(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }

loadExtensionClasses 方法總共做了兩件事情,一是對(duì) SPI 注解進(jìn)行解析,二是調(diào)用 loadDirectory 方法加載指定文件夾配置文件。SPI 注解解析過(guò)程比較簡(jiǎn)單,無(wú)需多說(shuō)。下面我們來(lái)看一下 loadDirectory 做了哪些事情。

    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
        // fileName = 文件夾路徑 + type 全限定名
        String fileName = dir + type.getName();

        try {
            Enumeration<URL> urls;
            // 獲取classLoader
            ClassLoader classLoader = findClassLoader();
            // 根據(jù)文件名加載所有的同名文件
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {
                //url中的elements
                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 方法先通過(guò) classLoader 獲取所有資源鏈接,然后再通過(guò) loadResource 方法加載資源。我們繼續(xù)跟下去,看一下 loadResource 方法的實(shí)現(xiàn)。

    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;
                // 按行讀取配置內(nèi)容
                while ((line = reader.readLine()) != null) {
                    // 定位 # 字符
                    final int ci = line.indexOf('#');
                    if (ci >= 0) {
                        // 截取 # 之前的字符串,# 之后的內(nèi)容為注釋?zhuān)枰雎?                        line = line.substring(0, ci);
                    }
                    line = line.trim();
                    if (line.length() > 0) {
                        try {
                            String name = null;
                            int i = line.indexOf('=');
                            if (i > 0) {
                                // 以等于號(hào) = 為界,截取鍵與值
                                //擴(kuò)展點(diǎn)name
                                name = line.substring(0, i).trim();
                                //擴(kuò)展點(diǎn)class路徑
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                                // 加載類(lèi),并通過(guò) loadClass 方法對(duì)類(lèi)進(jìn)行緩存
                                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 occurred when loading extension class (interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }

loadResource 方法用于讀取和解析配置文件,并通過(guò)反射加載類(lèi),最后調(diào)用 loadClass 方法進(jìn)行其他操作。loadClass 方法用于主要用于操作緩存,該方法的邏輯如下:


    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        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.");
        }

        // 檢測(cè)目標(biāo)類(lèi)上是否有 Adaptive 注解
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            if (cachedAdaptiveClass == null) {
                // 設(shè)置 cachedAdaptiveClass緩存
                cachedAdaptiveClass = clazz;
            } else if (!cachedAdaptiveClass.equals(clazz)) {
                throw new IllegalStateException("More than 1 adaptive class found: "
                        + cachedAdaptiveClass.getClass().getName()
                        + ", " + clazz.getClass().getName());
            }
        // 檢測(cè) clazz 是否是 Wrapper 類(lèi)型
        } else if (isWrapperClass(clazz)) {
            Set<Class<?>> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<>();
                wrappers = cachedWrapperClasses;
            }
            // 存儲(chǔ) clazz 到 cachedWrapperClasses 緩存中
            wrappers.add(clazz);
        // 程序進(jìn)入此分支,表明 clazz 是一個(gè)普通的拓展類(lèi)
        } else {
            // 檢測(cè) clazz 是否有默認(rèn)的構(gòu)造方法,如果沒(méi)有,則拋出異常
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                // 如果 name 為空,則嘗試從 Extension 注解中獲取 name,或使用小寫(xiě)的類(lèi)名作為 name
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }
            // 切分 name
            String[] names = NAME_SEPARATOR.split(name);
            if (names != null && names.length > 0) {
                // 如果類(lèi)上有 Activate 注解,則使用 names 數(shù)組的第一個(gè)元素作為鍵,
                // 存儲(chǔ) name 到 Activate 注解對(duì)象的映射關(guān)系
                Activate activate = clazz.getAnnotation(Activate.class);
                if (activate != null) {
                    cachedActivates.put(names[0], activate);
                }

                for (String n : names) {
                    if (!cachedNames.containsKey(clazz)) {
                        // 存儲(chǔ) Class 到名稱(chēng)的映射關(guān)系
                        cachedNames.put(clazz, n);
                    }
                    Class<?> c = extensionClasses.get(n);
                    if (c == null) {
                        // 存儲(chǔ)名稱(chēng)到 Class 的映射關(guān)系
                        extensionClasses.put(n, clazz);
                    } else if (c != clazz) {
                        throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                    }
                }
            }
        }
    }

loadClass 方法操作了不同的緩存,比如 cachedAdaptiveClass、cachedWrapperClassescachedNames 等等。除此之外,該方法沒(méi)有其他什么邏輯了。

關(guān)于Adaptive注解和Activate注解,這里先不作說(shuō)明,后續(xù)會(huì)有說(shuō)明。

回到createExtension方法,通過(guò) getExtensionClasses 獲取所有的拓展類(lèi)這步工作已經(jīng)講解完成了,接著就是通過(guò)反射創(chuàng)建拓展對(duì)象,這個(gè)比較比較簡(jiǎn)單,通過(guò)反射創(chuàng)建,不用講解。

如果沒(méi)有使用到 Dubbo IOC 與 AOP 話(huà),getExtension方法整體就結(jié)束了,該方法就返回了擴(kuò)展點(diǎn)的instance,本文中的測(cè)試類(lèi)的代碼沒(méi)有使用了Dubbo IOC 與 AOP,因此就結(jié)束了。

  1. 現(xiàn)在開(kāi)始講解createExtension方法調(diào)用的injectExtension方法,該方法是Dubbo IOC的實(shí)現(xiàn)
    private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                // 遍歷目標(biāo)類(lèi)上的所有方法
                for (Method method : instance.getClass().getMethods()) {
                    // 檢查方法是否已set 開(kāi)頭,且方法只有一個(gè)參數(shù),且方法訪(fǎng)問(wèn)級(jí)別是public
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        /**
                         * Check {@link DisableInject} to see if we need auto injection for this property
                         */
                        // 如果方法有 DisableInject 注解修飾,則忽略
                        if (method.getAnnotation(DisableInject.class) != null) {
                            continue;
                        }

                        // 獲取 setter 方法參數(shù)類(lèi)型
                        Class<?> pt = method.getParameterTypes()[0];
                        if (ReflectUtils.isPrimitives(pt)) {
                            continue;
                        }
                        try {
                            // 獲取屬性名,比如 setName 方法對(duì)應(yīng)屬性名 name
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            // 從 ObjectFactory 中獲取依賴(lài)對(duì)象
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                // 通過(guò)反射調(diào)用 setter 方法設(shè)置依賴(lài)
                                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;
    }

objectFactory 變量的類(lèi)型為 AdaptiveExtensionFactory,AdaptiveExtensionFactory 內(nèi)部維護(hù)了一個(gè) ExtensionFactory 列表,用于存儲(chǔ)其他類(lèi)型的 ExtensionFactory。Dubbo 目前提供了兩種 ExtensionFactory,分別是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于創(chuàng)建自適應(yīng)的拓展,后者是用于從 Spring 的 IOC 容器中獲取所需的拓展。這兩個(gè)類(lèi)的類(lèi)的代碼不是很復(fù)雜,這里就不一一分析了。

Dubbo IOC 目前僅支持 setter 方式注入,總的來(lái)說(shuō),邏輯比較簡(jiǎn)單易懂。

  1. 關(guān)于測(cè)試類(lèi)中的getDefaultExtension方法,獲取默認(rèn)擴(kuò)展點(diǎn),getDefaultExtension代碼如下:
    /**
     * Return default extension, return <code>null</code> if it's not configured.
     */
    public T getDefaultExtension() {
        // 獲取擴(kuò)展點(diǎn)的classes
        getExtensionClasses();

        // 默認(rèn)擴(kuò)展點(diǎn)校驗(yàn)
        if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
            return null;
        }
        // 獲取默認(rèn)擴(kuò)展點(diǎn)
        return getExtension(cachedDefaultName);
    }

這里實(shí)現(xiàn)比較簡(jiǎn)單,不在作說(shuō)明

本篇文章簡(jiǎn)單分別介紹了 Java SPI 與 Dubbo SPI 用法,并對(duì) Dubbo SPI 的加載拓展類(lèi)的過(guò)程進(jìn)行了分析。另外,在 Dubbo SPI 中還有一塊重要的邏輯這里沒(méi)有進(jìn)行分析,即 Dubbo SPI 的擴(kuò)展點(diǎn)自適應(yīng)機(jī)制。該機(jī)制的邏輯較為復(fù)雜,我們將會(huì)在下一篇文章中進(jìn)行詳細(xì)的分析。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前面我們了解過(guò)了Java的SPI擴(kuò)展機(jī)制,對(duì)于Java擴(kuò)展機(jī)制的原理以及優(yōu)缺點(diǎn)也有了大概的了解,這里繼續(xù)深入一下D...
    加大裝益達(dá)閱讀 5,247評(píng)論 2 20
  • Dubbo采用微內(nèi)核+插件體系,使得設(shè)計(jì)優(yōu)雅,擴(kuò)展性強(qiáng)。那所謂的微內(nèi)核+插件體系是如何實(shí)現(xiàn)的呢!大家是否熟悉spi...
    carl_zhao閱讀 1,027評(píng)論 1 3
  • 一.概覽 整體描述 dubbo利用spi擴(kuò)展機(jī)制實(shí)現(xiàn)大量的動(dòng)態(tài)擴(kuò)展,要想充分了解dubbo的擴(kuò)展機(jī)制,首先必須弄明...
    致慮閱讀 962評(píng)論 0 2
  • 為什么要設(shè)計(jì)adaptive?注解在類(lèi)上和注解在方法上的區(qū)別? adaptive設(shè)計(jì)的目的是為了識(shí)別固定已知類(lèi)和擴(kuò)...
    桑榆非晚95閱讀 446評(píng)論 0 0
  • 0 前言 站在一個(gè)框架作者的角度來(lái)說(shuō),定義一個(gè)接口,自己默認(rèn)給出幾個(gè)接口的實(shí)現(xiàn)類(lèi),同時(shí) 允許框架的使用者也能夠自定...
    七寸知架構(gòu)閱讀 16,418評(píng)論 3 66

友情鏈接更多精彩內(nèi)容