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

[TOC]

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

然而設(shè)計(jì)模式本身是一種抽象的設(shè)計(jì)概念,并沒(méi)有真正的代碼模板可以套用,至少網(wǎng)上能看到或書(shū)上翻到的代碼模板都是不適用的,生搬硬套是很多初學(xué)者的誤區(qū),無(wú)法理解設(shè)計(jì)模式的精髓。它真正的使用方式還必須結(jié)合實(shí)際的代碼設(shè)計(jì)場(chǎng)景靈活搭配,所以如果非要說(shuō)有什么模板,那本文推薦的例子會(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?jiàn),所以這套組合也許會(huì)是常用的打法,眾所周知,策略模式能將各個(gè)子業(yè)務(wù)邏輯拆到一個(gè)個(gè)類里面實(shí)現(xiàn),但是對(duì)調(diào)用者來(lái)說(shuō)它必須得知道每一種類型對(duì)應(yīng)的實(shí)現(xiàn)類類名是什么,然而工廠模式又恰好能夠屏蔽這個(gè)細(xì)節(jié),讓調(diào)用者可以優(yōu)雅簡(jiǎn)化一大層的邏輯判斷。

而說(shuō)到工廠模式,你可能會(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)目無(wú)論大小都已經(jīng)脫離不了Spring框架支撐了,把它作為必選套餐沒(méi)什么毛病,而Spring天生就有強(qiáng)大的容器可以作為工廠的存在,已經(jīng)包含了各種工廠的實(shí)現(xiàn)并支持混合運(yùn)用,因此把它作為工廠模式的最佳實(shí)現(xiàn)我覺(jué)得沒(méi)什么毛病。

因此接下來(lái)就說(shuō)一下如何通過(guò)Spring工廠+策略模式來(lái)優(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();

}

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

@Component
public class Busi01Strategy implements BusiStrategy {

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

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

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

最后有個(gè)重要的點(diǎn),就是這些策略產(chǎn)品必須注冊(cè)到Spring容器上,無(wú)論用什么樣的方式均可,比如這里用最常見(jiàn)的注解方式注冊(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,沒(méi)有業(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ì)講述。注意這段初始化邏輯不能寫(xiě)在構(gòu)造函數(shù)里,因?yàn)樵跇?gòu)造函數(shù)階段屬性還沒(méi)賦值,@Autowired未生效,會(huì)造成空指針異常。

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

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

最后我們?cè)賮?lái)看看,通過(guò)這樣改造后,核心業(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ù)編碼中可能都有過(guò)想改源碼的沖動(dòng),可是要么這源碼是編譯好的.class文件不可修改,要么就是公共代碼被其他模塊大量引用不敢修改,那有沒(méi)有辦法優(yōu)雅的改源碼——即能滿足當(dāng)前功能的擴(kuò)展需求,又不去改動(dòng)舊有代碼而兼容呢?

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

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

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

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

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

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

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

/**
 *  公共Vo對(duì)象之,分頁(yè)數(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;
        /** 每頁(yè)條數(shù) **/
        private Integer pageSize;
        /** 當(dāng)前頁(yè)的條數(shù) **/
        private Integer length;
        /** 總記錄數(shù) **/
        private Long total;
        /** 總頁(yè)數(shù) **/
        private Integer totalPage;
        
        private Boolean isFirst;
        
        private Boolean hasNext;
    }
    
}

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

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

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

