設(shè)計(jì)模式中的珠聯(lián)璧合

[TOC]

首先在開篇之前,我想先要解決一個(gè)問題:為什么要學(xué)設(shè)計(jì)模式服爷?按正經(jīng)的來說需要解決封裝性、松耦合获诈、可擴(kuò)展等問題仍源,但這里我先拋開這些不談,就我個(gè)人體會(huì)而言舔涎,一個(gè)很明顯的好處就是代碼的逼格變高了......隨之帶來的是“可讀性”變差了笼踩。之所以會(huì)這樣,其實(shí)就像一般人看不懂框架源碼一樣亡嫌,但你并不能說人家寫的不好嚎于,只不過你的理解和那些大牛的理解不在同一個(gè)層次上而已罷了。因此想要從一屆碼農(nóng)翻身藝術(shù)設(shè)計(jì)者挟冠,設(shè)計(jì)模式會(huì)是必要的取經(jīng)之路于购。

然而設(shè)計(jì)模式本身是一種抽象的設(shè)計(jì)概念,并沒有真正的代碼模板可以套用知染,至少網(wǎng)上能看到或書上翻到的代碼模板都是不適用的肋僧,生搬硬套是很多初學(xué)者的誤區(qū),無法理解設(shè)計(jì)模式的精髓控淡。它真正的使用方式還必須結(jié)合實(shí)際的代碼設(shè)計(jì)場(chǎng)景靈活搭配嫌吠,所以如果非要說有什么模板,那本文推薦的例子會(huì)是不錯(cuò)的選擇逸寓。

工廠+策略 優(yōu)化IF-ELSE

在大多數(shù)的業(yè)務(wù)代碼里少不了這樣的判斷:

public void doBusiness(TradeContext tradeContext) {

    String busiType = tradeContext.getBusiType();
    if ("1".equals(busiType)) {
        doBusi01(tradeContext);
    } else if ("2".equals(busiType)) {
        doBusi02(tradeContext);
    } else if ("3".equals(busiType)) {
        doBusi03(tradeContext);
    } else if ("4".equals(busiType) || "5".equals(busiType)) {
        doBusi045(tradeContext);
    } else if ("6".equals(busiType) || "7".equals(busiType)){
        doBusi067(tradeContext);
    } else if ("8".equals(busiType)) {
        doBusi08(tradeContext);
    } else if ("9".equals(busiType)) {
        doBusi09(tradeContext);
    } else {
        doBusiDefault(tradeContext);
    }
}

正是因?yàn)槌R娋诱祝赃@套組合也許會(huì)是常用的打法,眾所周知竹伸,策略模式能將各個(gè)子業(yè)務(wù)邏輯拆到一個(gè)個(gè)類里面實(shí)現(xiàn)泥栖,但是對(duì)調(diào)用者來說它必須得知道每一種類型對(duì)應(yīng)的實(shí)現(xiàn)類類名是什么簇宽,然而工廠模式又恰好能夠屏蔽這個(gè)細(xì)節(jié),讓調(diào)用者可以優(yōu)雅簡(jiǎn)化一大層的邏輯判斷吧享。

而說到工廠模式魏割,你可能會(huì)聯(lián)想三種:簡(jiǎn)單工廠+抽象工廠+工廠方法,簡(jiǎn)單工廠畢竟簡(jiǎn)單自有它的實(shí)用之處钢颂,但是它對(duì)“新增注冊(cè)”產(chǎn)品不友好钞它,而后兩種并不是這里需要闡述的,因?yàn)槠鋬?nèi)容混雜即難以實(shí)用殊鞭,而且有更強(qiáng)大的工廠可代替遭垛,那就是這里主要要闡述的Spring工廠

為什么也把它算在內(nèi),因?yàn)楝F(xiàn)在的Java項(xiàng)目無論大小都已經(jīng)脫離不了Spring框架支撐了操灿,把它作為必選套餐沒什么毛病锯仪,而Spring天生就有強(qiáng)大的容器可以作為工廠的存在,已經(jīng)包含了各種工廠的實(shí)現(xiàn)并支持混合運(yùn)用趾盐,因此把它作為工廠模式的最佳實(shí)現(xiàn)我覺得沒什么毛病庶喜。

因此接下來就說一下如何通過Spring工廠+策略模式來優(yōu)化上述IF-ELSE結(jié)構(gòu)

首先先定義產(chǎn)品接口,這個(gè)產(chǎn)品其實(shí)就是策略的抽象救鲤,但相對(duì)于策略抽象的接口久窟,多了一個(gè)方法,待會(huì)就知道它是做什么用的了:

public interface BusiStrategy {

    /**
     * 具體業(yè)務(wù)邏輯的抽象方法
     * @param tradeContext
     */
    void doBusi(TradeContext tradeContext);

    /**
     * 用于匹配該類適用于哪種業(yè)務(wù)類型
     * @return
     */
    String[] supports();

}

接下來就完成這個(gè)接口的實(shí)現(xiàn)本缠,也就是具體的策略產(chǎn)品斥扛,這里以Busi01舉例,當(dāng)然實(shí)際要根據(jù)自己的業(yè)務(wù)場(chǎng)景給個(gè)好聽的類名哦

@Component
public class Busi01Strategy implements BusiStrategy {

    @Override
    public void doBusi(TradeContext tradeContext) {
        // TODO handle busi01
    }

    @Override
    public String[] supports() {
        return new String[] {"1"};
    }
}

其他的照貓畫虎就行搓茬,由于support返回的是String[]數(shù)組犹赖,因此如果有多個(gè)類型的也同樣適用哦~

最后有個(gè)重要的點(diǎn),就是這些策略產(chǎn)品必須注冊(cè)到Spring容器上卷仑,無論用什么樣的方式均可峻村,比如這里用最常見的注解方式注冊(cè)

最后就要?jiǎng)?chuàng)建策略工廠了,也是整個(gè)策略模式實(shí)現(xiàn)的核心锡凝,同樣這個(gè)工廠也需要注冊(cè)到spring容器上粘昨,而且還要在spring bean的生命周期上做點(diǎn)事情

@Component
public class BusiStrategyFactory implements InitializingBean {

    /**
     * 這個(gè)是從容器中獲取得到的所有BusiStrategy實(shí)現(xiàn)類的Map
     * 這個(gè)Map的key值對(duì)應(yīng)的是容器中的beanName,沒有業(yè)務(wù)含義窜锯,也不需要賦予業(yè)務(wù)含義
     */
    @Autowired
    private Map<String, BusiStrategy> busiStrategyContextMap;

    /**
     * 這個(gè)是該工廠方法需要獲取的Map张肾,這個(gè)Map也包含了所有BusiStrategy實(shí)現(xiàn)類
     * 但這個(gè)Map的key值是帶有業(yè)務(wù)含義的,是busiType锚扎。
     */
    private Map<String, BusiStrategy> busiStrategyFactoryMap = new HashMap<>();

    /**
     * 獲取產(chǎn)品的方法吞瞪,根據(jù)busiType直接獲取具體的業(yè)務(wù)實(shí)現(xiàn)類
     * @param busiType
     * @return
     */
    public BusiStrategy getBusiStrategy(String busiType) {
        BusiStrategy busiStrategy = busiStrategyFactoryMap.get(busiType);
        // The following code will support on Java8 Optional Class
        // return Optional.ofNullable(busiStrategy).orElse(DefaultBusiStrategy.instance);
        if (busiStrategy != null) {
            return busiStrategy;
        } else {
            return DefaultBusiStrategy.instance;
        }
    }

    /**
     * 產(chǎn)品列表注冊(cè)初始化,該方法會(huì)在spring容器啟動(dòng)時(shí)加載該工廠的初始化階段調(diào)用
     * (這個(gè)時(shí)候已經(jīng)完成了屬性注入和@Autowire的注解注入了)
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        for (BusiStrategy busiStrategy : busiStrategyContextMap.values()) {
            String[] supports = busiStrategy.supports();
            for (String support : supports) {
                busiStrategyFactoryMap.put(support, busiStrategy);
            }
        }
    }

    /**
     * 默認(rèn)的策略單例實(shí)現(xiàn)
     */
    private static class DefaultBusiStrategy implements BusiStrategy {

        private static BusiStrategy instance = new DefaultBusiStrategy();

        private DefaultBusiStrategy() {}

        @Override
        public void doBusi(TradeContext tradeContext) {
            System.out.println("could not find strategy in factory");
        }

        @Override
        public String[] supports() {
            return new String[0];
        }
    }
}

在這里涉及到兩個(gè)知識(shí)點(diǎn)驾孔,一個(gè)是@Autowired注解注入的類型為Map類型時(shí)芍秆,會(huì)把所有符合類型的beans注入惯疙,其key值是唯一的beanName。

