Dubbo的SPI機(jī)制與JDK機(jī)制的不同及原理分析

從今天開(kāi)始,將會(huì)逐步介紹關(guān)于DUbbo的有關(guān)知識(shí)旷坦。首先先簡(jiǎn)單介紹一下DUbbo的整體概述庸诱。

概述

Dubbo是SOA(面向服務(wù)架構(gòu))服務(wù)治理方案的核心框架。用于分布式調(diào)用粗卜,其重點(diǎn)在于分布式的治理屋确。
簡(jiǎn)單的來(lái)說(shuō),可以把它分為四個(gè)角色续扔。服務(wù)提供方(Provider)攻臀、服務(wù)消費(fèi)方(Consumer)、注冊(cè)中心和監(jiān)控中心纱昧。通過(guò)注冊(cè)中心對(duì)服務(wù)進(jìn)行注冊(cè)和訂閱刨啸,通過(guò)監(jiān)控中心對(duì)服務(wù)進(jìn)行監(jiān)控。
*核心功能 *

  • Remoting:遠(yuǎn)程通訊砌些,提供對(duì)多種NIO框架抽象封裝呜投,包括“同步轉(zhuǎn)異步”和“請(qǐng)求-響應(yīng)”模式的信息交換方式。
  • Cluster: 服務(wù)框架,提供基于接口方法的透明遠(yuǎn)程過(guò)程調(diào)用存璃,包括多協(xié)議支持,以及軟負(fù)載均衡雕拼,失敗容錯(cuò)纵东,地址路由,動(dòng)態(tài)配置等集群支持啥寇。
  • Registry: 服務(wù)注冊(cè)偎球,基于注冊(cè)中心目錄服務(wù)洒扎,使服務(wù)消費(fèi)方能動(dòng)態(tài)的查找服務(wù)提供方,使地址透明衰絮,使服務(wù)提供方可以平滑增加或減少機(jī)器袍冷。

*Dubbo組件角色 *

Provider: 暴露服務(wù)的服務(wù)提供方
Consumer: 調(diào)用遠(yuǎn)程服務(wù)的服務(wù)消費(fèi)方
Registry: 服務(wù)注冊(cè)與發(fā)現(xiàn)的注冊(cè)中心
Monitor: 統(tǒng)計(jì)服務(wù)的調(diào)用次數(shù)和調(diào)用時(shí)間的監(jiān)控中心
Container: 服務(wù)運(yùn)行容器,常見(jiàn)的容器有Spring容器

調(diào)用關(guān)系:

  1. 服務(wù)容器負(fù)責(zé)啟動(dòng)猫牡,加載胡诗,運(yùn)行服務(wù)提供者
  2. 服務(wù)提供者在啟動(dòng)時(shí),向注冊(cè)中心注冊(cè)自己提供的服務(wù)淌友。
  3. 服務(wù)消費(fèi)者在啟動(dòng)時(shí)煌恢,向注冊(cè)中心訂閱自己所需的服務(wù)。
  4. 注冊(cè)中心返回服務(wù)提供者地址列表消費(fèi)者震庭,如果有變更瑰抵,注冊(cè)中心將基于長(zhǎng)連接推送變更數(shù)據(jù)給消費(fèi)者。
  5. 服務(wù)消費(fèi)者器联,從提供者地址列表中二汛,基于軟負(fù)載均衡算法,選一臺(tái)提供者進(jìn)行調(diào)用拨拓,如果調(diào)用失敗肴颊,再選另一臺(tái)調(diào)用。
  6. 服務(wù)消費(fèi)者和提供者千元,在內(nèi)存中累計(jì)調(diào)用次數(shù)和調(diào)用時(shí)間苫昌,定時(shí)每分鐘發(fā)送一次統(tǒng)計(jì)數(shù)據(jù)到監(jiān)控中心Monitor。


    1.png

SPI(Service Provider Interfaces)

它是Java提供的一套用來(lái)被第三方實(shí)現(xiàn)或者擴(kuò)展的API,它可以用來(lái)啟用框架擴(kuò)展和替換組件幸海。在JDK文檔中祟身,它這樣解釋道:

A service is a well-known set of interfaces and (usually abstract) classes. A service provider is a specific implementation of a service.

