從代理機制到Spring AOP

這篇文章準備從Java的代理機制講到Spring的AOP。php

1.代理模式

代理模式是很常見的一種設計模式,代理一詞拆開來看就是代爲受理,那顯然是要涉及到請求被代理的委託方,提供代理的代理方,以及想要經過代理來實際聯繫委託方的客戶三個角色。舉個生活中很常見的例子,各路的明星都會有個本身的經紀人來替本身打點各類各樣的事情,這種場景下,明星自己是委託方,經紀人是代理方,明星把本身安排演出、出席見面會的時間安排權利委託給經紀人,這樣當各個商家做爲客戶想要請明星來代言時,就只能經過經紀人來進行。這樣明星自己不用暴露身份,而經濟人也能夠在溝通中告知商家明星出席活動時要吃什麼飯,作什麼車的一些要求,省去了明星本身操心這些雞毛蒜皮小事兒。另外一方面,當經紀人也能夠給多個明星提供服務,這樣商家只接觸一個經紀人,能夠聯繫到不一樣的明星,找個適合本身公司的人選。
經過上面的例子,代理模式的優勢就顯而易見了:java

優勢一:能夠隱藏委託類的實現;程序員

優勢二:能夠實現客戶與委託類間的解耦,在不修改委託類代碼的狀況下可以作一些額外的處理。spring

2.字節碼與代理模式

Java程序員都應該知道,Java經過Java編譯器將.java源文件編譯成.class字節碼文件,這種.class文件是二進制文件,內容是隻有JVM虛擬機可以識別的機器碼,JVM虛擬機讀取字節碼文件,取出二進制數據,加載到內存中,解析.class文件內的信息,生成對應的Class對象,進而使Class對象建立類的具體實例來進行調用實現具體的功能。express

上圖說明了Java加載字節碼的流程,可是Java的強大在於不只僅能夠加載在編譯期生成好的字節碼,還能夠在運行期系統中,遵循Java編譯系統組織.class文件的格式和結構,生成相應的二進制數據,而後再把這個二進制數據加載轉換成對應的類,這樣,就完成了在代碼中,動態建立一個類的能力了,以下圖流程。編程

下面舉一個動態生成類的實例,經過Javassist實現,Javassist是一個開源的分析、編輯和建立Java字節碼的類庫,咱們可使用Javasisst工具在運行時動態建立字節碼並加載類,以下代碼:設計模式

/** * Created by zhoujunfu on 2018/9/6. */
public class JavassistDemo {
    
    public static void main(String[] args) {
        makeNewClass();
    }
    