另外就是InitializingBean的作用了妖啥,實(shí)現(xiàn)了這個(gè)接口的bean會(huì)在加載該bean的初始化階段回調(diào)afterPropertiesSet方法霉颠,這是spring bean生命周期內(nèi)的一部分內(nèi)容,我會(huì)在下一個(gè)專題詳細(xì)講述荆虱。注意這段初始化邏輯不能寫在構(gòu)造函數(shù)里蒿偎,因?yàn)樵跇?gòu)造函數(shù)階段屬性還沒賦值,@Autowired未生效怀读,會(huì)造成空指針異常诉位。

最后該工廠還提供了一個(gè)默認(rèn)單例實(shí)現(xiàn),是為了避免從容器中獲取不到實(shí)例而導(dǎo)致空指針異常愿吹。這個(gè)默認(rèn)單例就不需要依托于spring容器的管理了不从,而關(guān)于如何自己實(shí)現(xiàn)單例模式這里也就不具體再展開惜姐。這里采用的是內(nèi)部類單例模式犁跪,既有懶加載的作用也還保證了線程安全,在大多數(shù)非極端場(chǎng)合已經(jīng)足夠使用歹袁。

也許到這里大家還是會(huì)對(duì)這個(gè)核心實(shí)現(xiàn)類很懵坷衍,不過沒關(guān)系,記住就行了条舔,以后都是這樣的固定化模板枫耳,換個(gè)名字就可以直接套用了。

最后我們?cè)賮砜纯疵峡梗ㄟ^這樣改造后迁杨,核心業(yè)務(wù)邏輯會(huì)得到怎樣的優(yōu)化:

public void optimizeBusiness(TradeContext tradeContext) {
    String busiType = tradeContext.getBusiType();
    BusiStrategy busiStrategy = busiFactory.getBusiStrategy(busiType);

    busiStrategy.doBusi(tradeContext);
}

可以看到主流程及其簡(jiǎn)單,不需要任何IF-ELSE凄硼,十多行判斷的死代碼瞬間化為3行铅协,而且以后要是新增類型,新增策略產(chǎn)品類就可以了摊沉,不需要改主流程的代碼狐史,也不需要改策略工廠的代碼,這也體現(xiàn)了設(shè)計(jì)模式設(shè)計(jì)的總體大原則:多新增-少修改

裝飾者+適配器 源碼擴(kuò)展神器

實(shí)際業(yè)務(wù)編碼中可能都有過想改源碼的沖動(dòng)说墨,可是要么這源碼是編譯好的.class文件不可修改骏全,要么就是公共代碼被其他模塊大量引用不敢修改,那有沒有辦法優(yōu)雅的改源碼——即能滿足當(dāng)前功能的擴(kuò)展需求尼斧,又不去改動(dòng)舊有代碼而兼容呢姜贡?

曾采訪過很多同學(xué),他們的答案如出一轍:繼承要改的類棺棵,然后重寫掉要改的方法楼咳。沒錯(cuò)潘悼,就是這么簡(jiǎn)單,(那還需要我嗶嗶啥)

其實(shí)所謂適配器不過是高尚人士的說法而已了爬橡,其實(shí)它的核心體現(xiàn)就是繼承或?qū)崿F(xiàn)治唤,它能使得被適配的類能夠擴(kuò)展功能,并能在舊有的API接口定義不變的情況下讓新類得到調(diào)用糙申。

話說起來是那么簡(jiǎn)單宾添,但實(shí)際上操作起來其實(shí)沒那么方便,具體什么原因我也道不出什么所以然柜裸,我只知道這個(gè)時(shí)候裝飾者要出場(chǎng)了缕陕。

其實(shí)所謂裝飾者不過也是高尚人士的說法罷了,說白一點(diǎn)它就是包裝疙挺,再說直白一點(diǎn)它就是組合的設(shè)計(jì)扛邑。把要適配的類作為新類的成員變量,用構(gòu)造函數(shù)的方式將它傳入就可以了铐然。

據(jù)我“改”源碼經(jīng)驗(yàn)來看蔬崩,這套打法不像工廠+策略那樣,能總結(jié)出有什么標(biāo)準(zhǔn)化使用場(chǎng)景和模板搀暑,這也許就是設(shè)計(jì)模式中的那種抽象藝術(shù)所在吧沥阳,我只能說當(dāng)你沖動(dòng)的時(shí)候不妨再?zèng)_動(dòng)點(diǎn),想要繼承改源碼的時(shí)候不妨再配個(gè)組合自点,如果被適配的類是有接口實(shí)現(xiàn)的桐罕,除非被適配的API定義的類型不是接口類型,否則更推薦去實(shí)現(xiàn)接口而不要繼承來完成適配器桂敛,最后你會(huì)發(fā)現(xiàn)如此設(shè)計(jì)的代碼蘊(yùn)含著何等的藝術(shù)功炮。

吹了那么多,我以實(shí)際場(chǎng)景來總結(jié)一下這套組合打法會(huì)是怎樣的效果吧:

先簡(jiǎn)單介紹一下實(shí)際項(xiàng)目的背景:項(xiàng)目采用的是Mybatis + PageHelper 做分頁實(shí)現(xiàn)的术唬,PageHelper中默認(rèn)自帶了一個(gè)分頁類PageInfo薪伏,用于攜帶數(shù)據(jù)庫(kù)分頁查詢返回結(jié)果信息。而在項(xiàng)目框架中是不會(huì)直接將PageInfo對(duì)象作為接口返回字段的碴开,因此整個(gè)項(xiàng)目規(guī)范了分頁返回對(duì)象為PageVo毅该,這個(gè)類定義在公共的依賴包里,為了讓PageInfo對(duì)象快速化為PageVo對(duì)象潦牛,就定義了對(duì)應(yīng)的構(gòu)造方法:

/**
 *  公共Vo對(duì)象之眶掌,分頁數(shù)據(jù)存儲(chǔ)的Vo對(duì)象
 * @param <T>
 */
@Data
public class PageVo<T> implements Serializable {

    private static final long serialVersionUID = -2207112935012444854L;

    private Page pageInfo;
    
    private List<T> data;
    
    public PageVo() {
        pageInfo = new Page();
    }
    
    public PageVo(PageInfo<T> pageInfo) {
        this();
        this.pageInfo.currentPage = pageInfo.getPageNum();
        this.pageInfo.pageSize = pageInfo.getPageSize();
        this.pageInfo.length = pageInfo.getSize();
        this.pageInfo.total = pageInfo.getTotal();
        this.pageInfo.totalPage = pageInfo.getPages();
        this.data = pageInfo.getList();
        this.pageInfo.isFirst = pageInfo.isIsFirstPage();
        this.pageInfo.hasNext = pageInfo.isHasNextPage();
    }
    
    public PageVo(PageInfo<?> pageInfo, List<T> rows) {
        this();
        this.pageInfo.currentPage = pageInfo.getPageNum();
        this.pageInfo.pageSize = pageInfo.getPageSize();
        this.pageInfo.length = pageInfo.getSize();
        this.pageInfo.total = pageInfo.getTotal();
        this.pageInfo.totalPage = pageInfo.getPages();
        this.data = rows;
        this.pageInfo.isFirst = pageInfo.isIsFirstPage();
        this.pageInfo.hasNext = pageInfo.isHasNextPage();
    }
    
    @Data
    private class Page implements Serializable {

        private static final long serialVersionUID = -4019585481529601742L;

        private Integer currentPage;
        /** 每頁條數(shù) **/
        private Integer pageSize;
        /** 當(dāng)前頁的條數(shù) **/
        private Integer length;
        /** 總記錄數(shù) **/
        private Long total;
        /** 總頁數(shù) **/
        private Integer totalPage;
        
        private Boolean isFirst;
        
        private Boolean hasNext;
    }
    
}

在大多數(shù)情況,這樣設(shè)計(jì)完全沒問題巴碗,數(shù)據(jù)庫(kù)查詢出來的分頁對(duì)象通過構(gòu)造方法轉(zhuǎn)為PageVo返回給接口調(diào)用層朴爬。但后來項(xiàng)目用到了elasticSearch這種高級(jí)的東西,數(shù)據(jù)源不再是數(shù)據(jù)庫(kù)了橡淆,elasticSearch使用的API是spring-data提供的召噩,它又定義了一套分頁查詢結(jié)果對(duì)象標(biāo)準(zhǔn)Page接口耍群,此時(shí)很自然地我需要為PageVo添加一個(gè)構(gòu)造方法兼容返回蛇更。

