前言

容器是OOP的高級工具:

  以低耦合低侵入的方式打通從上到下的開發通道

    按部就班填充代碼邏輯實現業務功能,每層邏輯都可無縫替換

    OOP將業務程序分解成各個層次的對象,通過對象聯動完成業務

    無法很好地處理分散在各業務裏的通用系統需求

系統需求

  碼農才去關係的需求

    添加日誌信息:為每個方法添加統計時間

    添加系統權限校驗:針對某些方法進行限制

軟件工程中有個基本原則:關注點分離 Concern Separation

  不同的問題交給不同的部分去解決,每部分專注解決自己的問題

    Aspect Oriented Programming就是其中一種關注點分離的技術

    通用化功能的代碼實現即切面Aspect

    Aspect之於AOP,就相當於Class之於OOP,Bean之於Spring

 

框架搭建圖:

AP和modem之間的實現框架 高通_AOP

 

 

 


 

AOP的子民們

  切面Aspect:將橫切關注點邏輯進行模塊化封裝的實體對象

  通知Advice:好比是Class裏面的方法,還定義了織入邏輯的時機

  連接點Joinpoint,允許使用Advice的地方(對於SpringAOP來講默認只支持方法級別的Joinpoint)

  切入點Pointcut:定義一系列規則對Joinpoint進行篩選

  目標對象Target:符合Pointcut條件,要被織入橫切邏輯的對象

 

 

AOP是OOP裏的"寄生蟲"

  織入:將Aspect模塊化的橫切關注點集成到OOP裏

  織入器︰完成織入過程的執行者,如ajc

  Spring AOP則會使用一組類來作為織入器以完成最終的織入操作

 

Advice的種類

  BeforeAdvice:在JoinPoint前被執行的Advice(該方法並不能阻止JoinPoint的執行,除非拋出異常)

  AfterAdvice: 就好比try..catch..finaly裏面的finaly(無論Joinpoint能否正常執行,都會執行到)

  AfterReturningAdvice:在Joinpoint執行流程正常返回後被執行(如果Joinpoint拋出異常,則不會執行)

  AfterThrowingAdvice:在Joinpoint執行過程中拋出異常才會觸發(如果trycatch了,是不會到這的)

  AroundAdvice:在Joinpoin前和後都執行,最常用的Advice(常用)

  

 

 

 

 

AP和modem之間的實現框架 高通_動態代理_02

 

 

 

 


 Introduction——引入型Advice

  為目標類引入新接口,而不需要目標類做任何實現

  使得目標類在使用的過程中轉型成新接口對象,調用新接口的方法 

注:

  這種方式的操作,對於代碼的閲讀理解是比較複雜的

 

使用方法:

首先聲明好一個接口,一個實現類

public interface LittleUniverse {
    void burningup();
}
public class LittleUniverseImpl implements LittleUniverse {
    @Override
    public void burningup() {
        System.out.println("燃燒吧小宇宙");
    }
}

 

然後在Aspect中添加上如下代碼:

@DeclareParents(value = "com.imooc.controller..*",defaultImpl = com.imooc.introduction.impl.LittleUniverseImpl.class)
    public LittleUniverse littleUniverse;//這樣子我們便賦予了每個Controller都能變身為littleUniverse的權力

 

此時我們在容器中getBean獲取到了Controller之後,便可以將他強轉類型並調用接口的方法。

public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Entrance.class);
        HiController hiController = (HiController)applicationContext.getBean("hiController");
        ((LittleUniverse)hiController).burningup();
}

 


 

SpringAOP 的 實 現 原 理

 

在學習SpringAOP內部的實現原理之前,首先我們要了解代理模式

AP和modem之間的實現框架 高通_AOP_03

 

 

通常代理模式由(抽象主題)(被代理角色)(代理角色)三個模塊完成

  抽象主題:可以是抽象類或者抽象接口(用户只需要只需要面向抽象主題即可)

  代理類:實現或者繼承自抽象主題(實例化的時候使用代理類)(SpringAOP需要做的便是生成這麼個代理類,替換掉真正的實現類來提供服務)

  被代理類:實現或者繼承自抽象主題

 

代碼演示:

首先我們聲明一個(個人支付)接口及其接口實現類 (被代理對象)

public interface toCPayment {
    void pay();
}
public class toCPaymentImpl implements toCPayment {
    @Override
    public void pay() {
        System.out.println("以用户的名義進行支付");
    }
}