    public static Class<?> makeNewClass() {
        try {
            // 獲取ClassPool
            ClassPool pool = ClassPool.getDefault();
            // 建立Student類
            CtClass ctClass = pool.makeClass("com.fufu.aop.Student");
            // 建立Student類成員變量name
            CtField name = new CtField(pool.get("java.lang.String"), "name", ctClass);
            // 設置name爲私有
            name.setModifiers(Modifier.PRIVATE);
            // 將name寫入class
            ctClass.addField(name, CtField.Initializer.constant("")); //寫入class文件
            //增長set方法,名字爲"setName"
            ctClass.addMethod(CtNewMethod.setter("setName", name));
            //增長get方法,名字爲getname
            ctClass.addMethod(CtNewMethod.getter("getName", name));
            // 添加無參的構造體
            CtConstructor cons = new CtConstructor(new CtClass[] {}, ctClass);
            cons.setBody("{name = \"Brant\";}"); //至關於public Sclass(){this.name = "brant";}
            ctClass.addConstructor(cons);
            // 添加有參的構造體
            cons = new CtConstructor(new CtClass[] {pool.get("java.lang.String")}, ctClass);
            cons.setBody("{$0.name = $1;}");  //第一個傳入的形參$1,第二個傳入的形參$2,至關於public Sclass(String s){this.name = s;}
            ctClass.addConstructor(cons);

            //反射調用新建立的類
            Class<?> aClass =  ctClass .toClass();
            Object student = aClass.newInstance();
            Method getter = null;
            getter = student.getClass().getMethod("getName");
            System.out.println(getter.invoke(student));

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
複製代碼

介紹靜態和動態加載字節碼的兩種方式,是爲了引出下面關於兩種代理方式的介紹,代理機制經過代理類建立的時間不一樣分爲了靜態代理和動態代理:
靜態代理:代理類在編譯階段生成,程序運行前就已經存在,那麼這種代理方式被成爲靜態代理,這種狀況下的代理類一般都是咱們在Java代碼中定義的。
動態代理:代理類在程序運行時建立,也就是說,這種狀況下,代理類並非在Java代碼中定義的,而是在運行時根據咱們在Java代碼中的「指示」動態生成的。bash

目前,靜態代理主要有AspectJ靜態代理、JDK靜態代理技術、而動態代理有JDK動態代理、Cglib動態代理技術,而Spring Aop是整合使用了JDK動態代理和Cglib動態代理兩種技術,下面咱們結合實例一步一步介紹全部的概念。app

3.靜態代理

3.1 AspectJ靜態代理

對於AspectJ,咱們只會進行簡單的瞭解,爲後續理解打下基礎,如今只須要知道下面這一句定義:框架

AspectJ是一個Java實現的面向切面的框架,它擴展了Java語言。AspectJ有自定義的語法,因此它有一個專門的編譯器用來生成遵照Java字節編碼規範的Class文件。

注意上面定義中的「專門的編譯器」這個描述,能夠看出AspectJ是典型的靜態代理技術,由於是在編譯時期就生成了代理類,而使用AspectJ也確定須要指定特定的編譯器,下面咱們用AspectJ來實現上面的明星和經紀人的模型。

首先在maven工程中引入AspectJ依賴:

<dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.8.9</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjtools</artifactId>
      <version>1.8.9</version>
    </dependency>
複製代碼

而後在idea中將javac編譯器改成acj編譯器來支持AspectJ語法:

將明星的表演抽象成一個ShowService接口,包括了唱歌、跳舞的功能

public interface ShowService {
    // 歌唱表演
    void sing(String songName);
    // 舞蹈表演
    void dance();
}

複製代碼

明星類實現了ShowService接口:

package com.fufu.aop;

/**
 * Created by zhoujunfu on 2018/9/6.
 * 明星類
 */
public class Star implements ShowService{
    private String name;

    @Override
    public void sing(String songName) {
        System.out.println(this.name + " sing a song: " + songName);
    }

    @Override
    public void dance() {
        System.out.println(this.name + "dance");
    }

    public Star(String name) {
        this.name = name;
    }

    public Star() {
    }

    public static void main(String[] args) {
        Star star = new Star("Eminem");
        star.sing("Mockingbird");
    }
}
複製代碼

用AspectJ語法實現一個代理AgentAspectJ:

package com.fufu.aop;

/**
 * Created by zhoujunfu on 2018/9/7.
 */
public aspect AgentAspectJ {

    /**
     * 定義切點
     */
    pointcut sleepPointCut():call(* Star.sing(..));

    /**
     * 定義切點
     */
    pointcut eatPointCut():call(* Star.eat(..));

    /**
     * 定義前置通知
     *
     * before(參數):鏈接點函數{
     *     函數體
     * }
     */
    before():sleepPointCut(){
        getMoney();
    }

    /**
     * 定義後置通知
     * after(參數):鏈接點函數{
     *     函數體
     * }
     */
    after():sleepPointCut(){
        writeReceipt();
    }

    private void getMoney() {
        System.out.println("get money");
    }

    private void writeReceipt() {
        System.out.println("write receipt");
    }
}
複製代碼

建立一個Star並運行方法:

public static void main(String[] args) {
        Star star = new Star("Eminem");
        star.sing("Mockingbird");
    }
複製代碼

輸出:

get money
Eminem sing a song: Mockingbird
write receipt
複製代碼

能夠看到Star的sing()方法先後輸出了咱們在AgentAspectJ中定義的前置通知和後置通知,因此是AspectJ在編譯期間,根據AgentAspectJ代碼中定義的代碼,生成了加強的Star類,而咱們實際調用時,就會實現代理類的功能。具體的AspectJ語法咱們不深究,只須要知道pointcut是定義代理要代理的切入點,這裏是定義了兩個pointcut,分別是Star類的sing()方法和dance()方法。而before()和after()分別能夠定義具體在切入點先後須要的額外操做。

總結一下,AspctJ就是用特定的編譯器和語法,對類實現編譯期加強,實現靜態代理技術,下面咱們看JDK靜態代理。

3.2 JDK靜態代理

一般狀況下, JDK靜態代理更多的是一種設計模式,JDK靜態代理的代理類和委託類會實現同一接口或是派生自相同的父類,代理模式的基本類圖入下:

咱們接着經過把上面的明星和經紀人的例子寫成代碼來實現一個JDK靜態代理模式。

經紀人類也實現了ShowService接口,持有了一個明星對象來提供真正的表演,並在各項表演的先後加入了經紀人須要處理的事情,如收錢、開發票等:

package com.fufu.aop;

/** * Created by zhoujunfu on 2018/9/6. * 經紀人 */
public class Agent implements ShowService{

    private Star star;

    public Agent(Star star) {
        this.star = star;
    }

    private void getMoney() {
        System.out.println("get money");
    }

    private void writeReceipt() {
        System.out.println("write receipt");
    }
    @Override
    public void sing(String songName) {
        // 唱歌開始前收錢
        getMoney();
        // 明星開始唱歌
        star.sing(songName);
        // 唱歌結束後開發票
        writeReceipt();
    }

    @Override
    public void dance() {
        // 跳舞開始前收錢
        getMoney();
        // 明星開始跳舞
        star.dance();
        // 跳舞結束後開發票
        writeReceipt();
    }
}
複製代碼

經過經紀人來請明星表演:

public static void main(String[] args) {
        Agent agent = new Agent(new Star("Eminem"));
        agent.sing("Mockingbird");
 }
複製代碼

輸出:

get money
Eminem sing a song: Mockingbird
write receipt
複製代碼

以上就是一個典型的靜態代理的實例,很簡單可是也能說明問題,咱們來看看靜態代理的優缺點:

優勢: 業務類能夠只關注自身邏輯,能夠重用,經過代理類來增長通用的邏輯處理。

缺點: 1.代理對象的一個接口只服務於一種類型的對象,若是要代理的類不少,勢必要爲每個類都進行代理,靜態代理在程序規模稍大時就沒法勝任了。

2.若是接口增長一個方法,除了全部實現類須要實現這個方法外,全部代理類也須要實現此方法。增長了代碼維護的複雜度

另外,若是要按照上述的方法使用代理模式,那麼真實角色(委託類)必須是事先已經存在的,並將其做爲代理對象的內部屬性。可是實際使用時,一個真實角色必須對應一個代理角色,若是大量使用會致使類的急劇膨脹;此外,若是事先並不知道真實角色(委託類),該如何使用代理呢?這些問題能夠經過Java的動態代理類來解決。

4.動態代理

動態代理類的源碼是在程序運行期間由JVM根據反射等機制動態的生成,因此不存在代理類的字節碼文件。代理類和委託類的關係是在程序運行時肯定。

4.1 動態代理思路

想弄明白動態代理類實現的思路是什麼,咱們還需用從靜態代理的存在的問題入手,由於畢竟動態代理是爲了解決靜態代理存在問題而出現的,回過頭來看靜態代理的問題:

  1. 類膨脹: 每一個代理類都是一個須要程序員編寫的具體類,不現實。
  2. 方法級代理:代理類和實現類都實現相同接口,致使代理類每一個方法都須要進行代理,你有幾個方法我就要有幾個,編碼複雜,沒法維護。

動態代理如何解決:

  1. 第一個問題很容易回答,相似使用Javasisst的例子,在代碼中動態的建立代理類的字節碼,而後獲取到代理類對象。
  2. 第二問題就要引出InvocationHandler了,爲了構造出具備通用性和簡單性的代理類,能夠將全部的觸發真實角色動做交給一個觸發的管理器,讓這個管理器統一地管理觸發。這種管理器就是InvocationHandler。靜態代理中,代理類無非是在先後加入特定邏輯後,調用對應的實現類的方法,sleep()對應sleep(),run()對應run(),而在Java中,方法Method也是一個對象,因此,動態代理類能夠將對本身的全部調用做爲Method對象都交給InvocationHandler處理,InvocationHandler根據是什麼Method調用具體實現類的不一樣方法,InvocationHandler負責增長代理邏輯和調用具體的實現類的方法。

也就是說,動態代理類仍是和實現類實現相同的接口,可是動態代理類是根據實現類實現的接口動態生成,不須要使用者關心,另外動態代理類的全部方法調用,統一交給InvocationHandler,不用處理實現類每一個接口的每一個方法。

在這種模式之中:代理Proxy和RealSubject應該實現相同的功能,這一點至關重要。(我這裏說的功能,能夠理解爲某個類的public方法)

在面向對象的編程之中,若是咱們想要約定Proxy和RealSubject能夠實現相同的功能,有兩種方式:

a.一個比較直觀的方式,就是定義一個功能接口,而後讓Proxy 和RealSubject來實現這個接口。
b.還有比較隱晦的方式,就是經過繼承。由於若是Proxy繼承自RealSubject,這樣Proxy則擁有了RealSubject的功能,Proxy還能夠經過重寫RealSubject中的方法,來實現多態。

其中JDK中提供的建立動態代理的機制,是以a這種思路設計的,而cglib則是以b思路設計的。

4.1 JDK動態代理(經過接口)

先來看一個具體的例子,仍是以上邊明星和經紀人的模型爲例,這樣方便對比理解:

將明星的表演抽象成一個ShowService接口,包括了唱歌、跳舞的功能:

package com.fufu.aop;

/** * Created by zhoujunfu on 2018/9/6. */
public interface ShowService {
    // 歌唱表演
    void sing(String songName);
    // 舞蹈表演
    void dance();
}
複製代碼

明星類實現了ShowService接口:

package com.fufu.aop;

/** * Created by zhoujunfu on 2018/9/6. * 明星類 */
public class Star implements ShowService{
    private String name;

    @Override
    public void sing(String songName) {
        System.out.println(this.name + " sing a song: " + songName);
    }

    @Override
    public void dance() {
        System.out.println(this.name + "dance");
    }

    public Star(String name) {
        this.name = name;
    }

    public Star() {
    }
}
複製代碼

實現一個代理類的請求處理器,處理對具體類的全部方法的調用:

package com.fufu.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/** * Created by zhoujunfu on 2018/9/7. */
public class InvocationHandlerImpl implements InvocationHandler {

    ShowService target;

    public InvocationHandlerImpl(ShowService target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 表演開始前收錢
        getMoney();
        // 明星開始唱歌
        Object invoke = method.invoke(target, args);
        // 表演結束後開發票
        writeReceipt();

        return invoke;
    }

    private void getMoney() {
        System.out.println("get money");
    }

    private void writeReceipt() {
        System.out.println("write receipt");
    }
}
複製代碼

經過JDK動態代理機制實現一個動態代理:

package com.fufu.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * Created by zhoujunfu on 2018/9/7.
 */
public class JDKProxyDemo {

    public static void main(String[] args) {
        // 1.建立被代理的具體類
        Star star = new Star("Eminem");
        // 2.獲取對應的ClassLoader
        ClassLoader classLoader = star.getClass().getClassLoader();
        // 3.獲取被代理對象實現的全部接口
        Class[] interfaces = star.getClass().getInterfaces();
        // 4.設置請求處理器,處理全部方法調用
        InvocationHandler invocationHandler = new InvocationHandlerImpl(star);

        /**
         * 5.根據上面提供的信息,建立代理對象 在這個過程當中,
         *   a.JDK會經過根據傳入的參數信息動態地在內存中建立和.class文件等同的字節碼
         *   b.而後根據相應的字節碼轉換成對應的class,
         *   c.而後調用newInstance()建立實例
         */
        Object o = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        ShowService showService = (ShowService)o;
        showService.sing("Mockingbird");
    }
}
複製代碼

咱們從代理的建立入手,看看JDK的動態代理都作了什麼:

Object o = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
複製代碼
  1. Proxy.newProxyInstance()獲取Star類的全部接口列表(第二個參數:interfaces)
  2. 肯定要生成的代理類的類名,默認爲:com.sun.proxy.$ProxyXXXX
  3. 根據須要實現的接口信息,在代碼中動態建立該Proxy類的字節碼;
  4. 將對應的字節碼轉換爲對應的class對象;
  5. 建立InvocationHandler實例handler,用來處理Proxy全部方法調用
  6. Proxy的class對象以建立的handler對象爲參數(第三個參數:invocationHandler),實例化一個Proxy對象

而對於InvocationHandler,咱們須要實現下列的invoke方法:

public Object invoke(Object proxy, Method method, Object[] args) 
複製代碼

在調用代理對象中的每個方法時,在代碼內部,都是直接調用了InvocationHandler的invoke方法,而invoke方法根據代理類傳遞給本身的method參數來區分是什麼方法。

能夠看出,Proxy.newProxyInstance()方法生成的對象也是實現了ShowService接口的,因此能夠在代碼中將其強制轉換爲ShowService來使用,和靜態代理到達了一樣的效果。咱們能夠用下面代碼把生成的代理類的字節碼保存到磁盤裏,而後反編譯看看JDK生成的動態代理類的結構。

package com.fufu.aop;

import sun.misc.ProxyGenerator;

import java.io.FileOutputStream;
import java.io.IOException;

/** * Created by zhoujunfu on 2018/9/7. */
public class ProxyUtils {

    public static void main(String[] args) {
        Star star = new Star("Eminem");
        generateClassFile(star.getClass(), "StarProxy");
    }

    public static void generateClassFile(Class clazz, String proxyName) {

        //根據類信息和提供的代理類名稱,生成字節碼
        byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
        String paths = clazz.getResource(".").getPath();
        System.out.println(paths);
        FileOutputStream out = null;

        try {
            //保留到硬盤中
            out = new FileOutputStream(paths + proxyName + ".class");
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
複製代碼

反編譯StarPoxy.class文件後獲得:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.fufu.aop.ShowService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

// 動態代理類StarPoxy實現了ShowService接口
public final class StarProxy extends Proxy implements ShowService {
    // 加載接口中定義的全部方法
    private static Method m1;
    private static Method m3;
    private static Method m4;
    private static Method m2;
    private static Method m0;

    //構造函數接入InvocationHandler,也就是持有了InvocationHandler對象h
    public StarProxy(InvocationHandler var1) throws {
        super(var1);
    }

    public final boolean equals(Object var1) throws {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    // 自動生成的sing()方法,實際調用InvocationHandler對象h的invoke方法,傳入m3參數對象表明sing()方法
    public final void sing(String var1) throws {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
    
    //同理生成dance()方法
    public final void dance() throws {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    // 加載接口中定義的全部方法
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m3 = Class.forName("com.fufu.aop.ShowService").getMethod("sing", new Class[]{Class.forName("java.lang.String")});
            m4 = Class.forName("com.fufu.aop.ShowService").getMethod("dance", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
複製代碼

經過上面反編譯後的代碼能夠看出,JDK生成的動態代理類實現和具體類相同的接口,並持有InvocationHandler對象(InvocationHandler對象又持有具體類),調用動態代理類中方法,會觸發傳入InvocationHandler的invoke()方法,經過method參數,來區分調用的是什麼具體的方法,具體以下圖所示:

4.2 CGLIB動態代理(經過繼承)

JDK中提供的生成動態代理類的機制有個鮮明的特色是:

某個類必須有實現的接口,而生成的代理類也只能代理某個類接口定義的方法,好比:若是上面例子的Star實現了繼承自ShowService接口的方法外,另外實現了方法play(),則在產生的動態代理類中不會有這個方法了!更極端的狀況是:若是某個類沒有實現接口,那麼這個類就不能用JDK產生動態代理了!

幸虧咱們有cglib,「CGLIB(Code Generation Library),是一個強大的,高性能,高質量的Code生成類庫,它能夠在運行期擴展Java類與實現Java接口。」

cglib 建立某個類A的動態代理類的模式是:

1.查找A上的全部非final 的public類型的方法定義;
2.將這些方法的定義轉換成字節碼;
3.將組成的字節碼轉換成相應的代理的class對象;
4.實現 MethodInterceptor接口,用來處理對代理類上全部方法的請求(這個接口和JDK動態代理InvocationHandler的功能和角色是同樣的)

有了上邊JDK動態代理的例子,cglib的理解起來就簡單了,仍是先以實例說明,ShowService接口和Star類都複用以前的不變:

實現 MethodInterceptor接口:

package com.fufu.aop;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/** * Created by zhoujunfu on 2018/9/7. */
public class MethodInterceptorImpl implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 表演開始前收錢
        getMoney();
        // 明星開始唱歌
        Object invoke = methodProxy.invokeSuper(o, objects);
        // 表演結束後開發票
        writeReceipt();

        return invoke;
    }

    private void getMoney() {
        System.out.println("get money");
    }

    private void writeReceipt() {
        System.out.println("write receipt");
    }
}
複製代碼

建立動態代理:

package com.fufu.aop;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;

/** * Created by zhoujunfu on 2018/9/7. */
public class CglibProxyDemo {

    public static void main(String[] args) {
        Star star = new Star("Eminem");

        MethodInterceptor methodInterceptor = new MethodInterceptorImpl();

        //cglib 中增強器,用來建立動態代理
        Enhancer enhancer = new Enhancer();
        //設置要建立動態代理的類
        enhancer.setSuperclass(star.getClass());
        // 設置回調,這裏至關因而對於代理類上全部方法的調用,都會調用CallBack,而Callback則須要實行intercept()方法進行攔截
        enhancer.setCallback(methodInterceptor);

        ShowService showService = (ShowService) enhancer.create();
        showService.sing("Mockingbird");
    }
}
複製代碼

經過以上實例能夠看出,Cglib經過繼承實現動態代理,具體類不須要實現特定的接口,並且代理類能夠調用具體類的非接口方法,更加靈活。

5.Spring AOP

5.1 概念

AOP的具體概念就再也不說了,網上一搜一大把,這篇文章主要介紹Spring AOP低層使用的代理技術,由於平時在使用Spring AOP時,不少人都是copy配置,對上面介紹的這些技術概念並不清楚。

Spring AOP採用的是動態代理,在運行期間對業務方法進行加強,因此不會生成新類,對於動態代理技術,Spring AOP提供了對JDK動態代理的支持以及CGLib的支持,然而何時用哪一種代理呢?

一、若是目標對象實現了接口,默認狀況下會採用JDK的動態代理實現AOP

二、若是目標對象實現了接口,能夠強制使用CGLIB實現AOP

三、若是目標對象沒有實現了接口,必須採用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換

目前來看,Spring貌似和AspectJ沒半毛錢關係,那爲何在許多應用了Spring AOP的項目中都出現了@AspectJ的註解呢?Spring是應用的動態代理,怎麼會還和AspectJ有關係呢,緣由是Spring AOP基於註解配置的狀況下,須要依賴於AspectJ包的標準註解,可是不須要額外的編譯以及AspectJ的織入器,而基於XML配置不須要,因此Spring AOP只是複用了AspectJ的註解,並無其餘依賴AspectJ的地方。

當Spring須要使用@AspectJ註解支持時,須要在Spring配置文件中以下配置:

<aop:aspectj-autoproxy/>
複製代碼

而關於第二點強制使用CGLIB,能夠經過在Spring的配置文件以下配置實現:

<aop:aspectj-autoproxy proxy-target-class="true"/>
複製代碼

proxy-target-class屬性值決定是基於接口的仍是基於類的代理被建立。若是proxy-target-class 屬性值被設置爲true,那麼基於類的代理將起做用(這時須要cglib庫)。若是proxy-target-class屬值被設置爲false或者這個屬性被省略,那麼標準的JDK 基於接口的代理。

因此,雖然使用了Aspect的Annotation,可是並無使用它的編譯器和織入器。其實現原理是JDK動態代理或Cglib,在運行時生成代理類。

已經寫了這麼多了,下面再貼兩個Spring AOP的demo代碼吧,分別是基於XML和註解的:

5.2 基於XML

切面類:

package com.fufu.spring.aop;

import org.springframework.stereotype.Component;

/** * Created by zhoujunfu on 2018/9/7. * 基於XML的Spring AOP */
@Component
public class AgentAdvisorXML {

    public void getMoney() {
        System.out.println("get money");
    }

    public void writeReceipt() {
        System.out.println("write receipt");
    }
}

複製代碼

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="star" class="com.fufu.proxy.Star">
        <property name="name" value="Eminem"/>
    </bean>

    <bean id="agentAdvisorXML" class="com.fufu.spring.aop.AgentAdvisorXML"/>
    
     <!--Spring基於Xml的切面-->
     <aop:config>
         <!-- 定義切點函數 -->
         <aop:pointcut id="singPointCut" expression="execution(* com.fufu.proxy.Star.sing(..))"/>
         <!-- 定義切面 order 定義優先級,值越小優先級越大-->
         <aop:aspect ref="agentAdvisorXML" order="0">
             <!--前置通知-->
             <aop:before method="getMoney" pointcut-ref="singPointCut"/>
             <!--後置通知-->
             <aop:after method="writeReceipt" pointcut-ref="singPointCut"/>
         </aop:aspect>
     </aop:config>

</beans>
複製代碼

測試類:

package com.fufu.spring.aop;

import com.fufu.proxy.ShowService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/** * Created by zhoujunfu on 2018/9/7. */
public class Main {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop.xml");

        Object star = applicationContext.getBean("star");

        ShowService showService = (ShowService)star;
        showService.sing("Mockingbird");
    }
}
複製代碼

5.3 基於註解

切面類:

package com.fufu.spring.aop;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/** * Created by zhoujunfu on 2018/9/7. * 基於註解的Spring AOP */
@Aspect
@Component
public class AgentAdvisor {

    @Before(value = "execution(* com.fufu.proxy.ShowService.sing(..))")
    public void getMoney() {
        System.out.println("get money");
    }

    @After(value = "execution(* com.fufu.proxy.ShowService.sing(..))")
    public void writeReceipt() {
        System.out.println("write receipt");
    }
}

複製代碼

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


    <context:component-scan base-package="com.fufu.proxy, com.fufu.spring.aop"/>

    <aop:aspectj-autoproxy  proxy-target-class="true"/>

</beans>
複製代碼

測試類:

package com.fufu.spring.aop;

import com.fufu.proxy.ShowService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by zhoujunfu on 2018/9/7.
 */
public class Main {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");

        Object star = applicationContext.getBean("star");

        ShowService showService = (ShowService)star;
        showService.sing("Mockingbird");
    }
}
複製代碼

6.總結

以上內容,雖然比較淺顯易懂,可是能夠對Java代理機制和Spring AOP會有一個全面的理解,若有錯誤,歡迎指正。

相關文章
相關標籤/搜索