交流下Mybatis 的設計模式,文章內附源碼

雖然我們都知道有20多個設計模式豪筝,但是大多停留在概念層面腋腮,真實開發(fā)中很少遇到,Mybatis源碼中使用了大量的設計模式壤蚜,閱讀源碼并觀察設計模式在其中的應用,能夠更深入的理解設計模式徊哑。

Mybatis至少遇到了以下的設計模式的使用:

Builder模式袜刷,例如SqlSessionFactoryBuilder、XMLConfigBuilder莺丑、XMLMapperBuilder著蟹、XMLStatementBuilder、CacheBuilder梢莽;

工廠模式萧豆,例如SqlSessionFactory、ObjectFactory昏名、MapperProxyFactory涮雷;

單例模式,例如ErrorContext和LogFactory轻局;

代理模式洪鸭,Mybatis實現(xiàn)的核心样刷,比如MapperProxy、ConnectionLogger览爵,用的jdk的動態(tài)代理置鼻;還有executor.loader包使用了cglib或者javassist達到延遲加載的效果;

組合模式蜓竹,例如SqlNode和各個子類ChooseSqlNode等箕母;

模板方法模式,例如BaseExecutor和SimpleExecutor俱济,還有BaseTypeHandler和所有的子類例如IntegerTypeHandler嘶是;

適配器模式,例如Log的Mybatis接口和它對jdbc姨蝴、log4j等各種日志框架的適配實現(xiàn)俊啼;

裝飾者模式,例如Cache包中的cache.decorators子包中等各個裝飾者的實現(xiàn)左医;

迭代器模式授帕,例如迭代器模式PropertyTokenizer;

接下來挨個模式進行解讀浮梢,先介紹模式自身的知識跛十,然后解讀在Mybatis中怎樣應用了該模式。?

1秕硝、Builder模式

Builder模式的定義是“將一個復雜對象的構建與它的表示分離芥映,使得同樣的構建過程可以創(chuàng)建不同的表示≡恫颍”奈偏,它屬于創(chuàng)建類模式,一般來說躯护,如果一個對象的構建比較復雜惊来,超出了構造函數(shù)所能包含的范圍,就可以使用工廠模式和Builder模式棺滞,相對于工廠模式會產(chǎn)出一個完整的產(chǎn)品裁蚁,Builder應用于更加復雜的對象的構建,甚至只會構建產(chǎn)品的一個部分继准。

在Mybatis環(huán)境的初始化過程中枉证,SqlSessionFactoryBuilder會調用XMLConfigBuilder讀取所有的MybatisMapConfig.xml和所有的*Mapper.xml文件,構建Mybatis運行的核心對象Configuration對象移必,然后將該Configuration對象作為參數(shù)構建一個SqlSessionFactory對象室谚。

其中XMLConfigBuilder在構建Configuration對象時,也會調用XMLMapperBuilder用于讀取*Mapper文件,而XMLMapperBuilder會使用XMLStatementBuilder來讀取和build所有的SQL語句舞萄。

在這個過程中眨补,有一個相似的特點,就是這些Builder會讀取文件或者配置倒脓,然后做大量的XpathParser解析撑螺、配置或語法的解析、反射生成對象崎弃、存入結果緩存等步驟甘晤,這么多的工作都不是一個構造函數(shù)所能包括的,因此大量采用了Builder模式來解決饲做。

對于builder的具體類线婚,方法都大都用build*開頭,比如SqlSessionFactoryBuilder為例盆均,它包含以下方法:

即根據(jù)不同的輸入?yún)?shù)來構建SqlSessionFactory這個工廠對象塞弊。

2、工廠模式

在Mybatis中比如SqlSessionFactory使用的是工廠模式泪姨,該工廠沒有那么復雜的邏輯游沿,是一個簡單工廠模式。

