只有掌握了這三種代理模式,才能進軍Spring AOP哦!

代理模式定義

首先咱們來看看代理模式: java

image.png
所謂代理模式,是指客戶端(Client)並不直接調用實際的對象(下圖右下角的RealSubject),而是經過調用代理(ProxySubject),來間接的調用實際的對象。

代理模式的使用場合,通常是因爲客戶端不想直接訪問實際對象,或者訪問實際的對象存在技術上的障礙,於是經過代理對象做爲橋樑,來完成間接訪問。spring

業務場景

首先有個UserService接口,接口裏有一個添加用戶的方法ide

public interface UserService {
    void addUser();
}
複製代碼

這是它的實現類函數

public class UserServiceImpl implements UserService {
    @Override
    public void addUser() {
        System.out.println("添加一個用戶");
    }
}
複製代碼

如今須要在添加用戶的時候記錄一下日誌。固然,你能夠直接在addUser裏面直接寫添加日誌的代碼,工具

public void addUser() {
        System.out.println("添加一個用戶");
	System.out.println("拿個小本本記一下");
    }
複製代碼

可是Java推崇單一職責原則,若是這樣寫就違背了這個原則,咱們須要將添加日誌的代碼解耦出來,讓addUser()方法專一寫本身的業務邏輯。測試

靜態代理

根據類圖,建立一個靜態代理類this

public class UserStaticProxy implements UserService{
    private UserService userService;
    public UserStaticProxy(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void addUser() {
        userService.addUser();
        System.out.println("拿個小本本記錄一下");
    }
}
複製代碼

咱們創建一個測試類來測試靜態代理:編碼

public class Test {

    public static void main(String[] args) {
        UserStaticProxy userStaticProxy = new UserStaticProxy(new UserServiceImpl());
        userStaticProxy.addUser();
    }
}
複製代碼

運行結果: spa

image.png
如此,一個靜態代理類就建立好了,咱們能夠專一在Service寫業務邏輯,添加日誌等非業務邏輯交給這個靜態代理類來完成。

靜態代理的缺點

缺點一:接口增長方法,代理類須要同步維護

隨着業務擴大,UserService類裏不知有addUser方法,還有updateUser、deleteUser、batchUpdateUser、batchDeleteUser等方法,這些方法都須要記錄日誌。3d

UserServiceImpl類以下:

public class UserServiceImpl implements UserService {
    @Override
    public void addUser() {
        System.out.println("添加一個用戶");
    }

    @Override
    public void updateUser() {
        System.out.println("更新一個用戶");
    }

    @Override
    public void deleteUser() {
        System.out.println("刪除一個用戶");
    }

    @Override
    public void batchUpdateUser() {
        System.out.println("批量更新用戶");
    }

    @Override
    public void batchDeleteUser() {
        System.out.println("批量刪除用戶");
    }
}
複製代碼

那麼對應的靜態代理類以下:

public class UserStaticProxy implements UserService{
    private UserService userService;
    public UserStaticProxy(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void addUser() {
        userService.addUser();
        System.out.println("拿個小本本記錄一下");
    }

    @Override
    public void updateUser() {
        userService.updateUser();
        System.out.println("拿個小本本記錄一下");
    }

    @Override
    public void deleteUser() {
        userService.deleteUser();
        System.out.println("拿個小本本記錄一下");
    }

    @Override
    public void batchUpdateUser() {
        userService.batchUpdateUser();
        System.out.println("拿個小本本記錄一下");
    }

    @Override
    public void batchDeleteUser() {
        userService.batchDeleteUser();
        System.out.println("拿個小本本記錄一下");
    }
}
複製代碼

從上面咱們能夠看到,代理類裏有不少重複的日誌代碼。由於代理類和目標對象實現同一個接口,一旦接口增長方法,代理類也得同步增長方法而且得同步增長重複的額外功能代碼,增大了代碼量

缺點二:接口越多,致使代理類繁多

若是須要增長業務類,如StudentService,TeacherService等等,這些類裏的方法也都須要實現增長日誌的方法,那麼就須要同步建立對應的代理類。此外靜態代理類不是自動生成的,須要在編譯以前就編寫好的,若是業務愈來愈龐大,那麼建立的代理類愈來愈多,這樣又增大了代碼量

如何解決這些缺點呢?這時候就須要動態代理方法了

JDK動態代理

其實動態代理和靜態代理的本質是同樣的,最終程序運行時都須要生成一個代理對象實例,經過它來完成相關加強以及業務邏輯,只不過靜態代理須要硬編碼的方式指定,而動態代理支持運行時動態生成這種實現方式。

JDK自己幫咱們實現了動態代理,只須要使用newProxyInstance方法:

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 複製代碼

注意該方法是在Proxy類中是靜態方法,且接收的三個參數依次爲:

  • ClassLoader loader,:指定當前目標對象使用類加載器
  • Class<?>[] interfaces,:代理類須要實現的接口列表
  • InvocationHandler h:調用處理程序,將目標對象的方法分派到該調用處理程序

代碼示例:

public class DynamicProxy implements InvocationHandler {

    private Object target; // 目標對象

    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(target, args);
        System.out.println("拿個小本本記錄一下");
        return result;
    }

}
複製代碼

上文的invoke方法,負責加強目標對象的方法,接口類的全部方法都會走這個invoke方法。另外bind方法簡單封裝了JDK的代理方法newProxyInstance,負責返回接口類。

測試類:

public static void main(String[] args) {
        DynamicProxy dynamicProxy = new DynamicProxy();
        UserService userService = (UserService)dynamicProxy.bind(new UserServiceImpl());
        userService.addUser();
        userService.updateUser();
    }
複製代碼

運行結果以下:

image.png

如圖UserService接口裏的全部方法都已經加上了日誌邏輯了,此外,咱們看一下UserDynamicProxy這個類裏的target屬性是Object類型的。因此,這個動態代理的方法一樣能夠給其餘Service複用。能夠這樣調用:

DynamicProxy dynamicProxy = new DynamicProxy();
TeacherService teacherService = (TeacherService)dynamicProxy.bind(new TeacherServiceImpl());
複製代碼

綜上,動態代理解決了靜態代理的缺點

用arthas查看JDK動態代理生成的類

動態代理是運行時候動態生成代理類的,這個類放在內存中,咱們要怎麼才能看到這個類呢?

artias是阿里開源的一個牛逼閃閃的Java診斷工具,不懂的能夠看看這篇文章http://www.dblearn.cn/article/5,用它就能夠線上反編譯代碼。

這裏咱們添加一個斷點:

public static void main(String[] args) throws IOException {
        DynamicProxy dynamicProxy = new DynamicProxy();
        UserService userService = (UserService)dynamicProxy.bind(new UserServiceImpl());
        userService.addUser();
        userService.updateUser();
        System.in.read();
    }
複製代碼

運行 arthas

image.png

jad命令反編譯,java生成的代理類都在com.sun.proxy目錄下。所以反編譯命令以下

jad com.sun.proxy.$Proxy0

package com.sun.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.UserService;

public final class $Proxy0 extends Proxy implements UserService {
    private static Method m1;
    private static Method m6;
    private static Method m2;
    private static Method m7;
    private static Method m0;
    private static Method m3;
    private static Method m4;
    private static Method m5;