但在項(xiàng)目中,PageVo是定義在公共依賴包里的一套規(guī)范之一,先不說實(shí)際項(xiàng)目能不能改堰酿,即使開放出來改役首,在公共依賴級(jí)別引入別的依賴绑咱,如果不做好評(píng)估爵嗅,搞不好對(duì)其他模塊造成依賴污染和依賴沖突問題。

因此想要入手解決這個(gè)問題疲恢,只能另辟蹊徑凶朗,找準(zhǔn)適配點(diǎn),因此用于適配spring-dataPage接口和PageHelperPageInfo的適配器就誕生了显拳,通過適配PageInfo進(jìn)而間接完成了到PageVo的轉(zhuǎn)換棚愤。

/**
 * Mybatis-PageHelper 的 PageInfo 和 JPA 的 Page 適配器
 *  同時(shí)是裝飾類  解決elasticsearch不分頁的痛
 *
 * @param <T>
 */
public class PageInfoAdaptor<T> extends PageInfo<T> {

    private static final long serialVersionUID = 1L;
    
    private Page<T> pageInfo;
    
    public PageInfoAdaptor(Page<T> pageInfo) {
        this(pageInfo, null);
    }

    public PageInfoAdaptor(Page<T> pageInfo, Pageable page) {
        super();
        // 這里可以先簡(jiǎn)單理解為 this.pageInfo = pageInfo  此處的目的是讓elasticsearch分頁結(jié)果支持更多的信息
        this.pageInfo = page == null ? pageInfo : new PageImpl<>(pageInfo.getContent(), page, pageInfo.getTotalElements());
    }

    @Override
    public int getPageNum() {
        return pageInfo.getNumber() + 1;
    }
    
    @Override
    public int getPageSize() {
        return pageInfo.getSize();
    }
    
    @Override
    public int getSize() {
        return pageInfo.getNumberOfElements();
    }

    @Override
    public long getTotal() {
        return pageInfo.getTotalElements();
    }
    
    @Override
    public int getPages() {
        return pageInfo.getTotalPages();
    }

    @Override
    public List<T> getList() {
        return pageInfo.getContent();
    }
    // 省略其他操作...
    
    @Override
    public boolean isHasPreviousPage() {
        throw new UnsupportedOperationException("適配器不支持set操作");
    }

    @Override
    public void setPageNum(int pageNum) {
        throw new UnsupportedOperationException("適配器不支持setPageNum操作");
    }

    // 省略其他不支持的操作
    
    @Override
    public String toString() {
        return pageInfo.toString();
    }
}

這里可以對(duì)比一下,Spring-data設(shè)計(jì)的Page接口杂数,哪怕它自己只有一個(gè)實(shí)現(xiàn)類宛畦,但相對(duì)于PageHelper這個(gè)插件的PageInfo實(shí)體類顯然更具有擴(kuò)展性,這也可以體現(xiàn)出Spring框架之所以能包容萬象的魅力所在耍休。

模板方法 抽取公共邏輯

說到抽取公共邏輯刃永,減少冗余代碼,想到的應(yīng)該更多是抽取公共方法和工具類羊精,其實(shí)模板方法設(shè)計(jì)模式也可以做這件事情,但嚴(yán)格來講它和常規(guī)的抽取方法有所不同囚玫,它側(cè)重于抽取流程框架/算法骨架喧锦,而讓具體的分支細(xì)節(jié)由不同的子類去實(shí)現(xiàn),從這點(diǎn)上看它和抽取方法所達(dá)到的效果是不同的抓督。

所以這套設(shè)計(jì)其實(shí)也沒什么模板和特定的使用場(chǎng)景燃少,只能說在設(shè)計(jì)的時(shí)候可以多一種思路,多一份意識(shí)铃在,并有意為之阵具,有時(shí)候甚至可以把它當(dāng)作萬能膠水,配合工廠和策略能設(shè)計(jì)出更美妙的藝術(shù)定铜。

構(gòu)造器 不定參數(shù)構(gòu)造

一般來說幾個(gè)參數(shù)就直接定義阳液,多個(gè)固定參數(shù)就封裝實(shí)體,但參數(shù)可多可少揣炕,可有可無帘皿,這時(shí)候就免不了考慮構(gòu)造器模式了。

在一般框架中畸陡,構(gòu)造器還是用的挺多的鹰溜,而且要構(gòu)造的對(duì)象不僅僅是傳統(tǒng)意義上的"對(duì)象"虽填,還有可能是String或者特定集合的包裝。用好構(gòu)造器模式曹动,你就可以享受函數(shù)式編程的快感斋日,更有甚者,可以參考Java8中的stream API流式編程墓陈,你會(huì)得到雙份的快樂桑驱。

裝飾者+代理模式 批量增強(qiáng)

裝飾者模式可以做到單個(gè)方法或單個(gè)類的增強(qiáng),而要想多個(gè)方法或多個(gè)類一起增強(qiáng)跛蛋,那就少不了代理模式了熬的,其實(shí)嚴(yán)格意義上說并不存在裝飾+代理這種組合,因?yàn)榇砟J奖旧砭托枰b赊级,它沒有那么強(qiáng)大的能力讓對(duì)象本身自行增強(qiáng)押框,在這里提出來也就想要強(qiáng)調(diào)一點(diǎn):代理必包裝。

代理模式談起來只是一個(gè)抽象理逊,動(dòng)態(tài)代理更是這個(gè)抽象上的一層分支橡伞,在java中它的實(shí)現(xiàn)主要有兩種形態(tài),這里鑒于大家平時(shí)用得比較少晋被,就簡(jiǎn)單來過一遍:

JDK代理

JDK本身就已經(jīng)實(shí)現(xiàn)了一套自帶的代理工具兑徘,它的代理需要基于被代理對(duì)象有接口實(shí)現(xiàn),首先需要定義一個(gè)增強(qiáng)器羡洛,實(shí)現(xiàn)InvocationHandler接口挂脑,在它里面實(shí)現(xiàn)需要增強(qiáng)的操作

public class JDKProxy implements InvocationHandler {

    private Object target; // 傳入目標(biāo)代理對(duì)象

    /**
    * @param target: 被代理對(duì)象,從這里也體現(xiàn)出一種包裝欲侮,但真正的包裝不是這個(gè)類崭闲,這個(gè)類只是個(gè)增強(qiáng)器,包裝的目的是讓代理方法能調(diào)用到被代理對(duì)象的方法
    **/
    public JDKProxy(Object target) {
        this.target = target;
    }

    /**
    * @param proxy: 經(jīng)過代理后的對(duì)象
    * @param method: 需要被代理的方法威蕉,這里指的是被代理對(duì)象實(shí)現(xiàn)的所有接口方法
    * @param args: 方法參數(shù)
    **/
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("方法執(zhí)行前....");
        Object invoke = method.invoke(target, args);
        System.out.println("方法執(zhí)行后5蠹蟆!韧涨!");
        return invoke;
    }
}

有了這個(gè)增強(qiáng)器后牍戚,接下來就是把被代理對(duì)象搭配增強(qiáng)器生成最終的代理對(duì)象,這里就需要用到JDK的代理工具類Proxy

IUserDao userDao = new UserDaoImpl();
InvocationHandler jdkProxy = new JDKProxy(userDao);
ClassLoader classLoader = userDao.getClass().getClassLoader();
Class<?>[] interfaces = userDao.getClass().getInterfaces();

// 通過JDK的代理工具類Proxy創(chuàng)建出了最終被代理對(duì)象
IUserDao proxyUserDao = (IUserDao) Proxy.newProxyInstance(classLoader, interfaces, jdkProxy);

通過newProxyInstance源碼可以看出虑粥,里面其實(shí)做了三個(gè)核心的步驟:

// 創(chuàng)建動(dòng)態(tài)代理類的Class對(duì)象
Class<?> proxyClazz = Proxy.getProxyClass(classLoader, interfaces);
// 通過反射技術(shù)獲得動(dòng)態(tài)代理類的構(gòu)造函數(shù)
Constructor<?> constructor = proxyClazz.getConstructor(new Class[]{InvocationHandler.class});
// 通過構(gòu)造函數(shù)創(chuàng)建出代理對(duì)象進(jìn)行調(diào)用
IUserDao proxyUserDao =  (IUserDao) constructor.newInstance(new Object[]{jdkProxy});

后兩個(gè)步驟知道反射操作的都可以理解如孝,核心是在第一步操作getProxyClass,繼續(xù)深入理解它就知道舀奶,它的本質(zhì)原理就是生成一個(gè)新的類暑竟,這個(gè)類將實(shí)現(xiàn)被代理對(duì)象接口的所有方法(同時(shí)還包括Objectequals/hashCode/toString),為每個(gè)方法重寫并統(tǒng)一調(diào)用增強(qiáng)器中定義的方法,最后通過classloader加載進(jìn)來并使用但荤。