在面向?qū)ο蟮脑O(shè)計(jì)里面,模塊之間推薦是基于接口編程物独,而不是對(duì)實(shí)現(xiàn)類(lèi)進(jìn)行硬編碼袜硫,這樣做也是為了模塊設(shè)計(jì)的可拔插原則。為了在模塊裝配的時(shí)候不再程序里指明是那個(gè)實(shí)現(xiàn)挡篓,就需要一種服務(wù)發(fā)現(xiàn)的機(jī)制婉陷,jDK的SPI就是為某個(gè)接口尋找服務(wù)實(shí)現(xiàn)。


2.png

Java SPI實(shí)際上就是基于接口的編程+策略模式+配置文件組合實(shí)現(xiàn)的動(dòng)態(tài)加載機(jī)制官研。
它為某個(gè)接口尋找服務(wù)實(shí)現(xiàn)的機(jī)制秽澳。有點(diǎn)類(lèi)似IOC的思想,就是將裝配的控制權(quán)移到程序之外戏羽,在模式化設(shè)計(jì)中這個(gè)機(jī)制尤其重要担神,所以它的核心思想是解耦

使用場(chǎng)景

  • 數(shù)據(jù)庫(kù)驅(qū)動(dòng)加載接口實(shí)現(xiàn)類(lèi)的加載
    JDBC加載不同類(lèi)型數(shù)據(jù)庫(kù)的驅(qū)動(dòng)
  • 日志門(mén)面接口實(shí)現(xiàn)類(lèi)加載
    SLF4J加載不同提供商的日志實(shí)現(xiàn)類(lèi)
  • Spring
  • Dubbo

使用說(shuō)明

  1. 當(dāng)服務(wù)提供者提供了接口的一種具體實(shí)現(xiàn)后,在jar包的META-INF/service目錄下創(chuàng)建一個(gè)以"接口全限定名"為命名的文件始花,內(nèi)容為實(shí)現(xiàn)類(lèi)的全限定名妄讯。
  2. 接口實(shí)現(xiàn)類(lèi)所在的jar包放在主程序的classpath中
  3. 主程序通過(guò)java.util.ServiceLoader動(dòng)態(tài)加載實(shí)現(xiàn)模板孩锡,它通過(guò)掃描META-INF/services目錄下的配置文件找到實(shí)現(xiàn)類(lèi)的全限定名,把類(lèi)加載到JVM
  4. SPI的實(shí)現(xiàn)類(lèi)必須攜帶一個(gè)不帶參數(shù)的構(gòu)造方法
public final class ServiceLoader<S> implements Iterable<S>
{

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

    // 代表被加載的類(lèi)或者接口
    private final Class<S> service;
    // 用于定位亥贸、加載和實(shí)例化providers的類(lèi)加載器
    private final ClassLoader loader;
    // 創(chuàng)建ServiceLoader時(shí)采用的訪問(wèn)控制上下文
    private final AccessControlContext acc;
    // 緩存providers躬窜,按照實(shí)例化的順序排序
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    // 懶查找迭代器
    private LazyIterator lookupIterator;

    //重新加載,就相當(dāng)于重新創(chuàng)建ServiceLoader了炕置,用于新的服務(wù)提供者安裝到正在運(yùn)行的Java虛擬機(jī)
    public void reload() {
        //清空緩存中所有已實(shí)例化的服務(wù)提供者
        providers.clear();
        //新建一個(gè)迭代器荣挨,該迭代器會(huì)從頭查找和實(shí)例化服務(wù)提供者。
        lookupIterator = new LazyIterator(service, loader);
    }

    /**
    ** 私有構(gòu)造器
    ** 使用指定的類(lèi)加載器和服務(wù)創(chuàng)建服務(wù)加載器
    ** 如果沒(méi)有指定類(lèi)加載器讹俊,使用系統(tǒng)類(lèi)加載器垦沉,就是應(yīng)用類(lèi)加載器
    **/
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

   //解析失敗處理的方法
    private static void fail(Class<?> service, String msg, Throwable cause)
        throws ServiceConfigurationError
    {
        throw new ServiceConfigurationError(service.getName() + ": " + msg,
                                            cause);
    }

    private static void fail(Class<?> service, String msg)
        throws ServiceConfigurationError
    {
        throw new ServiceConfigurationError(service.getName() + ": " + msg);
    }

    private static void fail(Class<?> service, URL u, int line, String msg)
        throws ServiceConfigurationError
    {
        fail(service, u + ":" + line + ": " + msg);
    }