簡單工廠模式(Simple?Factory?Pattern):又稱為靜態(tài)工廠方法(Static?Factory?Method)模式肮砾,它屬于類創(chuàng)建型模式诀黍。在簡單工廠模式中,可以根據(jù)參數(shù)的不同返回不同類的實例仗处。簡單工廠模式專門定義一個類來負責創(chuàng)建其他類的實例眯勾,被創(chuàng)建的實例通常都具有共同的父類。

SqlSession可以認為是一個Mybatis工作的核心的接口婆誓,通過這個接口可以執(zhí)行執(zhí)行SQL語句吃环、獲取Mappers、管理事務洋幻。類似于連接MySQL的Connection對象郁轻。

可以看到,該Factory的openSession方法重載了很多個鞋屈,分別支持autoCommit、Executor故觅、Transaction等參數(shù)的輸入厂庇,來構建核心的SqlSession對象。

在DefaultSqlSessionFactory的默認工廠實現(xiàn)里输吏,有一個方法可以看出工廠怎么產(chǎn)出一個產(chǎn)品:

private?SqlSession?openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,

boolean?autoCommit)

{

Transaction tx =?null;

try?{

final?Environment environment = configuration.getEnvironment();

final?TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);

? ? ?tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);

final?Executor executor = configuration.newExecutor(tx, execType);

return?new?DefaultSqlSession(configuration, executor, autoCommit);

}?catch?(Exception e) {

closeTransaction(tx);?// may have fetched a connection so lets call

// close()

throw?ExceptionFactory.wrapException("Error?opening?session.??Cause:?"?+ e, e);

}?finally?{

? ? ?ErrorContext.instance().reset();

? ?}

?}

這是一個openSession調用的底層方法权旷,該方法先從configuration讀取對應的環(huán)境配置,然后初始化TransactionFactory獲得一個Transaction對象,然后通過Transaction獲取一個Executor對象拄氯,最后通過configuration躲查、Executor、是否autoCommit三個參數(shù)構建了SqlSession译柏。

在這里其實也可以看到端倪镣煮,SqlSession的執(zhí)行,其實是委托給對應的Executor來進行的鄙麦。

而對于LogFactory典唇,它的實現(xiàn)代碼:

public?final?class?LogFactory?{

private?static?Constructor<??extends Log> logConstructor;

private?LogFactory() {

// disable construction

?}

public?static?Log getLog(ClassaClass)?{

return?getLog(aClass.getName());

?}

這里有個特別的地方,是Log變量的的類型是ConstructorextendsLog>胯府,也就是說該工廠生產(chǎn)的不只是一個產(chǎn)品介衔,而是具有Log公共接口的一系列產(chǎn)品,比如Log4jImpl骂因、Slf4jImpl等很多具體的Log炎咖。

3、單例模式

單例模式(Singleton?Pattern):單例模式確保某一個類只有一個實例寒波,而且自行實例化并向整個系統(tǒng)提供這個實例乘盼,這個類稱為單例類,它提供全局訪問的方法影所。

單例模式的要點有三個:一是某個類只能有一個實例蹦肴;二是它必須自行創(chuàng)建這個實例;三是它必須自行向整個系統(tǒng)提供這個實例猴娩。單例模式是一種對象創(chuàng)建型模式阴幌。單例模式又名單件模式或單態(tài)模式。

在Mybatis中有兩個地方用到單例模式卷中,ErrorContext和LogFactory矛双,其中ErrorContext是用在每個線程范圍內的單例,用于記錄該線程的執(zhí)行環(huán)境錯誤信息蟆豫,而LogFactory則是提供給整個Mybatis使用的日志工廠议忽,用于獲得針對項目配置好的日志對象。

ErrorContext的單例實現(xiàn)代碼:

public?class?ErrorContext?{

private?static?final ThreadLocalLOCAL =?new?ThreadLocal();

private?ErrorContext()?{

?}

public?static?ErrorContext?instance()?{

ErrorContext context = LOCAL.get();

if?(context ==?null) {

context =?new?ErrorContext();

LOCAL.set(context);

? ?}

return?context;

?}

構造函數(shù)是private修飾十减,具有一個static的局部instance變量和一個獲取instance變量的方法栈幸,在獲取實例的方法中,先判斷是否為空如果是的話就先創(chuàng)建帮辟,然后返回構造好的對象速址。

只是這里有個有趣的地方是,LOCAL的靜態(tài)實例變量使用了ThreadLocal修飾由驹,也就是說它屬于每個線程各自的數(shù)據(jù)芍锚,而在instance()方法中,先獲取本線程的該實例,如果沒有就創(chuàng)建該線程獨有的ErrorContext并炮。

4默刚、代理模式

代理模式可以認為是Mybatis的核心使用的模式,正是由于這個模式逃魄,我們只需要編寫Mapper.java接口荤西,不需要實現(xiàn),由Mybatis后臺幫我們完成具體SQL的執(zhí)行嗅钻。

代理模式(Proxy?Pattern)?:給某一個對象提供一個代?理皂冰,并由代理對象控制對原對象的引用。代理模式的英?文叫做Proxy或Surrogate养篓,它是一種對象結構型模式秃流。

代理模式包含如下角色:

Subject:?抽象主題角色

Proxy:?代理主題角色

RealSubject:?真實主題角色

這里有兩個步驟,第一個是提前創(chuàng)建一個Proxy柳弄,第二個是使用的時候會自動請求Proxy舶胀,然后由Proxy來執(zhí)行具體事務;

當我們使用Configuration的getMapper方法時碧注,會調用mapperRegistry.getMapper方法嚣伐,而該方法又會調用mapperProxyFactory.newInstance(sqlSession)來生成一個具體的代理:

/**

*?@author?Lasse Voss

*/

public?class?MapperProxyFactory?{

private?final?Class mapperInterface;

private?final?MapmethodCache =?new?ConcurrentHashMap();

public?MapperProxyFactory(Class<T> mapperInterface)?{

this.mapperInterface = mapperInterface;

?}

public?Class?getMapperInterface()?{

return?mapperInterface;

?}

public?Map?getMethodCache()?{

return?methodCache;

?}

@SuppressWarnings("unchecked")

protected?T?newInstance(MapperProxy<T> mapperProxy)?{

return?(T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),?new?Class[] { mapperInterface },

? ? ? ?mapperProxy);

?}

public?T?newInstance(SqlSession sqlSession)?{

final?MapperProxymapperProxy =?new?MapperProxy(sqlSession, mapperInterface, methodCache);

return?newInstance(mapperProxy);

?}

}

在這里,先通過T?newInstance(SqlSession?sqlSession)方法會得到一個MapperProxy對象萍丐,然后調用T?newInstance(MapperProxy<T>?mapperProxy)生成代理對象然后返回轩端。

而查看MapperProxy的代碼,可以看到如下內容:

public?class?MapperProxy?implements?InvocationHandler,?Serializable?{

@Override

public?Object?invoke(Object proxy, Method method, Object[] args)?throws?Throwable?{

try?{

if?(Object.class.equals(method.getDeclaringClass())) {

return?method.invoke(this, args);

}?else?if?(isDefaultMethod(method)) {

return?invokeDefaultMethod(proxy, method, args);

? ? ?}

}?catch?(Throwable t) {

throw?ExceptionUtil.unwrapThrowable(t);

? ?}

final?MapperMethod mapperMethod = cachedMapperMethod(method);

return?mapperMethod.execute(sqlSession, args);

?}

非常典型的逝变,該MapperProxy類實現(xiàn)了InvocationHandler接口基茵,并且實現(xiàn)了該接口的invoke方法。

通過這種方式壳影,我們只需要編寫Mapper.java接口類拱层,當真正執(zhí)行一個Mapper接口的時候,就會轉發(fā)給MapperProxy.invoke方法宴咧,而該方法則會調用后續(xù)的sqlSession.cud>executor.execute>prepareStatement等一系列方法根灯,完成SQL的執(zhí)行和返回。

5掺栅、組合模式

組合模式組合多個對象形成樹形結構以表示“整體-部分”的結構層次烙肺。

組合模式對單個對象(葉子對象)和組合對象(組合對象)具有一致性,它將對象組織到樹結構中氧卧,可以用來描述整體與部分的關系桃笙。同時它也模糊了簡單元素(葉子對象)和復雜元素(容器對象)的概念,使得客戶能夠像處理簡單元素一樣來處理復雜元素假抄,從而使客戶程序能夠與復雜元素的內部結構解耦怎栽。

在使用組合模式中需要注意一點也是組合模式最關鍵的地方:葉子對象和組合對象實現(xiàn)相同的接口。這就是組合模式能夠將葉子節(jié)點和對象節(jié)點進行一致處理的原因宿饱。

Mybatis支持動態(tài)SQL的強大功能熏瞄,比如下面的這個SQL:

? ?UPDATE users

name =?#{name}

, age =?#{age}

, birthday =?#{birthday}

? ?</trim>

where?id =?${id}

</update>

在這里面使用到了trim、if等動態(tài)元素谬以,可以根據(jù)條件來生成不同情況下的SQL强饮;

在DynamicSqlSource.getBoundSql方法里,調用了rootSqlNode.apply(context)方法为黎,apply方法是所有的動態(tài)節(jié)點都實現(xiàn)的接口:

public?interface?SqlNode?{

boolean?apply(DynamicContext context);

}

對于實現(xiàn)該SqlSource接口的所有節(jié)點邮丰,就是整個組合模式樹的各個節(jié)點:

組合模式的簡單之處在于,所有的子節(jié)點都是同一類節(jié)點铭乾,可以遞歸的向下執(zhí)行剪廉,比如對于TextSqlNode,因為它是最底層的葉子節(jié)點炕檩,所以直接將對應的內容append到SQL語句中:

@Override

public?boolean?apply(DynamicContext context)?{

GenericTokenParser parser = createParser(new?BindingTokenParser(context, injectionFilter));

? ?context.appendSql(parser.parse(text));

return?true;

?}

但是對于IfSqlNode斗蒋,就需要先做判斷,如果判斷通過笛质,仍然會調用子元素的SqlNode泉沾,即contents.apply方法,實現(xiàn)遞歸的解析妇押。

@Override

public?boolean?apply(DynamicContext context)?{

if?(evaluator.evaluateBoolean(test, context.getBindings())) {

? ? ?contents.apply(context);

return?true;

? ?}

return?false;

?}

6跷究、模板方法模式

模板方法模式是所有模式中最為常見的幾個模式之一,是基于繼承的代碼復用的基本技術敲霍。添加筆者微信俊马,回復關鍵字:架構,可以獲取更多筆者整理的架構和設計模式干貨色冀。

模板方法模式需要開發(fā)抽象類和具體子類的設計師之間的協(xié)作潭袱。一個設計師負責給出一個算法的輪廓和骨架,另一些設計師則負責給出這個算法的各個邏輯步驟锋恬。代表這些具體邏輯步驟的方法稱做基本方法(primitive?method)屯换;而將這些基本方法匯總起來的方法叫做模板方法(template?method),這個設計模式的名字就是從此而來与学。

模板類定義一個操作中的算法的骨架彤悔,而將一些步驟延遲到子類中。使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟索守。

在Mybatis中晕窑,sqlSession的SQL執(zhí)行,都是委托給Executor實現(xiàn)的卵佛,Executor包含以下結構:

其中的BaseExecutor就采用了模板方法模式杨赤,它實現(xiàn)了大部分的SQL執(zhí)行邏輯敞斋,然后把以下幾個方法交給子類定制化完成:

protected?abstract?int?doUpdate(MappedStatement ms, Object parameter)?throws?SQLException;

protected?abstract?List?doFlushStatements(boolean?isRollback)?throws?SQLException;

protected?abstractList?doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,

ResultHandler resultHandler, BoundSql boundSql)

?throws?SQLException;

該模板方法類有幾個子類的具體實現(xiàn),使用了不同的策略:

簡單SimpleExecutor:每執(zhí)行一次update或select疾牲,就開啟一個Statement對象植捎,用完立刻關閉Statement對象。(可以是Statement或PrepareStatement對象)

重用ReuseExecutor:執(zhí)行update或select阳柔,以sql作為key查找Statement對象焰枢,存在就使用,不存在就創(chuàng)建舌剂,用完后济锄,不關閉Statement對象,而是放置于Map<String,?Statement>內霍转,供下一次使用荐绝。(可以是Statement或PrepareStatement對象)

批量BatchExecutor:執(zhí)行update(沒有select,JDBC批處理不支持select)避消,將所有sql都添加到批處理中(addBatch())很泊,等待統(tǒng)一執(zhí)行(executeBatch()),它緩存了多個Statement對象沾谓,每個Statement對象都是addBatch()完畢后委造,等待逐一執(zhí)行executeBatch()批處理的;BatchExecutor相當于維護了多個桶均驶,每個桶里都裝了很多屬于自己的SQL昏兆,就像蘋果藍里裝了很多蘋果,番茄藍里裝了很多番茄妇穴,最后爬虱,再統(tǒng)一倒進倉庫。(可以是Statement或PrepareStatement對象)

比如在SimpleExecutor中這樣實現(xiàn)update方法:

@Override

public?int?doUpdate(MappedStatement ms, Object parameter)?throws?SQLException?{

Statement stmt =?null;

try?{

? ? ?Configuration configuration = ms.getConfiguration();

StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT,?null,

null);

? ? ?stmt = prepareStatement(handler, ms.getStatementLog());

return?handler.update(stmt);

}?finally?{

? ? ?closeStatement(stmt);

? ?}

?}