所以其實(shí)動(dòng)態(tài)代理最終也是變成靜態(tài)代理罗岖,再本質(zhì)點(diǎn)就是包裝,只不過開發(fā)者懶腹躁,或者說不想寫那么多重復(fù)代碼桑包,于是便讓JVM幫忙寫,就演變成看似神奇又很牛逼的技術(shù)纺非。

cglib代理

cglib代理也是動(dòng)態(tài)代理的一種實(shí)現(xiàn)哑了,它是解決JDK代理中被代理對(duì)象必須依賴于接口的局限,cglib代理能給任意對(duì)象進(jìn)行代理烧颖,而且其實(shí)現(xiàn)原理是直接基于字節(jié)碼操作一步到位的弱左,有學(xué)者認(rèn)為會(huì)更高效,當(dāng)然在性能上的爭(zhēng)議不是這里需要討論的問題炕淮,大家只要知道它不需要依賴接口實(shí)現(xiàn)即可拆火。

使用它需要引入cglib包,它的使用思路也是跟JDK代理一致涂圆,也需要?jiǎng)?chuàng)建一個(gè)增強(qiáng)器(實(shí)現(xiàn)MethodInterceptor)并做一層包裝们镜,這里其實(shí)可以放在一起操作:

public class CglibProxy implements MethodInterceptor {

    private Object target;

    public Object getInstance(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        // 1. 設(shè)置代理對(duì)象為被代理對(duì)象的子類
        enhancer.setSuperclass(target.getClass());
        // 2. 重寫被代理對(duì)象的方法
        enhancer.setCallback(this);
        // 3. 創(chuàng)建出被代理對(duì)象
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理后對(duì)象:" + o.getClass());
        Object result = method.invoke(target, args);
        System.out.println("執(zhí)行結(jié)束!H笄浮模狭!");
        return result;
    }
}

這段代碼可以成為模板代碼,甚至可以改造成單例后做成工具類踩衩,為每一個(gè)需要代理的對(duì)象做一層包裝嚼鹉。cglib代理的原理要想深入理解起來確實(shí)有點(diǎn)復(fù)雜,但其實(shí)可以簡(jiǎn)單理解成就是生成一個(gè)新的類繼承了被代理對(duì)象九妈,重寫了所有被代理對(duì)象的方法實(shí)現(xiàn)反砌,使之調(diào)用了增強(qiáng)器定義的方法。

以上兩種都是動(dòng)態(tài)代理的基礎(chǔ)實(shí)現(xiàn)萌朱,實(shí)際用到的地方也不多,所以不是這里要介紹的重點(diǎn)策菜,因?yàn)橛袀€(gè)更強(qiáng)大的代替實(shí)現(xiàn)方案晶疼,那還是依賴于Spring的AOP代理

spring AOP代理

嚴(yán)謹(jǐn)一點(diǎn):AOP其實(shí)是面向切面編程的思想,不屬于代理模式的范疇

以上的代理能做到的是對(duì)方法的批量增量又憨,而要想做到對(duì)多個(gè)類的批量增量翠霍,那還可以借助更強(qiáng)大的基于容器管理的替代方案:Spring的AOP代理。

先來看下用法蠢莺,這里介紹的是基于注解的用法寒匙,因此除了spring-context外還需要spring-aspects依賴

首先定義好切面類,所謂切面,即包含切點(diǎn)和通知兩個(gè)要素锄弱,理解這兩個(gè)概念對(duì)AOP實(shí)現(xiàn)有很大幫助考蕾。

@Aspect
public class LogAspect {

    /**
     * 切點(diǎn)表達(dá)式,通常是一個(gè)空方法会宪,標(biāo)記@Ponitcut
     */
    @Pointcut("execution(public * com.edu.springlearn.aop.*.*(..))")
    public void pointCut() {

    }

    /**
     * 前置通知@Before肖卧,方法調(diào)用前執(zhí)行
     * @param joinPoint 連接點(diǎn)對(duì)象,可以從中獲取到對(duì)應(yīng)的方法簽名/類信息
     */
    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("BusinessService start, methodName is "+ name +" ,param is" + args);
    }

    /**
     * 結(jié)束通知掸鹅,無論正常還是異常都會(huì)執(zhí)行
     */
    @After("com.edu.springlearn.aop.LogAspect.pointCut()")
    public void logEnd() {
        System.out.println("BusinessService end");
    }

    /**
     * 返回通知塞帐,在方法正常返回時(shí)執(zhí)行
     * @param joinPoint
     * @param result  返回結(jié)果
     */
    @AfterReturning(value = "pointCut()", returning = "result")
    public void logReturn(JoinPoint joinPoint, Object result) {
        System.out.println("BusinessService success return, method name is : "+ joinPoint.getSignature().getName() +" ,result is" + result);
    }

    /**
     * 異常通知,方法執(zhí)行異常時(shí)執(zhí)行
     * @param ex
     */
    @AfterThrowing(value = "pointCut()", throwing = "ex")
    public void logException(Exception ex) {
        System.out.println("BusinessService occur an exception: " + ex.getMessage());
        ex.printStackTrace();
    }

}

以上切面類介紹了幾個(gè)必要的注解@Aspect表示切面巍沙,@Pointcut表示切點(diǎn)葵姥,通知類注解包括@Before/@After/@AfterReturning/@AfterThrowing以及還有更強(qiáng)大的環(huán)繞通知@Around

除此之外句携,還需要將寫好的這個(gè)切面類注冊(cè)到Spring容器中去榔幸,至于怎么注冊(cè)那就有很多辦法了,自己選擇务甥。

最后最關(guān)鍵的地方在于開啟@EnableAspectJAutoProxy注解功能牡辽,必須要在啟動(dòng)類上帶上這個(gè)注解,當(dāng)然現(xiàn)代的springboot項(xiàng)目以及默認(rèn)帶上了敞临。

因此整個(gè)AOP實(shí)現(xiàn)原理都在這個(gè)@EnableAspectJAutoProxy注解上态辛,這里就簡(jiǎn)單快速過一遍AOP的實(shí)現(xiàn)原理,先來看一張整體的時(shí)序圖:

SpringAOP.png

@EnableAspectJAutoProxy注解上標(biāo)記了一個(gè)@Import(AspectJAutoProxyRegistrar.class)AspectJAutoProxyRegistrar這個(gè)類是ImportBeanDefinitionRegistrar的實(shí)現(xiàn)類挺尿,在我另外一個(gè)專題:Spring注解驅(qū)動(dòng)開發(fā)一節(jié)會(huì)闡述奏黑,它會(huì)在Spring注冊(cè)階段執(zhí)行registerBeanDefinitions方法,因此就會(huì)執(zhí)行關(guān)鍵代碼:

AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

跟進(jìn)這段代碼编矾,就會(huì)發(fā)現(xiàn)它額外注冊(cè)了另一個(gè)關(guān)鍵類:AnnotationAwareAspectJAutoProxyCreator這個(gè)bean的名稱是org.springframework.aop.config.internalAutoProxyCreator

AnnotationAwareAspectJAutoProxyCreator.png

而從這個(gè)類AnnotationAwareAspectJAutoProxyCreator繼承關(guān)系可以知道熟史,它是SmartInstantiationAwareBeanPostProcessor的實(shí)現(xiàn)類,在我另外一個(gè)專題:Spring注解驅(qū)動(dòng)開發(fā)一節(jié)會(huì)闡述窄俏,它是一個(gè)經(jīng)典的后置處理器蹂匹,但它處理的階段是在Springbean的創(chuàng)建階段

那具體是在那個(gè)地方實(shí)現(xiàn)的呢凹蜈?可以通過AbstractAutowireCapableBeanFactory#createBean源碼中有這么一段代碼和一段注釋:

// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
    return bean;
}

也就是說限寞,剩余的所有springbean在創(chuàng)建前都會(huì)經(jīng)歷一遍InstantiationAwareBeanPostProcessor后置處理器的洗禮,如果它能返回一個(gè)代理對(duì)象仰坦,則直接返回履植,不會(huì)再經(jīng)歷正常的創(chuàng)建過程。

Object beanInstance = doCreateBean(beanName, mbdToUse, args);

因此最后我們很自然地跟到AbstractAutoProxyCreator#postProcessBeforeInstantiation方法悄晃,在里面有個(gè)關(guān)鍵方法:shouldSkip里的findCandidateAdvisors()最后來到:BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors

到這一步玫霎,它會(huì)將所有候選的@Aspect類中的增強(qiáng)器找出來,最終返回一個(gè)List<Advisor>增強(qiáng)器集合的結(jié)果。其中Advisor就是我們定義出來的通知方法的一個(gè)包裝庶近。

想知道具體怎么找出來的翁脆,可以繼續(xù)跟進(jìn),如果只是想知道主流程,那建議跳過

如果繼續(xù)跟進(jìn)拦盹,可以看到它會(huì)遍歷所有已注冊(cè)的bean鹃祖,拿到Class類型進(jìn)入判斷:this.advisorFactory.isAspect(beanType),這里就可以篩選出哪些是切面注解的類了。接著對(duì)于切面類又繼續(xù)進(jìn)入this.advisorFactory.getAdvisors(factory)在里面真正能把通知方法篩選出來的是AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod)內(nèi)的實(shí)現(xiàn):

private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[] {
            Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
/**
* Find and return the first AspectJ annotation on the given method(作者注:其中是已經(jīng)過濾掉Pointcut的)
*/
protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
    for (Class<?> clazz : ASPECTJ_ANNOTATION_CLASSES) {
        AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) clazz);
        if (foundAnnotation != null) {
            return foundAnnotation;
        }
    }
    return null;
}

最后找到的結(jié)果都會(huì)包裝成Advisor其具體實(shí)現(xiàn)類描述:

return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
this, aspectInstanceFactory, declarationOrderInAspect, aspectName);

0 = {InstantiationModelAwarePointcutAdvisorImpl@2004} "InstantiationModelAwarePointcutAdvisor: expression [pointCut()]; advice method [public void com.edu.springlearn.aop.LogAspect.logStart(org.aspectj.lang.JoinPoint)]; perClauseKind=SINGLETON"
1 = {InstantiationModelAwarePointcutAdvisorImpl@2101} "InstantiationModelAwarePointcutAdvisor: expression [com.edu.springlearn.aop.LogAspect.pointCut()]; advice method [public void com.edu.springlearn.aop.LogAspect.logEnd()]; perClauseKind=SINGLETON"
2 = {InstantiationModelAwarePointcutAdvisorImpl@2353} "InstantiationModelAwarePointcutAdvisor: expression [pointCut()]; advice method [public void com.edu.springlearn.aop.LogAspect.logReturn(org.aspectj.lang.JoinPoint,java.lang.Object)]; perClauseKind=SINGLETON"
3 = {InstantiationModelAwarePointcutAdvisorImpl@2354} "InstantiationModelAwarePointcutAdvisor: expression [pointCut()]; advice method [public void com.edu.springlearn.aop.LogAspect.logException(java.lang.Exception)]; perClauseKind=SINGLETON"

前面說到普舆,在創(chuàng)建階段后置處理器會(huì)優(yōu)先執(zhí)行恬口,如果有返回bean則直接返回,則不再進(jìn)入正常的bean創(chuàng)建過程沼侣。

protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
    // 省略了其它干擾代碼...
    Object bean = null;
    if (targetType != null) {
    bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
        if (bean != null) {
            bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
        }
    }
    return bean;
}

但遺憾的是這里真正代理后的bean不是在這里返回的祖能,這個(gè)前置處理器的主要任務(wù)就是找出了所有增強(qiáng)器而已。因此這些bean還是會(huì)經(jīng)歷正常的創(chuàng)建過程蛾洛,只不過在初始化階段會(huì)進(jìn)入AbstractAutoProxyCreator#postProcessAfterInitialization后置處理器處理养铸,一個(gè)很明顯的代碼標(biāo)示就在這里了:

/**
* Create a proxy with the configured interceptors if the bean is identified as one to proxy by the subclass.
* @see #getAdvicesAndAdvisorsForBean
*/
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

其中wrapIfNecessary實(shí)現(xiàn)有兩個(gè)關(guān)鍵操作:

Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); // 第一步:根據(jù)所有通知方法的切點(diǎn)表達(dá)式判斷該bean能應(yīng)用哪些通知方法(增強(qiáng)器)
if (specificInterceptors != DO_NOT_PROXY) {
    this.advisedBeans.put(cacheKey, Boolean.TRUE);
    Object proxy = createProxy(
            bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); // 創(chuàng)建代理對(duì)象
    this.proxyTypes.put(cacheKey, proxy.getClass());
    return proxy;
}

跳過第一步,來看第二步操作轧膘,最關(guān)鍵的地方在于DefaultAopProxyFactory#createAopProxy

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}

到這里就可以看到一些很熟悉的影子钞螟,spring會(huì)根據(jù)bean的類型自動(dòng)判斷使用JDK代理還是CGLIB代理。以CGLIB代理為例谎碍,繼續(xù)進(jìn)入CglibAopProxy#getCallbacks就知道鳞滨,它其實(shí)設(shè)定了DynamicAdvisedInterceptorMethodInterceptorcallback,所以到這里是不是已經(jīng)有了CGLIB熟悉的味道了蟆淀?

總結(jié)一下:首先由AbstractAutoProxyCreator這個(gè)后置處理器對(duì)容器創(chuàng)建的bean做了一層包裝wrapIfNecessary,這個(gè)方法內(nèi)會(huì)根據(jù)被代理對(duì)象的特性決定使用JDK動(dòng)態(tài)代理還是CGLIB動(dòng)態(tài)代理拯啦。

如果使用CGLIB動(dòng)態(tài)代理,使用的是CglibAopProxygetProxy方法會(huì)返回enhance.create返回的對(duì)象熔任,并設(shè)置callbackDynamicAdvisedInterceptorMethodInterceptor褒链。

這樣一來,容器中獲取到的被代理對(duì)象就為CGLIB代理后的對(duì)象了疑苔,接下來當(dāng)調(diào)用這個(gè)對(duì)象中符合切點(diǎn)表達(dá)式的方法時(shí)甫匹,自然會(huì)進(jìn)入DynamicAdvisedInterceptorintercept方法,那么Spring是如何設(shè)計(jì)使得這些雜亂無章的增強(qiáng)器能夠按照用戶給定希望的順序來執(zhí)行呢惦费?預(yù)知更多精彩赛惩,請(qǐng)看下節(jié)分解。

責(zé)任鏈模式

道理很簡(jiǎn)單趁餐,用著很高端,這種神奇的效果以至于你只能在源碼里頭才能看得到它的影子篮绰,因此上面拖了一大段在敘述SpringAOP的原理后雷,只不過為了引出這種設(shè)計(jì)模式

接著上節(jié),繼續(xù)分析DynamicAdvisedInterceptorintercept方法,首先通過下面語句獲得了一條增強(qiáng)器鏈臀突,通過上節(jié)分析可知勉抓,所謂增強(qiáng)器鏈就是被代理bean可適配的通知方法

List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

最后獲取到的增強(qiáng)器鏈就如下所示:

0 = {ExposeInvocationInterceptor@1966}
1 = {AspectJAfterThrowingAdvice@1967} "org.springframework.aop.aspectj.AspectJAfterThrowingAdvice: advice method [public void com.edu.springlearn.aop.LogAspect.logException(java.lang.Exception)]; aspect name 'logAspect'"
2 = {AfterReturningAdviceInterceptor@1968}
3 = {AspectJAfterAdvice@1969} "org.springframework.aop.aspectj.AspectJAfterAdvice: advice method [public void com.edu.springlearn.aop.LogAspect.logEnd()]; aspect name 'logAspect'"
4 = {MethodBeforeAdviceInterceptor@1970}

接下來核心步驟就是進(jìn)入CglibMethodInvocation#proceed方法了,把它整一塊貼出來看看:

public Object proceed() throws Throwable {
    // We start with an index of -1 and increment early.
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return invokeJoinpoint();
    }

    Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        // Evaluate dynamic method matcher here: static part will already have
        // been evaluated and found to match.
        InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
        if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
            return dm.interceptor.invoke(this);
        }
        else {
            // Dynamic matching failed.
            // Skip this interceptor and invoke the next in the chain.
            return proceed();
        }
    }
    else {
        // It's an interceptor, so we just invoke it: The pointcut will have
        // been evaluated statically before this object was constructed.
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}

首先是判斷當(dāng)前增強(qiáng)器鏈索引已經(jīng)是最后一個(gè)時(shí)候学,執(zhí)行被代理對(duì)象的對(duì)應(yīng)方法:invokeJoinpoint藕筋。當(dāng)然,由于一開始索引值為-1梳码,所以不會(huì)執(zhí)行隐圾。

接下來無論那個(gè)分支,其實(shí)都是進(jìn)入每一個(gè)MethodInterceptorinvoke方法掰茶,這個(gè)方法傳入的參數(shù)是this暇藏。