    //解析服務(wù)提供者配置文件中的一行
    //首先去掉注釋檢驗(yàn),然后保存
    //返回下一行行號(hào)
    //重復(fù)的配置項(xiàng)不會(huì)被保存
    private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
                          List<String> names)
        throws IOException, ServiceConfigurationError
    {
        String ln = r.readLine();
        if (ln == null) {
            return -1;
        }
        int ci = ln.indexOf('#');
        if (ci >= 0) ln = ln.substring(0, ci);
        ln = ln.trim();
        int n = ln.length();
        if (n != 0) {
            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                fail(service, u, lc, "Illegal configuration-file syntax");
            int cp = ln.codePointAt(0);
            if (!Character.isJavaIdentifierStart(cp))
                fail(service, u, lc, "Illegal provider-class name: " + ln);
            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
                cp = ln.codePointAt(i);
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                    fail(service, u, lc, "Illegal provider-class name: " + ln);
            }
            if (!providers.containsKey(ln) && !names.contains(ln))
                names.add(ln);
        }
        return lc + 1;
    }

  //解析配置文件仍劈,解析指定的url配置文件
  //使用parseLine方法進(jìn)行解析厕倍,未被實(shí)例化的服務(wù)提供者會(huì)被保存到緩存中。
    private Iterator<String> parse(Class<?> service, URL u)
        throws ServiceConfigurationError
    {
        InputStream in = null;
        BufferedReader r = null;
        ArrayList<String> names = new ArrayList<>();
        try {
            in = u.openStream();
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
            while ((lc = parseLine(service, u, r, lc, names)) >= 0);
        } catch (IOException x) {
            fail(service, "Error reading configuration file", x);
        } finally {
            try {
                if (r != null) r.close();
                if (in != null) in.close();
            } catch (IOException y) {
                fail(service, "Error closing configuration file", y);
            }
        }
        return names.iterator();
    }

    //服務(wù)提供者查找的迭代器
    private class LazyIterator implements Iterator<S>
    {
        //服務(wù)提供者接口
        Class<S> service;
        //類(lèi)加載器
        ClassLoader loader;
        //保存實(shí)現(xiàn)類(lèi)的url
        Enumeration<URL> configs = null;
        //保存實(shí)現(xiàn)類(lèi)的全名
        Iterator<String> pending = null;
        //迭代器中下一個(gè)實(shí)現(xiàn)類(lèi)的全名
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

        public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    }
    //獲取迭代器
    //返回遍歷服務(wù)提供者的迭代器
    //以懶加載的方式加載可用的服務(wù)提供者
    //懶加載的實(shí)現(xiàn)是:解析配置文件和實(shí)例化服務(wù)提供者的工作由迭代器本身完成
    public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

   //為指定的服務(wù)使用指定的類(lèi)加載器來(lái)創(chuàng)建一個(gè)ServiceLoader
    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

   //使用線程上下文的類(lèi)加載器來(lái)創(chuàng)建一個(gè)ServiceLoader
    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

   //使用擴(kuò)展類(lèi)加載器為指定的服務(wù)創(chuàng)建ServiceLoader
   //只能找到并加載已經(jīng)安裝到當(dāng)前Java虛擬機(jī)中的服務(wù)提供者贩疙,應(yīng)用程序類(lèi)路徑中的服務(wù)提供者將被忽略
    public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        ClassLoader prev = null;
        while (cl != null) {
            prev = cl;
            cl = cl.getParent();
        }
        return ServiceLoader.load(service, prev);
    }

    /**
     * Returns a string describing this service.
     *
     * @return  A descriptive string
     */
    public String toString() {
        return "java.util.ServiceLoader[" + service.getName() + "]";
    }

}

ServiceLoader不是實(shí)例化以后讹弯,就去讀取文件的具體實(shí)現(xiàn)。而是等到使用迭代器去遍歷的時(shí)候这溅,才會(huì)加載對(duì)應(yīng)的配置文件去解析组民,調(diào)用hasNext方法時(shí)就去加載配置文件進(jìn)行解析,調(diào)用Next方法的時(shí)候進(jìn)行實(shí)例化并緩存悲靴。

優(yōu)點(diǎn)
使用Java SPI機(jī)制的優(yōu)勢(shì)是實(shí)現(xiàn)解耦臭胜,使得第三方服務(wù)模塊的裝配控制的邏輯與調(diào)用者的業(yè)務(wù)代碼分離,而不是耦合在一起癞尚。應(yīng)用程序可以根據(jù)實(shí)際業(yè)務(wù)情況啟用框架擴(kuò)展或替代框架組件耸三。