然後我們對於(個人支付)接口的實現類定義一個類(代理對象)

public class AlipayToC implements toCPayment {
    toCPayment toCPayment;
    public AlipayToC(toCPayment toCPayment){
        this.toCPayment = toCPayment;
    }
    @Override
    public void pay() {
        beforPay();
        toCPayment.pay();
        afterPay();
    }
    private void afterPay() {
        System.out.println("支付給慕課網");
    }
    private void beforPay() {
        System.out.println("從招行取款");
    }
}

 

這時候我們去調用支付方法

public class ProxyDemo {
    public static void main(String[] args) {
       toCPayment toCProxy = new AlipayToC(new toCPaymentImpl());
       toCProxy.pay();
    }
}

執行結果:

  從招行取款
  以用户的名義進行支付
  支付給慕課網

 

-----------------------

上面這種代理模式是靜態的代理模式,如果我們要添加多一個(企業支付)接口,即使支付前後的方法一樣,只是裏頭的pay()方法改變,那麼我們又要重新按照剛剛的流程走一步。這樣也是相對比較麻煩的

這種模式的思路是沒有問題的,只是相關的實現方式需要改進。

 

尋求改進:

在改進前我們重新回顧一下ClassLoader(類加載器) 

  溯源ClassLoader

    通過帶有包名的類來獲取對應class文件的二進制字節流

    根據讀取的字節流,將代表的靜態存儲結構轉化為(方法區的)運行時數據結構

    生成一個代表該類的Class對象,作為方法區該類的數據訪問入口

 

在複習完ClassLoader之後呢,我們可以根據ClassLoader對切入點進行改進:

  根據一定規則去改動或者生成新的字節流,將切面邏輯織入其中(既然字節流能定義類的行為,我們將切面邏輯織入其中,使其動態生成天生織入了切面邏輯的類。)

     行之有效的方案就是取代被代理類的動態代理機制

      根據接口或者目標類,計算出代理類的字節碼並加載到JVM中去

      

SpringAOP的實現原理之JDK動態代理

  JDK1.3之後,java就為我們引入了JDK的動態代理機制

    靜態代理,需要在編譯前實現,編譯完成後,代理類是一個實際的Class文件

    JDK動態代理,是在運行時動態生成的,編譯完成後並沒有實際的Class文件,而是在運行時動態生成類的字節碼,並加載到JVM中(要求【被代理的類】必須實現接口)(並不要求【代理對象】去實現接口,所以可以複用代理對象的邏輯)

    (採用JDK動態代理相比靜態代理,邏輯可複用)

其中涉及兩個比較重要的類:

  java.lang.reflect.InvocationHandler:並非代理類,只是用來統一管理橫切邏輯的Aspect。

  java.lang.reflect.Proxy:真正的代理類是由Proxy創建出來的。該類最關鍵的方法就是newProxyInstance方法,該方法最終會返回創建好的動態代理實例。

代碼演示:

首先創建管理橫切邏輯的,將被包裝類通過構造函數傳遞進來。

public class AlipayInvocationHandler implements InvocationHandler {

    private Object targetObject;
    public AlipayInvocationHandler(Object targetObject){
        this.targetObject = targetObject;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        beforPay();
        Object result = method.invoke(targetObject,args);
        afterPay();
        return result;
    }
    private void afterPay() {
        System.out.println("支付給慕課網");
    }
    private void beforPay() {
        System.out.println("從招行取款");
    }
}

 

創建工具類,去創建出真正的Proxy

public class jdkDynamicProxyUtil {

    public static <T>T newProxyInstance(T targetObject, InvocationHandler handler){
        ClassLoader classLoader = targetObject.getClass().getClassLoader();
        Class<?>[] interfaces = targetObject.getClass().getInterfaces();
        return (T)Proxy.newProxyInstance(classLoader,interfaces,handler);
    }


}

最後main方法調用

public class ProxyDemo {
    public static void main(String[] args) {
   
        toCPayment toCPayment = new toCPaymentImpl();//首先創建被代理的類
        InvocationHandler handler = new AlipayInvocationHandler(toCPayment);
        toCPayment toCProxy = jdkDynamicProxyUtil.newProxyInstance(toCPayment,handler);
        toCProxy.pay();


        ToBPayment toBPayment = new ToBPaymentImpl();
        InvocationHandler handlerToB = new AlipayInvocationHandler(toBPayment);
        ToBPayment toBProxy =jdkDynamicProxyUtil.newProxyInstance(toBPayment,handlerToB);
        toBProxy.pay();
    }
}

 