7腾它、適配器模式

適配器模式(Adapter?Pattern)?:將一個接口轉換成客戶希望的另一個接口跑筝,適配器模式使接口不兼容的那些類可以一起工作,其別名為包裝器(Wrapper)瞒滴。適配器模式既可以作為類結構型模式曲梗,也可以作為對象結構型模式。

在Mybatsi的logging包中妓忍,有一個Log接口:

/**

*?@author?Clinton Begin

*/

public?interface?Log?{

boolean?isDebugEnabled();

boolean?isTraceEnabled();

void?error(String s, Throwable e);

void?error(String s);

void?debug(String s);

void?trace(String s);

void?warn(String s);

}

該接口定義了Mybatis直接使用的日志方法虏两,而Log接口具體由誰來實現(xiàn)呢?Mybatis提供了多種日志框架的實現(xiàn)世剖,這些實現(xiàn)都匹配這個Log接口所定義的接口方法定罢,最終實現(xiàn)了所有外部日志框架到Mybatis日志包的適配:

比如對于Log4jImpl的實現(xiàn)來說,該實現(xiàn)持有了org.apache.log4j.Logger的實例旁瘫,然后所有的日志方法祖凫,均委托該實例來實現(xiàn)直晨。

public?class?Log4jImpl?implements?Log?{

private?static?final?String FQCN = Log4jImpl.class.getName();

private?Logger log;

public?Log4jImpl(String clazz)?{

? ?log = Logger.getLogger(clazz);

?}

@Override

public?boolean?isDebugEnabled()?{

return?log.isDebugEnabled();

?}

@Override

public?boolean?isTraceEnabled()?{

return?log.isTraceEnabled();

?}

@Override

public?void?error(String s, Throwable e)?{

? ?log.log(FQCN, Level.ERROR, s, e);

?}

@Override

public?void?error(String s)?{

log.log(FQCN, Level.ERROR, s,?null);

?}

@Override

public?void?debug(String s)?{

log.log(FQCN, Level.DEBUG, s,?null);

?}

@Override

public?void?trace(String s)?{

log.log(FQCN, Level.TRACE, s,?null);

?}

@Override

public?void?warn(String s)?{

log.log(FQCN, Level.WARN, s,?null);

?}

}