缺點(diǎn)
雖然ServiceLoader也算是使用的延遲加載,但是基本只能通過(guò)遍歷全部獲取浇揩,也就是接口的實(shí)現(xiàn)類(lèi)全部加載并實(shí)例化一遍仪壮。如果你并不想用某些實(shí)現(xiàn)類(lèi),它也被加載并實(shí)例化了胳徽,這就造成了浪費(fèi)积锅。獲取某個(gè)實(shí)現(xiàn)類(lèi)的方式不夠靈活,只能通過(guò)Iterator形式獲取养盗,不能根據(jù)某個(gè)參數(shù)來(lái)獲取對(duì)應(yīng)的實(shí)現(xiàn)類(lèi)缚陷。
多個(gè)并發(fā)多線程使用ServiceLoader類(lèi)的實(shí)例是不安全的。

Dubbo的SPI機(jī)制

3.png

從圖中可以看出往核,Dubbo進(jìn)行各個(gè)模塊的擴(kuò)展時(shí)蹬跃,是通過(guò)ExtensionLoader與擴(kuò)展點(diǎn)進(jìn)行關(guān)聯(lián)的。
在Dubbo中的擴(kuò)展點(diǎn)需要滿(mǎn)足以下幾個(gè)特點(diǎn):

  1. 擴(kuò)展點(diǎn)必須是Interface類(lèi)型铆铆,必須被@SPI注釋
  2. 配置文件存儲(chǔ)在META-INF/services/META-INF/dubbo/META-INF/dubbo/internal蝶缀,這些路徑下定義的文件名為擴(kuò)展點(diǎn)接口的全類(lèi)名,文件中以鍵值對(duì)的形式配置擴(kuò)展點(diǎn)的擴(kuò)展實(shí)現(xiàn)薄货,這與JDk SPI的存儲(chǔ)形式有很大不同,所以在Dubbo中無(wú)法直接使用ServiceLoader, 而是使用ExtensionLoader翁都,可用于載入Dubbo中的各種可配置組件,比如動(dòng)態(tài)代理方式(ProxyFactory)谅猾、負(fù)載均衡策略(LoadBalance)柄慰、RCP協(xié)議(Protocol)、攔截器(Filter)税娜、容器類(lèi)型(Container)坐搔、集群方式(Cluster)和注冊(cè)中心類(lèi)型等。
    META-INF/dubbo/internal/com.alibaba.dubbo.common.extension.ExtensionFactory 中定義的擴(kuò)展 :
adaptive = com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory 
spi = com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory 
spring = com.alibaba.dubbo.config.spring.extension.SpringExtensionFactor

在標(biāo)識(shí)擴(kuò)展點(diǎn)時(shí)會(huì)用到這幾個(gè)標(biāo)識(shí)敬矩,@SPI 概行、 @Adaptive、 @Activate

@SPI (注解在類(lèi)上):該注解標(biāo)識(shí)了接口是一個(gè)擴(kuò)展點(diǎn)弧岳,屬性value用來(lái)指定默認(rèn)適配擴(kuò)展點(diǎn)的名稱(chēng)凳忙。
@Activate(注解在類(lèi)型和方法上):@Activate注解在擴(kuò)展點(diǎn)的實(shí)現(xiàn)類(lèi)上,表示了一個(gè)擴(kuò)展類(lèi)被獲取到的條件禽炬,符合條件就被獲取涧卵,不符合條件就不獲取,根據(jù)@Activate中的group腹尖、value屬性來(lái)過(guò)濾柳恐。
@Adaptive(注解在類(lèi)型和方法上):如果注解在類(lèi)上,這個(gè)類(lèi)就是缺省的適配擴(kuò)展热幔。注解在擴(kuò)展點(diǎn)Interface的方法上時(shí)乐设,dubbo會(huì)動(dòng)態(tài)的生成一個(gè)這個(gè)擴(kuò)展點(diǎn)的適配擴(kuò)展類(lèi)(生成代碼,動(dòng)態(tài)編譯實(shí)例化Class),名稱(chēng)為擴(kuò)展點(diǎn)Interface的簡(jiǎn)單類(lèi)名+$Adaptive,這樣做的目的是為了在運(yùn)行時(shí)去適配不同的擴(kuò)展實(shí)例断凶,在運(yùn)行時(shí)通過(guò)傳入的URL類(lèi)型的參數(shù)或者內(nèi)部含有獲取URL方法的參數(shù)伤提,從URL中獲取到要使用的擴(kuò)展類(lèi)的名稱(chēng),再去根據(jù)名稱(chēng)加載對(duì)應(yīng)的擴(kuò)展實(shí)例认烁,用這個(gè)擴(kuò)展實(shí)例對(duì)象調(diào)用相同的方法肿男。如果運(yùn)行時(shí)沒(méi)有適配到運(yùn)行的擴(kuò)展實(shí)例,那么就使用@SPI注解缺省指定的擴(kuò)展却嗡。通過(guò)這種方式就實(shí)現(xiàn)了運(yùn)行時(shí)去適配到對(duì)應(yīng)的擴(kuò)展舶沛。
我們隨機(jī)找一個(gè)源碼中定義的接口: Transporter