索引值遞增1,取第零個(gè)MethodInterceptor濒蒋,它是默認(rèn)的ExposeInvocationInterceptor,看看他的實(shí)現(xiàn):

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
    MethodInvocation oldInvocation = invocation.get();
    invocation.set(mi);
    try {
        return mi.proceed();
    }
    finally {
        invocation.set(oldInvocation);
    }
}

它好像沒做什么盐碱,直接執(zhí)行了mi.proceed()其中mi是剛才傳入的參數(shù)this,這不就又遞歸回到了上面標(biāo)記的函數(shù)去了嗎沪伙?

所以接下來要做的事情也一樣瓮顽,判斷當(dāng)前增強(qiáng)器鏈索引已經(jīng)是最后一個(gè)時(shí),執(zhí)行被代理對(duì)象的對(duì)應(yīng)方法:invokeJoinpoint围橡,此時(shí)的索引值是零暖混,由于還有其他增強(qiáng)器,所以不會(huì)執(zhí)行某饰。

索引值遞增1儒恋,取第一個(gè)增強(qiáng)器是AspectJAfterThrowingAdvice來看看它的invoke實(shí)現(xiàn):

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
    try {
        return mi.proceed();
    }
    catch (Throwable ex) {
        if (shouldInvokeOnThrowing(ex)) {
            invokeAdviceMethod(getJoinPointMatch(), null, ex);
        }
        throw ex;
    }
}

這個(gè)實(shí)現(xiàn)似乎也一樣,直接調(diào)用了mi.proceed()黔漂,只有當(dāng)它異常時(shí)才會(huì)調(diào)用異常通知所保存的通知方法invokeAdviceMethod诫尽。mi是剛才傳入的參數(shù)this,這不就又遞歸回到了上面標(biāo)記的函數(shù)去了嗎炬守?

所以接下來要做的事情也一樣牧嫉,判斷當(dāng)前增強(qiáng)器鏈索引已經(jīng)是最后一個(gè)時(shí),執(zhí)行被代理對(duì)象的對(duì)應(yīng)方法:invokeJoinpoint减途,此時(shí)的索引值是1酣藻,由于還有其他增強(qiáng)器,所以不會(huì)執(zhí)行鳍置。

索引值遞增1辽剧,取第二個(gè)增強(qiáng)器是AfterReturningAdviceInterceptor來看看它的invoke實(shí)現(xiàn):

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
    Object retVal = mi.proceed();
    this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
    return retVal;
}

非常簡(jiǎn)單,也是先調(diào)用mi.proceed()執(zhí)行完后再去回調(diào)AfterReturningAdviceInterceptor所保存的正常返回的通知方法税产。mi是剛才傳入的參數(shù)this怕轿,這不就又遞歸回到了上面標(biāo)記的函數(shù)去了嗎偷崩?

所以接下來要做的事情也一樣,判斷當(dāng)前增強(qiáng)器鏈索引已經(jīng)是最后一個(gè)時(shí)撞羽,執(zhí)行被代理對(duì)象的對(duì)應(yīng)方法:invokeJoinpoint阐斜,此時(shí)的索引值是2,由于還有其他增強(qiáng)器诀紊,所以不會(huì)執(zhí)行谒出。

索引值遞增1,取第三個(gè)增強(qiáng)器是AspectJAfterAdvice來看看它的invoke實(shí)現(xiàn):

try {
    return mi.proceed();
}
finally {
    invokeAdviceMethod(getJoinPointMatch(), null, null);
}

同樣也是先直接調(diào)用mi.proceed()邻奠,執(zhí)行完最終才會(huì)調(diào)用該advice所保存的通知方法笤喳。mi是剛才傳入的參數(shù)this,這不就又遞歸回到了上面標(biāo)記的函數(shù)去了嗎惕澎?

所以接下來要做的事情也一樣莉测,判斷當(dāng)前增強(qiáng)器鏈索引已經(jīng)是最后一個(gè)時(shí),執(zhí)行被代理對(duì)象的對(duì)應(yīng)方法:invokeJoinpoint唧喉,此時(shí)的索引值是3捣卤,由于還有其他增強(qiáng)器,所以不會(huì)執(zhí)行八孝。

索引值遞增1董朝,取第四個(gè)增強(qiáng)器是MethodBeforeAdviceInterceptor來看看它的invoke實(shí)現(xiàn):

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
    this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
    return mi.proceed();
}

此時(shí)不一樣了,它先調(diào)用了該AdviceInterceptor對(duì)應(yīng)的通知方法干跛,也就是@Before標(biāo)記的前置通知子姜,然后再進(jìn)行調(diào)用mi.proceed()。mi是剛才傳入的參數(shù)this楼入,這不就又遞歸回到了上面標(biāo)記的函數(shù)去了嗎哥捕?

所以接下來要做的事情也一樣,判斷當(dāng)前增強(qiáng)器鏈索引已經(jīng)是最后一個(gè)時(shí)嘉熊,執(zhí)行被代理對(duì)象的對(duì)應(yīng)方法:invokeJoinpoint遥赚,此時(shí)的索引值是4,已經(jīng)是最后一個(gè)了阐肤,所以調(diào)用完成了凫佛。

此時(shí)mi.proceed()執(zhí)行返回了,隨即回到上一個(gè)MethodBeforeAdviceInterceptor也對(duì)應(yīng)成功返回了孕惜,接著又回到由AspectJAfterAdvice調(diào)用的mi.proceed()也成功返回了愧薛,接著AspectJAfterAdviceinvoke函數(shù)也正常執(zhí)行完成,調(diào)用finally代碼塊函數(shù)執(zhí)行了通知方法衫画。接著回到了由AfterReturningAdviceInterceptor調(diào)用的mi.proceed()也成功返回了毫炉,接著AspectJAfterAdviceinvoke函數(shù)執(zhí)行了后面的通知方法,并返回了執(zhí)行結(jié)果削罩。接著回到了AspectJAfterThrowingAdvice調(diào)用的mi.proceed()也成功返回了碘箍,接著AspectJAfterAdviceinvoke函數(shù)沒有異常拋出遵馆,catch代碼塊不會(huì)執(zhí)行。最后就是默認(rèn)的adviceInterceptor丰榴,也成功執(zhí)行完成了。

面向切面編程是構(gòu)建大型項(xiàng)目或框架封裝都少不了的重要思想秆撮,在業(yè)務(wù)上日志記錄/權(quán)限攔截都可能需要它四濒,在框架上事務(wù)控制/RPC遠(yuǎn)程調(diào)用也都少不了它。Spring結(jié)合它自身強(qiáng)大的容器實(shí)現(xiàn)的AOP职辨,在神奇之余也困擾著大眾開發(fā)者盗蟆,例如最經(jīng)典的事務(wù)注解@Transactional,用著它卻抱怨事務(wù)不生效舒裤,雖然歸總起來原因有很多喳资,但實(shí)際上大多數(shù)情況都是一個(gè)大原則沒把握住,那就是:代理必裝飾

觀察者模式

觀察者模式這詞眼確實(shí)挺拗口的腾供,它更像是一種事件委派機(jī)制或者說是一種通知響應(yīng)機(jī)制仆邓,它的主要目的就是為了解耦并方便擴(kuò)展,在前端開發(fā)的領(lǐng)域可能更有深刻體會(huì)伴鳖,例如在用戶點(diǎn)擊鼠標(biāo)事件時(shí)需要通知到各個(gè)事件回調(diào)函數(shù)去處理节值,或例如在Vue中當(dāng)js變量發(fā)生變化時(shí)需要通知到視圖中使用到這個(gè)變量的位置都發(fā)生改變。

觀察者模式的實(shí)現(xiàn)本身不難榜聂,核心就是要設(shè)計(jì)一個(gè)觀察者并維護(hù)一個(gè)觀察者對(duì)象的集合搞疗,調(diào)用觀察者的事件觸發(fā)函數(shù)時(shí)需要將集合中的每個(gè)對(duì)象進(jìn)行響應(yīng)回調(diào),在JDK中就已經(jīng)封裝了觀察者模式的實(shí)現(xiàn)须肆,主要核心類就是Observable觀察者

// 這里來研究觀察者的核心源碼
public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs; // 觀察者對(duì)象集合

    /** Construct an Observable with zero Observers. */
    public Observable() {
        obs = new Vector<>();
    }

    public synchronized void addObserver(Observer o) {
        // 觀察者對(duì)象維護(hù)方法之 添加觀察者對(duì)象 (這例如在js中的addEventListener)
        obs.addElement(o);
    }

    public synchronized void deleteObserver(Observer o) {
        // 觀察者對(duì)象維護(hù)方法之 刪除觀察者對(duì)象 (這例如在js中的removeEventListener)
        obs.removeElement(o);
    }

    public void notifyObservers() {
        notifyObservers(null);
    }

    public void notifyObservers(Object arg) {
        // 派發(fā)通知的方法 (這例如在js中的各種事件函數(shù)click等)
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }
}