8蜕衡、裝飾者模式

裝飾模式(Decorator?Pattern)?:動態(tài)地給一個對象增加一些額外的職責(Responsibility)隐锭,就增加對象功能來說色徘,裝飾模式比生成子類實現(xiàn)更為靈活拐云。其別名也可以稱為包裝器(Wrapper)矛辕,與適配器模式的別名相同收苏,但它們適用于不同的場合悄雅。根據(jù)翻譯的不同台诗,裝飾模式也有人稱之為“油漆工模式”完箩,它是一種對象結構型模式。?

在mybatis中拉队,緩存的功能由根接口Cache(org.apache.ibatis.cache.Cache)定義弊知。添加筆者微信,回復關鍵字:架構粱快,可以獲取更多筆者整理的架構和設計模式干貨秩彤。

整個體系采用裝飾器設計模式,數(shù)據(jù)存儲和緩存的基本功能由PerpetualCache(org.apache.ibatis.cache.impl.PerpetualCache)永久緩存實現(xiàn)事哭,然后通過一系列的裝飾器來對PerpetualCache永久緩存進行緩存策略等方便的控制漫雷。如下圖:

用于裝飾PerpetualCache的標準裝飾器共有8個(全部在org.apache.ibatis.cache.decorators包中):

FifoCache:先進先出算法,緩存回收策略