@SPI("netty")
public interface Transporter {
    // 綁定一個(gè)服務(wù)器
    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
    Server bind(URL url, ChannelHandler handler) throws RemotingException;

    // 連接一個(gè)服務(wù)器,即創(chuàng)建一個(gè)客戶(hù)端
    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;
}

ExtensionLoader會(huì)通過(guò)createAdaptiveExtensionClassCode方法動(dòng)態(tài)生成一個(gè)Transporter$Adaptive類(lèi)窗价,生成的代碼如下:

package com.alibaba.dubbo.remoting;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Transporter$Adaptive implements com.alibaba.dubbo.remoting.Transporter{
    
public com.alibaba.dubbo.remoting.Client connect(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
        //URL參數(shù)為空則拋出異常如庭。
        if (arg0 == null) 
            throw new IllegalArgumentException("url == null");
        
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
        
        (com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        return extension.connect(arg0, arg1);
    }
    public com.alibaba.dubbo.remoting.Server bind(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
        if (arg0 == null) 
            throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("server", url.getParameter("transporter", "netty"));
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
        (com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        
        return extension.bind(arg0, arg1);
    }
}

這些代碼都是模板代碼,最核心的代碼只有一行撼港,是為了去獲取指定名稱(chēng)的擴(kuò)展實(shí)例對(duì)象坪它。
com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);

擴(kuò)展加載器 ExtensionLoader

它控制著所有擴(kuò)展點(diǎn)的初始化骤竹、加載擴(kuò)展的過(guò)程。
ExtensionLoader中會(huì)存儲(chǔ)兩個(gè)靜態(tài)屬性往毡,EXTENSION_LOADERS保存內(nèi)核開(kāi)放的擴(kuò)展點(diǎn)對(duì)應(yīng)的ExtensionLoader實(shí)例對(duì)象蒙揣;EXTENSION_INSTANCES保存了擴(kuò)展類(lèi)型(Class)和擴(kuò)展類(lèi)型的實(shí)例對(duì)象

private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);

    //這是jdk的SPI擴(kuò)展機(jī)制中配置文件路徑,dubbo為了兼容jdk的SPI
    private static final String SERVICES_DIRECTORY = "META-INF/services/";

    //用于用戶(hù)自定義的擴(kuò)展實(shí)現(xiàn)配置文件存放路徑
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";

    //用于dubbo內(nèi)部提供的擴(kuò)展實(shí)現(xiàn)配置文件存放路徑
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

    private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");

    //擴(kuò)展加載器集合开瞭,key為擴(kuò)展接口懒震,例如Protocol等
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

    //擴(kuò)展實(shí)現(xiàn)集合,key為擴(kuò)展實(shí)現(xiàn)類(lèi)嗤详,value為擴(kuò)展對(duì)象
    //例如key為Class<DubboProtocol>个扰,value為DubboProtocol對(duì)象
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
    //擴(kuò)展接口,例如Protocol等
    private final Class<?> type;

    //對(duì)象工廠葱色,獲得擴(kuò)展實(shí)現(xiàn)的實(shí)例递宅,用于injectExtension方法中將擴(kuò)展實(shí)現(xiàn)類(lèi)的實(shí)例注入到相關(guān)的依賴(lài)屬性。
    //比如StubProxyFactoryWrapper類(lèi)中有Protocol protocol屬性冬筒,就是通過(guò)set方法把Protocol的實(shí)現(xiàn)類(lèi)實(shí)例賦值
    private final ExtensionFactory objectFactory;

    //以下提到的擴(kuò)展名就是在配置文件中的key值恐锣,類(lèi)似于“dubbo”等

    //緩存的擴(kuò)展名與擴(kuò)展類(lèi)映射,和cachedClasses的key和value對(duì)換舞痰。
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();

    //緩存的擴(kuò)展實(shí)現(xiàn)類(lèi)集合
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();

    //擴(kuò)展名與加有@Activate的自動(dòng)激活類(lèi)的映射
    private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();

    //緩存的擴(kuò)展對(duì)象集合土榴,key為擴(kuò)展名,value為擴(kuò)展對(duì)象
    //例如Protocol擴(kuò)展响牛,key為dubbo玷禽,value為DubboProcotol
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();

    //緩存的自適應(yīng)( Adaptive )擴(kuò)展對(duì)象,例如例如AdaptiveExtensionFactory類(lèi)的對(duì)象
    private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();

    //緩存的自適應(yīng)擴(kuò)展對(duì)象的類(lèi)呀打,例如AdaptiveExtensionFactory類(lèi)
    private volatile Class<?> cachedAdaptiveClass = null;

    //緩存的默認(rèn)擴(kuò)展名矢赁,就是@SPI中設(shè)置的值
    private String cachedDefaultName;

    //創(chuàng)建cachedAdaptiveInstance異常
    private volatile Throwable createAdaptiveInstanceError;

    //拓展Wrapper實(shí)現(xiàn)類(lèi)集合
    private Set<Class<?>> cachedWrapperClasses;

    //拓展名與加載對(duì)應(yīng)拓展類(lèi)發(fā)生的異常的映射
    private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();