/**
 * Mybatis-PageHelper 的 PageInfo 和 JPA 的 Page 適配器
 *  同時(shí)是裝飾類  解決elasticsearch不分頁(yè)的痛
 *
 * @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分頁(yè)結(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框架之所以能包容萬(wàn)象的魅力所在。

模板方法 抽取公共邏輯

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

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

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

一般來(lái)說(shuō)幾個(gè)參數(shù)就直接定義,多個(gè)固定參數(shù)就封裝實(shí)體,但參數(shù)可多可少,可有可無(wú),這時(shí)候就免不了考慮構(gòu)造器模式了。

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

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

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

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

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)過(guò)代理后的對(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í)行后!!!");
        return invoke;
    }
}

有了這個(gè)增強(qiáng)器后,接下來(lái)就是把被代理對(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();

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

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

// 創(chuàng)建動(dòng)態(tài)代理類的Class對(duì)象
Class<?> proxyClazz = Proxy.getProxyClass(classLoader, interfaces);
// 通過(guò)反射技術(shù)獲得動(dòng)態(tài)代理類的構(gòu)造函數(shù)
Constructor<?> constructor = proxyClazz.getConstructor(new Class[]{InvocationHandler.class});
// 通過(guò)構(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è)方法重寫(xiě)并統(tǒng)一調(diào)用增強(qiáng)器中定義的方法,最后通過(guò)classloader加載進(jìn)來(lái)并使用。

所以其實(shí)動(dòng)態(tài)代理最終也是變成靜態(tài)代理,再本質(zhì)點(diǎn)就是包裝,只不過(guò)開(kāi)發(fā)者懶,或者說(shuō)不想寫(xiě)那么多重復(fù)代碼,于是便讓JVM幫忙寫(xiě),就演變成看似神奇又很牛逼的技術(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)議不是這里需要討論的問(wèn)題,大家只要知道它不需要依賴接口實(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. 重寫(xiě)被代理對(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é)束!!!");
        return result;
    }
}

這段代碼可以成為模板代碼,甚至可以改造成單例后做成工具類,為每一個(gè)需要代理的對(duì)象做一層包裝。cglib代理的原理要想深入理解起來(lái)確實(shí)有點(diǎn)復(fù)雜,但其實(shí)可以簡(jiǎn)單理解成就是生成一個(gè)新的類繼承了被代理對(duì)象,重寫(xiě)了所有被代理對(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代理。

先來(lái)看下用法,這里介紹的是基于注解的用法,因此除了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é)束通知,無(wú)論正常還是異常都會(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

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

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

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

SpringAOP.png

@EnableAspectJAutoProxy注解上標(biāo)記了一個(gè)@Import(AspectJAutoProxyRegistrar.class)AspectJAutoProxyRegistrar這個(gè)類是ImportBeanDefinitionRegistrar的實(shí)現(xiàn)類,在我另外一個(gè)專題:Spring注解驅(qū)動(dòng)開(kāi)發(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)開(kāi)發(fā)一節(jié)會(huì)闡述,它是一個(gè)經(jīng)典的后置處理器,但它處理的階段是在Springbean的創(chuàng)建階段

那具體是在那個(gè)地方實(shí)現(xiàn)的呢?可以通過(guò)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;
}

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

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

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

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

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

如果繼續(xù)跟進(jìn),可以看到它會(huì)遍歷所有已注冊(cè)的bean,拿到Class類型進(jìn)入判斷:this.advisorFactory.isAspect(beanType),這里就可以篩選出哪些是切面注解的類了。接著對(duì)于切面類又繼續(xù)進(jìn)入this.advisorFactory.getAdvisors(factory)在里面真正能把通知方法篩選出來(lái)的是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)過(guò)濾掉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"

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

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)建過(guò)程,只不過(guò)在初始化階段會(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ò)第一步,來(lái)看第二步操作,最關(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

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

責(zé)任鏈模式

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

接著上節(jié),繼續(xù)分析DynamicAdvisedInterceptorintercept方法,首先通過(guò)下面語(yǔ)句獲得了一條增強(qiáng)器鏈,通過(guò)上節(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}

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

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)然,由于一開(kāi)始索引值為-1,所以不會(huì)執(zhí)行。

接下來(lái)無(wú)論那個(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);
    }
}

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

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

索引值遞增1,取第一個(gè)增強(qiáng)器是AspectJAfterThrowingAdvice來(lái)看看它的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ù)去了嗎?

所以接下來(lái)要做的事情也一樣,判斷當(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來(lái)看看它的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ù)去了嗎?

所以接下來(lái)要做的事情也一樣,判斷當(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來(lái)看看它的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ù)去了嗎?

所以接下來(lái)要做的事情也一樣,判斷當(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來(lái)看看它的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ù)去了嗎?

所以接下來(lái)要做的事情也一樣,判斷當(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ù)沒(méi)有異常拋出,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,在神奇之余也困擾著大眾開(kāi)發(fā)者,例如最經(jīng)典的事務(wù)注解@Transactional,用著它卻抱怨事務(wù)不生效,雖然歸總起來(lái)原因有很多,但實(shí)際上大多數(shù)情況都是一個(gè)大原則沒(méi)把握住,那就是:代理必裝飾

觀察者模式

觀察者模式這詞眼確實(shí)挺拗口的,它更像是一種事件委派機(jī)制或者說(shuō)是一種通知響應(yīng)機(jī)制,它的主要目的就是為了解耦并方便擴(kuò)展,在前端開(kāi)發(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觀察者

// 這里來(lái)研究觀察者的核心源碼
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è)問(wèn)題:

  1. 為了保證線程安全而使用了重量級(jí)的synchronized + Vector 方式(先不說(shuō)其本身的優(yōu)化)。
  2. Observer的回調(diào)中直接將Observable作為參數(shù)傳遞本身不夠優(yōu)雅,在回調(diào)中又開(kāi)放了adddelete方法。
  3. 要手動(dòng)區(qū)分不同類型的事件,雖然可以通過(guò)創(chuàng)建多個(gè)Observable解決,但缺乏語(yǔ)義,不夠優(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),先來(lái)看看基于Spring的觀察者應(yīng)如何開(kāi)發(fā):

先來(lái)看下最基本的事件響應(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)呢?

首先先來(lái)自定義個(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ā)器可以通過(guò)Aware接口注入,也可以通過(guò)@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)聽(tīng)器的時(shí)候只需要新增一個(gè)類,并將其注冊(cè)到Spring容器中即可,不需要改變?cè)械臉I(yè)務(wù)代碼。

那么Spring的事件派發(fā)和響應(yīng)又是什么個(gè)原理呢?換句話說(shuō)Spring如何利用自己強(qiáng)大的容器優(yōu)勢(shì)實(shí)現(xiàn)這種觀察者模式的呢?接下來(lái)我們進(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);
        }
    }
}

先來(lái)關(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)聽(tīng)器:

getApplicationListeners(event, type)

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

getTaskExecutor()

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

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

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

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

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

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

函數(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定義的廣播器則用它,如果沒(méi)有則使用默認(rèn)的廣播器SimpleApplicationEventMulticaster并注冊(cè)到容器中(保證了廣播器的單例)。

接下來(lái)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);
        }
    }
}

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

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è)問(wèn)題,從容器初始化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無(wú)法應(yīng)用(瘋狂暗示),這樣創(chuàng)建出來(lái)的bean是不完整的,也是很危險(xiǎn)的,會(huì)使得業(yè)務(wù)bean的部分功能缺失,例如@Autowire/@Transactional注解失效等。

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

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

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

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

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

先來(lái)看一下使用@EventListener注解監(jiān)聽(tīng)方式:

@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);    
    }
    
}

其中可以通過(guò)classes屬性來(lái)指定需要監(jiān)聽(tīng)的事件類型

那么這個(gè)注解又是如何監(jiān)聽(tīng)的呢?它主要通過(guò)EventListenerMethodProcessor完成處理的,來(lái)研究一下這個(gè)處理器,它是一個(gè)接口的實(shí)現(xiàn)類SmartInitializingSingleton,而這個(gè)接口是干嘛,又是如何工作的呢?來(lái)看一下其注釋介紹

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)聽(tīng)器。

說(shuō)了那么多那具體是哪里呢?通過(guò)創(chuàng)建剩余單例bean過(guò)程進(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) {
        // ...此處省略萬(wàn)行代碼
    }

    // 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了,接著又來(lái)一頓遍歷,這個(gè)時(shí)候就可以看到SmartInitializingSingleton接口的回調(diào)觸發(fā)時(shí)機(jī)了。

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

context.addApplicationListener(applicationListener);

最后再來(lái)回看之前存留的問(wèn)題(其實(shí)已經(jīng)瘋狂暗示了),先來(lái)對(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ì)有問(wèn)題 (自己腦補(bǔ)在極端情況的問(wèn)題) ,但基于注解的方式恰好是在所有單例bean初始化完成時(shí)機(jī)進(jìn)行注冊(cè)工作的,所注冊(cè)的直接就是Listener實(shí)例對(duì)象,這種恰到好處的做法也許也是它被更推薦的原因吧~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。