LoggingCache:輸出緩存命中的日志信息

LruCache:最近最少使用算法鳍咱,緩存回收策略

ScheduledCache:調度緩存降盹,負責定時清空緩存

SerializedCache:緩存序列化和反序列化存儲

SoftCache:基于軟引用實現(xiàn)的緩存管理策略

SynchronizedCache:同步的緩存裝飾器,用于防止多線程并發(fā)訪問

WeakCache:基于弱引用實現(xiàn)的緩存管理策略

另外谤辜,還有一個特殊的裝飾器TransactionalCache:事務性的緩存

正如大多數(shù)持久層框架一樣蓄坏,mybatis緩存同樣分為一級緩存和二級緩存

一級緩存,又叫本地緩存丑念,是PerpetualCache類型的永久緩存涡戳,保存在執(zhí)行器中(BaseExecutor),而執(zhí)行器又在SqlSession(DefaultSqlSession)中脯倚,所以一級緩存的生命周期與SqlSession是相同的渔彰。

二級緩存,又叫自定義緩存推正,實現(xiàn)了Cache接口的類都可以作為二級緩存胳岂,所以可配置如encache等的第三方緩存。二級緩存以namespace名稱空間為其唯一標識舔稀,被保存在Configuration核心配置對象中乳丰。

二級緩存對象的默認類型為PerpetualCache,如果配置的緩存是默認類型内贮,則mybatis會根據(jù)配置自動追加一系列裝飾器产园。

Cache對象之間的引用順序為:

SynchronizedCache–>LoggingCache–>SerializedCache–>ScheduledCache–>LruCache–>PerpetualCache

9汞斧、迭代器模式

迭代器(Iterator)模式,又叫做游標(Cursor)模式什燕。GOF給出的定義為:提供一種方法訪問一個容器(container)對象中各個元素粘勒,而又不需暴露該對象的內部細節(jié)。?

Java的Iterator就是迭代器模式的接口屎即,只要實現(xiàn)了該接口庙睡,就相當于應用了迭代器模式:

比如Mybatis的PropertyTokenizer是property包中的重量級類,該類會被reflection包中其他的類頻繁的引用到技俐。這個類實現(xiàn)了Iterator接口乘陪,在使用時經(jīng)常被用到的是Iterator接口中的hasNext這個函數(shù)。