ExtensionLoader沒(méi)有提供public的構(gòu)造方法,有一個(gè)私有的構(gòu)造方法贬丛,獲取ExtensionLoader實(shí)例的工廠方法撩银,但是提供了一個(gè)public static的getExtensionLoader。其public成員方法中有三個(gè)比較重要的方法:
getActiveExtension: 根據(jù)條件獲取當(dāng)前擴(kuò)展可自動(dòng)激活的實(shí)現(xiàn)
getExtension: 根據(jù)名稱(chēng)獲取當(dāng)前擴(kuò)展的指定實(shí)現(xiàn)
getAdaptiveExtension: 獲取當(dāng)前擴(kuò)展的自適應(yīng)實(shí)現(xiàn)

 private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
@SPI
public interface ExtensionFactory {
    <T> T getExtension(Class<T> type, String name);

}

從上可以看出ExtensionFactory也是一個(gè)擴(kuò)展點(diǎn)豺憔,有兩個(gè)實(shí)現(xiàn)類(lèi):SpiExtensionFactoryAdaptiveExtensionFactory额获,實(shí)際上還有一個(gè)SpringExtensionFactory,不同的實(shí)現(xiàn)類(lèi)可以用不同的方式來(lái)完成擴(kuò)展點(diǎn)實(shí)現(xiàn)的加載。如果要加載的擴(kuò)展點(diǎn)類(lèi)型是ExtensionFactory,那么object設(shè)置為null恭应。
默認(rèn)的ExtensionFactory實(shí)現(xiàn)中抄邀,AdaptiveExtensionFactory被@Adaptive注解注釋?zhuān)簿褪钦f(shuō)它是ExtensionFactory對(duì)應(yīng)的自適應(yīng)擴(kuò)展實(shí)現(xiàn)(每個(gè)擴(kuò)展點(diǎn)最多只能有一個(gè)自適應(yīng)實(shí)現(xiàn),如果所有實(shí)現(xiàn)中沒(méi)有被@Adaptive注釋的昼榛,那么dubbo會(huì)動(dòng)態(tài)生成一個(gè)自適應(yīng)實(shí)現(xiàn)類(lèi))

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    //擴(kuò)展對(duì)象的集合境肾,默認(rèn)的可以分為dubbo 的SPI中接口實(shí)現(xiàn)類(lèi)對(duì)象或者Spring bean對(duì)象
    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        //遍歷所有支持的擴(kuò)展名
        for (String name : loader.getSupportedExtensions()) {
            //擴(kuò)展對(duì)象加入到集合中
            list.add(loader.getExtension(name));
        }
        //返回一個(gè)不可修改的集合
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        for (ExtensionFactory factory : factories) {
            //通過(guò)擴(kuò)展接口和擴(kuò)展名獲得擴(kuò)展對(duì)象
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}

上述代碼中調(diào)用到了ExtensionLoader類(lèi)中的getSupportedExtensions方法,所以接下來(lái)再分析ExtensionLoader類(lèi)。

 public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        //擴(kuò)展點(diǎn)接口為空奥喻,拋出異常
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        //判斷type是否是一個(gè)接口類(lèi)
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        //判斷是否為可擴(kuò)展的接口
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }

        //從擴(kuò)展加載器集合中取出擴(kuò)展接口對(duì)應(yīng)的擴(kuò)展加載器
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);

        //如果為空偶宫,則創(chuàng)建該擴(kuò)展接口的擴(kuò)展加載器,并且添加到EXTENSION_LOADERS
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            //創(chuàng)建適配器對(duì)象
                            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;
    }