    public final void addUser() {
        try {
            this.h.invoke(this, m3, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void updateUser() {
        try {
            this.h.invoke(this, m4, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void deleteUser() {
        try {
            this.h.invoke(this, m5, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void batchUpdateUser() {
        try {
            this.h.invoke(this, m6, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void batchDeleteUser() {
        try {
            this.h.invoke(this, m7, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m6 = Class.forName("proxy.UserService").getMethod("batchUpdateUser", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m7 = Class.forName("proxy.UserService").getMethod("batchDeleteUser", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            m3 = Class.forName("proxy.UserService").getMethod("addUser", new Class[0]);
            m4 = Class.forName("proxy.UserService").getMethod("updateUser", new Class[0]);
            m5 = Class.forName("proxy.UserService").getMethod("deleteUser", new Class[0]);
            return;
        }
        catch (NoSuchMethodException noSuchMethodException) {
            throw new NoSuchMethodError(noSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException classNotFoundException) {
            throw new NoClassDefFoundError(classNotFoundException.getMessage());
        }
    }

    public final boolean equals(Object object) {
        try {
            return (Boolean)this.h.invoke(this, m1, new Object[]{object});
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String toString() {
        try {
            return (String)this.h.invoke(this, m2, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int hashCode() {
        try {
            return (Integer)this.h.invoke(this, m0, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
}

複製代碼

由上面的代碼能夠看到咱們的代理類已經生成好了,沒當咱們調用方法如 addUser(),實際分派到h變量的invoke方法上執行:

this.h.invoke(this, m3, null);

h變量是什麼呢?其實就是咱們實現了InvocationHandler的DynamicProxy類。

cglib動態代理

經過觀察上面的靜態代理和JDK動態代理模式,發現要求目標對象實現一個接口,可是有時候目標對象只是一個單獨的對象,並無實現任何的接口。這時候要怎麼處理呢?下面引出大名鼎鼎的CGlib動態代理

cglib代理,也叫做子類代理,它是在內存中構建一個子類對象從而實現對目標對象功能的擴展。

要用cglib須要引入它的jar包,由於spring已經集成了它,所以引入spring包便可

編寫代理類:

public class CGLibProxy implements MethodInterceptor {
    private Object target; // 目標對象
    public Object bind(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        //設置父類
        enhancer.setSuperclass(this.target.getClass());
        //設置回調函數
        enhancer.setCallback(this);
        //建立子類(代理對象)
        return enhancer.create();
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object result = methodProxy.invokeSuper(obj, args);
        System.out.println("拿個小本本記錄一下");
        return result;
    }
}
複製代碼

其中,Enhancer須要設置目標對象爲父類(由於生成的代理類須要繼承目標對象)

測試類:

public static void main(String[] args) throws IOException {
        CGLibProxy cgLibProxy = new CGLibProxy();
        UserServiceImpl userService = (UserServiceImpl)cgLibProxy.bind(new UserServiceImpl());
        userService.addUser();
        userService.updateUser();
        System.in.read();
    }
複製代碼

運行結果:

image.png

咱們看到已經成功代理了。可是結果有亂碼出現,此處設置一個// TODO,我猜想是Spring對CGlib再封裝致使的,也請知道的大大回答一下。

用arthas查看cglib動態代理生成的類

步驟和JDK代理類雷同,只不過cglib的代理類生成在和測試類同一個包下,因爲代碼太多,只上部分代碼

package com.example.demo.proxy;

import com.example.demo.proxy.UserServiceImpl;
import java.lang.reflect.Method;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.cglib.core.Signature;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

public class UserServiceImpl$$EnhancerByCGLIB$$3ca8cfc3 extends UserServiceImpl implements Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$deleteUser$0$Method;
    private static final MethodProxy CGLIB$deleteUser$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$addUser$1$Method;
    private static final MethodProxy CGLIB$addUser$1$Proxy;
    private static final Method CGLIB$updateUser$2$Method;
    private static final MethodProxy CGLIB$updateUser$2$Proxy;
    private static final Method CGLIB$batchUpdateUser$3$Method;
    private static final MethodProxy CGLIB$batchUpdateUser$3$Proxy;
    private static final Method CGLIB$batchDeleteUser$4$Method;
    private static final MethodProxy CGLIB$batchDeleteUser$4$Proxy;
    private static final Method CGLIB$equals$5$Method;
    private static final MethodProxy CGLIB$equals$5$Proxy;
    private static final Method CGLIB$toString$6$Method;
    private static final MethodProxy CGLIB$toString$6$Proxy;
    private static final Method CGLIB$hashCode$7$Method;
    private static final MethodProxy CGLIB$hashCode$7$Proxy;
    private static final Method CGLIB$clone$8$Method;
    private static final MethodProxy CGLIB$clone$8$Proxy;

    public final void deleteUser() {
        MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
        if (methodInterceptor == null) {
            UserServiceImpl$$EnhancerByCGLIB$$3ca8cfc3.CGLIB$BIND_CALLBACKS(this);
            methodInterceptor = this.CGLIB$CALLBACK_0;
        }
        if (methodInterceptor != null) {
            Object object = methodInterceptor.intercept(this, CGLIB$deleteUser$0$Method, CGLIB$emptyArgs, CGLIB$deleteUser$0$Proxy);
            return;
        }
        super.deleteUser();
    }

    public final void addUser() {
        MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
        if (methodInterceptor == null) {
            UserServiceImpl$$EnhancerByCGLIB$$3ca8cfc3.CGLIB$BIND_CALLBACKS(this);
            methodInterceptor = this.CGLIB$CALLBACK_0;
        }
        if (methodInterceptor != null) {
            Object object = methodInterceptor.intercept(this, CGLIB$addUser$1$Method, CGLIB$emptyArgs, CGLIB$addUser$1$Proxy);
            return;
        }
        super.addUser();
    }

    public final void updateUser() {
        MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
        if (methodInterceptor == null) {
            UserServiceImpl$$EnhancerByCGLIB$$3ca8cfc3.CGLIB$BIND_CALLBACKS(this);
            methodInterceptor = this.CGLIB$CALLBACK_0;
        }
        if (methodInterceptor != null) {
            Object object = methodInterceptor.intercept(this, CGLIB$updateUser$2$Method, CGLIB$emptyArgs, CGLIB$updateUser$2$Proxy);
            return;
        }
        super.updateUser();
    }

複製代碼

其中

public class UserServiceImpl$$EnhancerByCGLIB$$3ca8cfc3 extends UserServiceImpl

能夠看到生成的代理類繼承了目標對象,所以有兩個注意點:

  1. 目標對象不能處理被final關鍵字修飾,由於被final修飾的對象是不可繼承的。
  2. 目標對象的方法若是爲final/static,那麼就不會被攔截,即不會執行目標對象額外的業務方法.
相關文章
相關標籤/搜索