那觀察者對(duì)象是什么呢匿乃,它其實(shí)就是一個(gè)接口定義,實(shí)現(xiàn)了這個(gè)接口的都可以作為觀察者對(duì)象:

public interface Observer {
    // 事件響應(yīng)回調(diào)
    void update(Observable o, Object arg);
}

以上就是JDK觀察者的基本實(shí)現(xiàn)豌汇,可以看到還是非常好理解的幢炸,但是在實(shí)際項(xiàng)目中確并不會(huì)用這種默認(rèn)實(shí)現(xiàn)的觀察者,這種基本實(shí)現(xiàn)很明顯可以看出幾個(gè)問題:

  1. 為了保證線程安全而使用了重量級(jí)的synchronized + Vector 方式(先不說其本身的優(yōu)化)瘤礁。
  2. Observer的回調(diào)中直接將Observable作為參數(shù)傳遞本身不夠優(yōu)雅阳懂,在回調(diào)中又開放了adddelete方法。
  3. 要手動(dòng)區(qū)分不同類型的事件柜思,雖然可以通過創(chuàng)建多個(gè)Observable解決敢艰,但缺乏語義,不夠優(yōu)雅俯艰。
  4. 擴(kuò)展性還有優(yōu)化空間权悟,新增或刪除觀察者對(duì)象時(shí)仍需手動(dòng)維護(hù)Observable,也不夠優(yōu)雅陨享。
  5. 事件響應(yīng)的方式是同步的葱淳,更多希望是有異步支持钝腺。

為此基于強(qiáng)大容器加持的Spring設(shè)計(jì)的觀察者模式才會(huì)是實(shí)際真正可用的模式,也是本文所想介紹的重點(diǎn)赞厕,先來看看基于Spring的觀察者應(yīng)如何開發(fā):

先來看下最基本的事件響應(yīng)接口實(shí)現(xiàn)

public class SpringEventListener implements ApplicationListener<ApplicationEvent> {

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("收到事件 :" + event.getClass());
        System.out.println("事件參數(shù) :" + event.getSource());
    }
}

當(dāng)然這個(gè)類要起作用也需要注冊(cè)到spring容器中去

ApplicationEvent是事件類型的接口艳狐,按照這種默認(rèn)的方式在最基礎(chǔ)的容器可以收到兩大事件,分別為容器啟動(dòng)完成事件和容器銷毀事件皿桑。

收到事件 :class org.springframework.context.event.ContextRefreshedEvent
事件參數(shù) :org.springframework.context.annotation.AnnotationConfigApplicationContext@17f052a3, started on Sat Oct 17 23:01:09 CST 2020
收到事件 :class org.springframework.context.event.ContextClosedEvent
事件參數(shù) :org.springframework.context.annotation.AnnotationConfigApplicationContext@17f052a3, started on Sat Oct 17 23:01:09 CST 2020

以上是Spring默認(rèn)給出的事件毫目,那我們又如何定義自己的事件并派發(fā)和響應(yīng)呢?

首先先來自定義個(gè)事件:

public class MyEvent extends ApplicationEvent {


    /**
     * Create a new {@code ApplicationEvent}.
     *
     * @param source the object on which the event initially occurred or with which the event is associated (never {@code null})
     */
    public MyEvent(Object source) {
        super(source);
    }
}

接著在業(yè)務(wù)代碼里如何派發(fā)這個(gè)事件呢诲侮,要想派發(fā)事件就需要有事件派發(fā)器ApplicationEventPublisher镀虐,而這個(gè)事件派發(fā)器可以通過Aware接口注入,也可以通過@Autowire注解自動(dòng)注入沟绪,因?yàn)樗?code>Spring的內(nèi)部bean(實(shí)際上就是ApplicationContext)

@Service
public class MyEventPublisher implements ApplicationEventPublisherAware {

    //@Autowired
    private ApplicationEventPublisher eventPublisher;

    public void publishMyEvent() {
        eventPublisher.publishEvent(new MyEvent("我的自定義事件"));
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.eventPublisher = applicationEventPublisher;
    }
}

那么在響應(yīng)自定義事件時(shí)就可以按照上面的方式進(jìn)行響應(yīng)了:

@Service
public class MyEventListener implements ApplicationListener<MyEvent> {


    @Override
    public void onApplicationEvent(MyEvent event) {
        System.out.println("收到事件 :" + event.getClass());
        System.out.println("事件參數(shù) :" + event.getSource());
    }
}

這樣以后要擴(kuò)展事件監(jiān)聽器的時(shí)候只需要新增一個(gè)類刮便,并將其注冊(cè)到Spring容器中即可,不需要改變?cè)械臉I(yè)務(wù)代碼绽慈。

那么Spring的事件派發(fā)和響應(yīng)又是什么個(gè)原理呢恨旱?換句話說Spring如何利用自己強(qiáng)大的容器優(yōu)勢(shì)實(shí)現(xiàn)這種觀察者模式的呢?接下來我們進(jìn)入publishEvent方法一探究竟久信。

很快我們就進(jìn)入到了AbstractApplicationContext.publishEvent方法:

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
    Assert.notNull(event, "Event must not be null");

    // Decorate event as an ApplicationEvent if necessary
    ApplicationEvent applicationEvent;
    if (event instanceof ApplicationEvent) {
        applicationEvent = (ApplicationEvent) event;
    }
    else {
        applicationEvent = new PayloadApplicationEvent<>(this, event);
        if (eventType == null) {
            eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
        }
    }

    // Multicast right now if possible - or lazily once the multicaster is initialized
    if (this.earlyApplicationEvents != null) {
        this.earlyApplicationEvents.add(applicationEvent);
    }
    else {
        getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
    }

    // Publish event via parent context as well...
    if (this.parent != null) {
        if (this.parent instanceof AbstractApplicationContext) {
            ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
        }
        else {
            this.parent.publishEvent(event);
        }
    }
}

先來關(guān)注其重點(diǎn)代碼:

getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);

這是一個(gè)事件廣播器窖杀,其默認(rèn)初始化的是SimpleApplicationEventMulticaster,進(jìn)入其multicastEvent方法

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    Executor executor = getTaskExecutor();
    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        if (executor != null) {
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            invokeListener(listener, event);
        }
    }
}

首先可以看到它會(huì)匹配所有該事件類型的事件監(jiān)聽器:

getApplicationListeners(event, type)

其次會(huì)去找異步處理器

getTaskExecutor()

如果能找到則通過異步的方式回調(diào)事件響應(yīng)invokeListener裙士,如果沒有則通過同步的方式響應(yīng)invokeListener入客,最終都會(huì)回調(diào)自己寫的事件響應(yīng)方法了。

那么事件廣播器EventMulticaster又是怎么初始化的呢腿椎?同時(shí)事件廣播器又如何注冊(cè)所有的EventListener監(jiān)聽器的呢桌硫?這就得回到容器初始化的代碼開始走起了:

來進(jìn)入AbstractApplicationContext.refresh方法,里面有兩個(gè)階段是值得關(guān)注的:

@Override
public void refresh() throws BeansException, IllegalStateException {
// ... 省略萬行代碼
    // Initialize event multicaster for this context.
    initApplicationEventMulticaster();

    // Initialize other special beans in specific context subclasses.
    onRefresh();

    // Check for listener beans and register them.
    registerListeners();
// ... 省略萬行代碼
}

函數(shù)字面意思已經(jīng)解釋得很清楚了啃炸,initApplicationEventMulticaster就是初始化事件廣播器:

protected void initApplicationEventMulticaster() {
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
        this.applicationEventMulticaster =
                beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
    }
    else {
        this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
        beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
    }
}

可以看著這個(gè)方法很簡(jiǎn)單铆隘,如果容器中有APPLICATION_EVENT_MULTICASTER_BEAN_NAME定義的廣播器則用它,如果沒有則使用默認(rèn)的廣播器SimpleApplicationEventMulticaster并注冊(cè)到容器中(保證了廣播器的單例)南用。

接下來registerListeners就是為這個(gè)廣播器注冊(cè)所有的Listener膀钠。

protected void registerListeners() {
    // Register statically specified listeners first.
    for (ApplicationListener<?> listener : getApplicationListeners()) {
        getApplicationEventMulticaster().addApplicationListener(listener);
    }

    // Do not initialize FactoryBeans here: We need to leave all regular beans uninitialized to let post-processors apply to them!
    String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
    for (String listenerBeanName : listenerBeanNames) {
        getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
    }

    // Publish early application events now that we finally have a multicaster...
    Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
    this.earlyApplicationEvents = null;
    if (earlyEventsToProcess != null) {
        for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
            getApplicationEventMulticaster().multicastEvent(earlyEvent);
        }
    }
}