在ExtensionLoader的私有構(gòu)造方法中可以看出衫嵌,在選擇ExtensionFactory的時(shí)候读宙,并不是用getExtension(name)來(lái)獲取某個(gè)具體的實(shí)現(xiàn)類(lèi),而是調(diào)用getAdaptiveExtension來(lái)獲取一個(gè)自適應(yīng)的實(shí)現(xiàn)楔绞。
首先檢查緩存的adaptiveInstance是否存在,如果存在則直接使用唇兑,否則的話調(diào)用createAdaptiveExtension方法來(lái)創(chuàng)建新的adaptiveInstance并且緩存起來(lái)酒朵,也就是說(shuō)對(duì)于某個(gè)擴(kuò)展點(diǎn),每次調(diào)用ExtensionLoader.getAdaptiveExtension獲取到的都是同一個(gè)實(shí)例扎附。
在調(diào)用getAdaptiveExtensionClass中首先調(diào)用getExtensionClasses()
在getAdaptiveExtensionClass()中蔫耽,調(diào)用getExtensionClasses()獲取擴(kuò)展實(shí)現(xiàn)類(lèi)數(shù)組,并存放在cachedClasses屬性中留夜。
再?gòu)膅etExtensionClasses()看匙铡,當(dāng)cachedClasses為空時(shí)牡彻,調(diào)用loadExtensionClasses()
getExtensionClasses()會(huì)加載當(dāng)前Extension的所有實(shí)現(xiàn)礁蔗,如果有@Adaptive類(lèi)型,則會(huì)賦值給cachedAdaptiveClass屬性緩存起來(lái)祭衩,如果沒(méi)有找到@Adaptive類(lèi)型實(shí)現(xiàn)嚼摩,則動(dòng)態(tài)創(chuàng)建一個(gè)AdaptiveExtensionClass钦讳。

首先會(huì)獲取到該擴(kuò)展點(diǎn)類(lèi)的注解中的值,獲取默認(rèn)值枕面,然后從特定目錄下讀取配置文件中的信息愿卒,
最后通過(guò)loadClass,將有關(guān)類(lèi)放到extensionClasses變量中

 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);
        }
    }
 private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        //緩存的自適應(yīng)擴(kuò)展對(duì)象
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
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;
    }


private Map<String, Class<?>> loadExtensionClasses() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            //@SPI內(nèi)的默認(rèn)值
            String value = defaultAnnotation.value();
            if ((value = value.trim()).length() > 0) {
                String[] names = NAME_SEPARATOR.split(value);
                //只允許有一個(gè)默認(rèn)值
                if (names.length > 1) {
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }
                if (names.length == 1) cachedDefaultName = names[0];
            }
        }

        //從配置文件中加載實(shí)現(xiàn)類(lèi)數(shù)組
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadDirectory(extensionClasses, DUBBO_DIRECTORY);
        loadDirectory(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }


private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
        //拼接接口全限定名潮秘,得到完整的文件名
        String fileName = dir + type.getName();
        try {
            Enumeration<java.net.URL> urls;
            //獲取ExtensionLoader類(lèi)信息
            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);
        }
    }

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) {
                    //跳過(guò)被#注釋的內(nèi)容
                    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) {
                                //根據(jù)"="拆分key跟value
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                                //加載擴(kuò)展類(lèi)
                                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);
        }
    }

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        //該類(lèi)是否實(shí)現(xiàn)擴(kuò)展接口
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Error when load extension class(interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + "is not subtype of interface.");
        }
        //判斷該類(lèi)是否為擴(kuò)展接口的適配器
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            if (cachedAdaptiveClass == null) {
                cachedAdaptiveClass = clazz;
            } else if (!cachedAdaptiveClass.equals(clazz)) {
                throw new IllegalStateException("More than 1 adaptive class found: "
                        + cachedAdaptiveClass.getClass().getName()
                        + ", " + clazz.getClass().getName());
            }
        } else if (isWrapperClass(clazz)) {
            Set<Class<?>> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                wrappers = cachedWrapperClasses;
            }
            wrappers.add(clazz);
        } else {
            //通過(guò)反射獲得構(gòu)造器對(duì)象
            clazz.getConstructor();
            //未配置擴(kuò)展名琼开,自動(dòng)生成,例如DemoFilter為 demo枕荞,主要用于兼容java SPI的配置柜候。
            if (name == null || name.length() == 0) {
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }
            // 獲得擴(kuò)展名,可以是數(shù)組买猖,有多個(gè)拓?cái)U(kuò)展名改橘。
            String[] names = NAME_SEPARATOR.split(name);
            if (names != null && names.length > 0) {
                Activate activate = clazz.getAnnotation(Activate.class);
                //如果是自動(dòng)激活的實(shí)現(xiàn)類(lèi),則加入到緩存
                if (activate != null) {
                    cachedActivates.put(names[0], activate);
                }
                for (String n : names) {
                    if (!cachedNames.containsKey(clazz)) {
                        cachedNames.put(clazz, n);
                    }
                    //緩存擴(kuò)展實(shí)現(xiàn)類(lèi)
                    Class<?> c = extensionClasses.get(n);
                    if (c == null) {
                        extensionClasses.put(n, clazz);
                    } else if (c != clazz) {
                        throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                    }
                }
            }
        }
    }