SpringAOP的實現原理之CGLIB動態代理

cgLib代碼生成庫:Code Generation Library

  不要求被代理類實現接口

  內部主要封裝了ASM java字節碼操控框架(轉化字節碼產生新類)

  動態生成子類以覆蓋非final的方法,綁定鈎子回調自定義攔截器  

 

CGLib庫中,和動態代理最相關的就是net.sf.cglib.proxy.Proxy類。

  (1)在net.sf.cglib.proxy包下有個類叫做Callback,這個類就是一個比較核心的底層接口了,看名字就知道該類是用來提供回調實現了,該類只是一個空接口(標記),主要幹活的是MethodInterceptor類

  (2)MethodInterceptor類中有一個intercept(Object var1, Method var2, Object[] var3, MethodProxy var4)方法,方法的前三個參數跟JDK動態代理的invork方法一樣

      1.動態代理對象 2.被代理的方法實例 3.被代理方法實例裏需要的參數數組。第四個參數則是動態代理Method的對象

  (3)有了上面這些類,還需要個核心類來創建出最終代理對象(Enhancer),該類的核心方法create(),既可以用動態的方式調用,也可以通過靜態(static)的方式調用。

 

 

代碼演示:

首先我們創建被代理對象類:

public class CommonPayment {
    public void pay(){
        System.out.println("個人或者公司都可以走這個支付通道");
    }
}

然後創建切面Aspect類

public class AlipayMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        beforPay();
        Object result = methodProxy.invokeSuper(o,args);
        afterPay();
        return result;
    }
    private void afterPay() {
        System.out.println("支付給慕課網");
    }
    private void beforPay() {
        System.out.println("從招行取款");
    }
}

然後針對cglib創建一個工具類

public class CglibUtil {
    public static <T>T createProxy(T targetObject, MethodInterceptor methodInterceptor){
        return (T)Enhancer.create(targetObject.getClass(),methodInterceptor);
    }
}

最後main方法執行

public static void main(String[] args) {
//CGLib動態代理
        CommonPayment commonPayment = new CommonPayment();
        MethodInterceptor methodInterceptor = new AlipayMethodInterceptor();
        CommonPayment commonPaymentProxy = CglibUtil.createProxy(commonPayment,methodInterceptor);
        commonPaymentProxy.pay();
     //哪怕是切換了代理對象也不需要對應的去創建接口
        toCPayment toCPayment = new toCPaymentImpl();
        toCPayment toCProxy = CglibUtil.createProxy(toCPayment,methodInterceptor);
        toCProxy.pay();

    }

 

 

總結:

實現機制:

  JDK動態代理:基於反射機制實現,要求業務類必須實現接口

  CGLIB:基於ASM機制實現,生成業務類的子類作為代理類

優勢:

  JDK動態代理:

    JDK原生,在JVM裏運行較為可靠

    平滑支持JDK版本的升級

  CGLIB:

    被代理對象無需實現接口,能實現代理類的無侵入

    

在SpringAOP中的底層機制中靠的就是CGLIB和JDK動態代理共存

默認策略:Bean實現了接口則使用JDK,否則使用CGLIB


 AspectJ框架

AspectJ框架是Eclipse託管給apache基金會的一個開源項目,提供了完整的AOP解決方案。AspectJ是AOP的java實現版本,定義了AOP的語法,可以説是對java的擴展。

 

AspectJ提供了兩套強大的機制:

  1.定義切面語法以及切面語法的解析機制

  2.提供了強大的織入工具

 

Spring-AOP與AspectJ框架 所支持的織入點:

AP和modem之間的實現框架 高通_代理類_04

 

 

如圖所示,我們可以看出SpringAOP只支持方法上的織入點,而AspectJ可以説是支持了所有的連接點(所以我們可以稱AspectJ是一套AOP的完整解決方案)

既然AspectJ這麼強大,為什麼Spring沒有完整複用呢。因為在方法上的織入已經可以滿足大多數的業務,而且在AspectJ的學習與使用上也遠比Spring複雜。

 


 

SpringAOP的脈絡分析圖:

AP和modem之間的實現框架 高通_代理類_05

 

日拱一卒無有盡,功不唐捐終入海