這里首先通過getApplicationListeners得到的監(jiān)聽器放進(jìn)廣播器中,當(dāng)然一開始沒有裹虫,其次通過getBeanNamesForTypebeanName放進(jìn)廣播器中肿嘲,這里就有了一個(gè)細(xì)節(jié)問題:為什么上面是將Listener實(shí)例對(duì)象放進(jìn)廣播器的,那下面為什么只放beanName呢筑公?為什么不通過getBean方法將實(shí)例對(duì)象創(chuàng)建出來放進(jìn)廣播器里去呢雳窟?同時(shí)這一段注釋又說明了什么?咋們先存下這些問題待會(huì)再說

Do not initialize FactoryBeans here: We need to leave all regular beans uninitialized to let post-processors apply to them!

EventListener 和 SmartInitializingSingleton

到這里看似這個(gè)原理分析就結(jié)束了匣屡,但是仔細(xì)一看還有一個(gè)問題封救,從容器初始化refresh流程可以看出拇涤,Listener 的初始化是先于大部分業(yè)務(wù)bean的(業(yè)務(wù)bean的初始化理應(yīng)在finishBeanFactoryInitialization階段),但我們實(shí)際運(yùn)用的時(shí)候常常會(huì)將Listener作為業(yè)務(wù)bean使用的誉结,這時(shí)候就觸犯了一個(gè)禁區(qū)鹅士,如果業(yè)務(wù)bean被錯(cuò)誤地提前初始化,會(huì)使得某些未得初始化的后置處理器postProcessors無法應(yīng)用(瘋狂暗示)搓彻,這樣創(chuàng)建出來的bean是不完整的如绸,也是很危險(xiǎn)的,會(huì)使得業(yè)務(wù)bean的部分功能缺失旭贬,例如@Autowire/@Transactional注解失效等。

@Override
public void refresh() throws BeansException, IllegalStateException {
// ... 省略萬行代碼
    // Check for listener beans and register them.
    registerListeners();

    // Instantiate all remaining (non-lazy-init) singletons.
    finishBeanFactoryInitialization(beanFactory);

    // Last step: publish corresponding event.
    finishRefresh();
// ... 省略萬行代碼
}

這也是在開發(fā)Spring應(yīng)用需要注意的問題搪泳,即要將實(shí)現(xiàn)特殊接口的bean和業(yè)務(wù)bean分開稀轨,不能耦合在一起。

為此Spring也考慮到了岸军,并為我們?cè)O(shè)計(jì)出了一個(gè)新的方式可以在業(yè)務(wù)bean中監(jiān)聽事件處理奋刽,那就是@EventListener注解,這也是實(shí)際運(yùn)用比較多的一種方式艰赞。

先來看一下使用@EventListener注解監(jiān)聽方式:

@Service
public class BusiEventListener {
    
    @Autowired
    private BusinessService businessService;
    
    @EventListener(classes = {MyEvent.class})
    public void listenEvent(MyEvent myEvent) {
        System.out.println(myEvent.getSource());
        businessService.div(2, 1);    
    }
    
}

其中可以通過classes屬性來指定需要監(jiān)聽的事件類型

那么這個(gè)注解又是如何監(jiān)聽的呢佣谐?它主要通過EventListenerMethodProcessor完成處理的,來研究一下這個(gè)處理器方妖,它是一個(gè)接口的實(shí)現(xiàn)類SmartInitializingSingleton狭魂,而這個(gè)接口是干嘛,又是如何工作的呢党觅?來看一下其注釋介紹

Callback interface triggered at the end of the singleton pre-instantiation phase during {@link BeanFactory} bootstrap. This interface can be implemented by singleton beans in order to perform some initialization after the regular singleton instantiation algorithm, avoiding side effects with accidental early initialization

This callback variant is somewhat similar to ContextRefreshedEvent but doesn't require an implementation of ApplicationListener

Invoked right at the end of the singleton pre-instantiation phase, with a guarantee that all regular singleton beans have been created already.

粗略用不標(biāo)準(zhǔn)的英文翻譯解釋就是:在保證所有單例bean創(chuàng)建完成后觸發(fā)雌澄,這個(gè)時(shí)機(jī)類似于容器刷新完成事件觸發(fā)的時(shí)機(jī),但不需要實(shí)現(xiàn)ApplicationListener監(jiān)聽器杯瞻。

說了那么多那具體是哪里呢镐牺?通過創(chuàng)建剩余單例bean過程進(jìn)入到這個(gè)方法:DefaultListableBeanFactory.preInstantiateSingletons 的最后一段

public void preInstantiateSingletons() throws BeansException {

    List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

    // Trigger initialization of all non-lazy singleton beans...
    for (String beanName : beanNames) {
        // ...此處省略萬行代碼
    }

    // Trigger post-initialization callback for all applicable beans...
    for (String beanName : beanNames) {
        Object singletonInstance = getSingleton(beanName);
        if (singletonInstance instanceof SmartInitializingSingleton) {
            final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
            if (System.getSecurityManager() != null) {
                AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                    smartSingleton.afterSingletonsInstantiated();
                    return null;
                }, getAccessControlContext());
            }
            else {
                smartSingleton.afterSingletonsInstantiated();
            }
        }
    }
}

此處可以看到兩頓遍歷,在第一次遍歷的時(shí)候就已經(jīng)初始化所有的單例bean了魁莉,接著又來一頓遍歷睬涧,這個(gè)時(shí)候就可以看到SmartInitializingSingleton接口的回調(diào)觸發(fā)時(shí)機(jī)了。

接著再回頭看這個(gè)類EventListenerMethodProcessor的回調(diào)旗唁,這個(gè)方法afterSingletonsInstantiated所做之事非常之多畦浓,很多不是本節(jié)所需要介紹的重點(diǎn),只需要看到最后有處理applicationListener并添加進(jìn)容器中的廣播器操作就可以了逆皮。

context.addApplicationListener(applicationListener);

最后再來回看之前存留的問題(其實(shí)已經(jīng)瘋狂暗示了),先來對(duì)比以下基于接口的方式和基于注解方式實(shí)現(xiàn)的Listener在注冊(cè)階段的不同宅粥,基于接口方式的Listener是在業(yè)務(wù)bean初始化之前注冊(cè)的,但為了避免業(yè)務(wù)bean被提前錯(cuò)誤地初始化电谣,注冊(cè)Listener時(shí)只是將beanName注冊(cè)好秽梅,延后到getListeners的時(shí)候才真正初始化了Listener抹蚀。這種巧妙的處理在極大多數(shù)情況都不會(huì)有問題 (自己腦補(bǔ)在極端情況的問題) ,但基于注解的方式恰好是在所有單例bean初始化完成時(shí)機(jī)進(jìn)行注冊(cè)工作的企垦,所注冊(cè)的直接就是Listener實(shí)例對(duì)象环壤,這種恰到好處的做法也許也是它被更推薦的原因吧~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市钞诡,隨后出現(xiàn)的幾起案子郑现,更是在濱河造成了極大的恐慌,老刑警劉巖荧降,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件接箫,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡朵诫,警方通過查閱死者的電腦和手機(jī)辛友,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來剪返,“玉大人废累,你說我怎么就攤上這事⊥衙ぃ” “怎么了邑滨?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)钱反。 經(jīng)常有香客問我掖看,道長(zhǎng),這世上最難降的妖魔是什么诈铛? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任乙各,我火速辦了婚禮,結(jié)果婚禮上幢竹,老公的妹妹穿的比我還像新娘耳峦。我一直安慰自己,他們只是感情好焕毫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布蹲坷。 她就那樣靜靜地躺著,像睡著了一般邑飒。 火紅的嫁衣襯著肌膚如雪循签。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天疙咸,我揣著相機(jī)與錄音县匠,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛乞旦,可吹牛的內(nèi)容都是我干的贼穆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼兰粉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼故痊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起玖姑,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤愕秫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后焰络,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體戴甩,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年闪彼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了等恐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡备蚓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出囱稽,到底是詐尸還是另有隱情郊尝,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布战惊,位于F島的核電站流昏,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏吞获。R本人自食惡果不足惜况凉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望各拷。 院中可真熱鬧刁绒,春花似錦、人聲如沸烤黍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽速蕊。三九已至嫂丙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間规哲,已是汗流浹背跟啤。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人隅肥。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓竿奏,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親武福。 傳聞我的和親對(duì)象是個(gè)殘疾皇子议双,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355