public?class?PropertyTokenizer?implements?Iterator?{

private?String name;

private?String indexedName;

private?String index;

private?String children;

public?PropertyTokenizer(String fullname)?{

int?delim = fullname.indexOf('.');

if?(delim > -1) {

name = fullname.substring(0, delim);

children = fullname.substring(delim +?1);

}?else?{

? ? ?name = fullname;

children =?null;

? ?}

? ?indexedName = name;

delim = name.indexOf('[');

if?(delim > -1) {

index = name.substring(delim +?1, name.length() -?1);

name = name.substring(0, delim);

? ?}

?}

public?String?getName()?{

return?name;

?}

public?String?getIndex()?{

return?index;

?}

public?String?getIndexedName()?{

return?indexedName;

?}

public?String?getChildren()?{

return?children;

?}

@Override

public?boolean?hasNext()?{

returnchildren?!=null;

?}

@Override

public?PropertyTokenizer?next()?{

return?new?PropertyTokenizer(children);

?}

@Override

public?void?remove()?{

throw?new?UnsupportedOperationException(

"Remove is not supported, as it has no meaning in the context of properties.");

?}

}

可以看到雕擂,這個類傳入一個字符串到構造函數(shù)啡邑,然后提供了iterator方法對解析后的子串進行遍歷,是一個很常用的方法類井赌。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末谤逼,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子仇穗,更是在濱河造成了極大的恐慌流部,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,332評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纹坐,死亡現(xiàn)場離奇詭異贵涵,居然都是意外死亡,警方通過查閱死者的電腦和手機恰画,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,508評論 3 385
  • 文/潘曉璐 我一進店門宾茂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拴还,你說我怎么就攤上這事跨晴。” “怎么了片林?”我有些...
    開封第一講書人閱讀 157,812評論 0 348
  • 文/不壞的土叔 我叫張陵端盆,是天一觀的道長。 經(jīng)常有香客問我费封,道長焕妙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,607評論 1 284
  • 正文 為了忘掉前任弓摘,我火速辦了婚禮焚鹊,結果婚禮上,老公的妹妹穿的比我還像新娘韧献。我一直安慰自己末患,他們只是感情好研叫,可當我...
    茶點故事閱讀 65,728評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著璧针,像睡著了一般嚷炉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上探橱,一...
    開封第一講書人閱讀 49,919評論 1 290
  • 那天申屹,我揣著相機與錄音,去河邊找鬼隧膏。 笑死哗讥,一個胖子當著我的面吹牛,可吹牛的內容都是我干的私植。 我是一名探鬼主播,決...
    沈念sama閱讀 39,071評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼车酣,長吁一口氣:“原來是場噩夢啊……” “哼曲稼!你這毒婦竟也來了?” 一聲冷哼從身側響起湖员,我...
    開封第一講書人閱讀 37,802評論 0 268
  • 序言:老撾萬榮一對情侶失蹤贫悄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后娘摔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窄坦,經(jīng)...
    沈念sama閱讀 44,256評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,576評論 2 327
  • 正文 我和宋清朗相戀三年凳寺,在試婚紗的時候發(fā)現(xiàn)自己被綠了鸭津。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,712評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡肠缨,死狀恐怖逆趋,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情晒奕,我是刑警寧澤闻书,帶...
    沈念sama閱讀 34,389評論 4 332
  • 正文 年R本政府宣布,位于F島的核電站脑慧,受9級特大地震影響魄眉,放射性物質發(fā)生泄漏。R本人自食惡果不足惜闷袒,卻給世界環(huán)境...
    茶點故事閱讀 40,032評論 3 316
  • 文/蒙蒙 一坑律、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧囊骤,春花似錦脾歇、人聲如沸蒋腮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽池摧。三九已至,卻和暖如春激况,著一層夾襖步出監(jiān)牢的瞬間作彤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,026評論 1 266
  • 我被黑心中介騙來泰國打工乌逐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留竭讳,地道東北人。 一個月前我還...
    沈念sama閱讀 46,473評論 2 360
  • 正文 我出身青樓浙踢,卻偏偏與公主長得像绢慢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子洛波,可洞房花燭夜當晚...
    茶點故事閱讀 43,606評論 2 350