上述代碼完成了自適應(yīng)擴(kuò)展點(diǎn)類(lèi)型的實(shí)現(xiàn)和實(shí)例化玉控,下面方法是擴(kuò)展點(diǎn)自動(dòng)注入的實(shí)現(xiàn)飞主,它會(huì)獲取處理當(dāng)前實(shí)例的所有set方法對(duì)應(yīng)的參數(shù)類(lèi)型和property名稱(chēng),根據(jù)這兩個(gè)條件從ExtensionFactory中查詢(xún),如果有返回?cái)U(kuò)展點(diǎn)實(shí)例碌识,那么就進(jìn)行注入操作碾篡。

private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                //反射獲得該類(lèi)中所有的方法
                for (Method method : instance.getClass().getMethods()) {
                    //如果是set方法
                    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
                         */
                        if (method.getAnnotation(DisableInject.class) != null) {
                            continue;
                        }
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            //獲得屬性,比如StubProxyFactoryWrapper類(lèi)中有Protocol protocol屬性筏餐,
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            //獲得屬性值开泽,比如Protocol對(duì)象,也可能是Bean對(duì)象
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                //注入依賴(lài)屬性
                                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;
    }

文章參考:
dubbo源碼一:ExtensionLoader及獲取適配類(lèi)過(guò)程解析
Dubbo擴(kuò)展點(diǎn)加載機(jī)制 - ExtensionLoader
【Dubbo】Adaptive

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末魁瞪,一起剝皮案震驚了整個(gè)濱河市穆律,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌导俘,老刑警劉巖峦耘,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異旅薄,居然都是意外死亡辅髓,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)少梁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)洛口,“玉大人,你說(shuō)我怎么就攤上這事凯沪〉谘妫” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵著洼,是天一觀的道長(zhǎng)樟遣。 經(jīng)常有香客問(wèn)我,道長(zhǎng)身笤,這世上最難降的妖魔是什么豹悬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮液荸,結(jié)果婚禮上瞻佛,老公的妹妹穿的比我還像新娘。我一直安慰自己娇钱,他們只是感情好伤柄,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著文搂,像睡著了一般适刀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上煤蹭,一...
    開(kāi)封第一講書(shū)人閱讀 52,196評(píng)論 1 308
  • 那天笔喉,我揣著相機(jī)與錄音取视,去河邊找鬼。 笑死常挚,一個(gè)胖子當(dāng)著我的面吹牛作谭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播奄毡,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼折欠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了吼过?” 一聲冷哼從身側(cè)響起锐秦,我...
    開(kāi)封第一講書(shū)人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎那先,沒(méi)想到半個(gè)月后农猬,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡售淡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了慷垮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片揖闸。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖料身,靈堂內(nèi)的尸體忽然破棺而出汤纸,到底是詐尸還是另有隱情,我是刑警寧澤芹血,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布贮泞,位于F島的核電站,受9級(jí)特大地震影響幔烛,放射性物質(zhì)發(fā)生泄漏啃擦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一饿悬、第九天 我趴在偏房一處隱蔽的房頂上張望令蛉。 院中可真熱鬧,春花似錦狡恬、人聲如沸珠叔。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)祷安。三九已至,卻和暖如春兔乞,著一層夾襖步出監(jiān)牢的瞬間汇鞭,已是汗流浹背凉唐。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留虱咧,地道東北人熊榛。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像腕巡,于是被迫代替她去往敵國(guó)和親玄坦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容