五、代理模式詳解

7.代理模式

7.1.課程目標

1、掌握代理模式的應用場景和實現原理。

2、瞭解靜態代理和動態代理的區別。

3、瞭解CGLib和JDK Proxy的根本區別。

4、手寫實現定義的動態代理。

7.2.內容定位

都知道 SpringAOP 是用代理模式實現,到底是怎麼實現的?我們來一探究竟,並且自己仿真手寫 還原部分細節。

7.3.代理模式定義

代理模式(ProxyPattern)是指為其他對象提供一種代理,以控制對這個對象的訪問,屬於結構型模式。

在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標 對象之間起到中介的作用。

官方原文:Provide a surrogate or placeholder for another object to control access to it.

首先來看代理模式的通用UML類圖:

五、代理模式詳解

代理模式一般包含三種角色:

抽象主題角色(Subject):抽象主題類的主要職責是聲明真實主題與代理的共同接口方法,該類可以是接口也可以是抽象類;

真實主題角色(RealSubject):該類也被稱為被代理類,該類定義了代理所表示的真實對象,是負責執行系統真正的邏輯業務對象;

代理主題角色(Proxy):也被稱為代理類,其內部持有 RealSubject 的引用,因此具備完全的對 RealSubject的代理權。客戶端調用代理對象的方法,同時也調用被代理對象的方法,但是會在代理對 象前後增加一些處理代碼。

在代碼中,一般代理會被理解為代碼增強,實際上就是在原代碼邏輯前後增加一些代碼邏輯,而使調用者無感知代理模式屬於結構型模式

,分為靜態代理動態代理

7.4.代理模式的應用場景

生活中的租房中介、售票黃牛、婚介、經紀人、快遞、事務代理、非侵入式日誌監聽等,都是代理 模式的實際體現。當無法或不想直接引用某個對象或訪問某個對象存在困難時,可以通過也給代理對象 來間接訪問。使用代理模式主要有兩個目的:一是保護目標對象,二是增強目標對象。

7.5.代理模式的通用寫法

下面是代理模式的通用代碼展示。

首先創建代理主題角色ISubject類:

<code> public interface ISubject {
     void request();
 }/<code>

創建真實主題角色RealSubject類:

<code> public class RealSubject implements ISubject {
     public void request() {
         System.out.println("real service is called.");
    }
 }/<code>

創建代理主題角色Proxy類:

<code> public class Proxy implements ISubject {
 ​
     private ISubject subject;
 ​
     public Proxy(ISubject subject){
         this.subject = subject;
    }
 ​
     public void request() {
         before();
         subject.request();
         after();
    }
 ​
     public void before(){
         System.out.println("called before request().");
    }
 ​
     public void after(){
         System.out.println("called after request().");
    }
 }/<code>

客戶端調用代碼:

<code> public class Client {
     public static void main(String[] args) {
         Proxy proxy = new Proxy(new RealSubject());
         proxy.request();
    }
 }/<code>

運行結果

<code> called before request().
 real service is called.
 called after request()./<code>

7.6.從靜態代理到動態代理

舉個例子,有些人到了適婚年齡,其父母總是迫不及待地希望早點抱孫子。而現在在各種壓力之下, 很多人都選擇晚婚晚育。於是著急的父母就開始到處為自己的子女相親,比子女自己還著急。下面來看代碼實現。

靜態代理:

創建頂層接口IPerson的代碼如下:

<code> public interface IPerson {
     void findLove();
 }/<code>

兒子張三要找對象,實現ZhangSan類:

<code> public class ZhangSan implements IPerson {
     public void findLove() {
         System.out.println("兒子要求:膚白貌美大長腿");
    }
 }/<code>

父親張老三要幫兒子張三相親,實現Father類:

<code> public class ZhangLaosan implements IPerson {
 ​
     private ZhangSan zhangsan;
 ​
     public ZhangLaosan(ZhangSan zhangsan) {
         this.zhangsan = zhangsan;
    }
 ​
     public void findLove() {
         // before
         System.out.println("張老三開始物色");
         zhangsan.findLove();
         // after
         System.out.println("開始交往");
    }
 }/<code>

來看測試代碼:

<code> public class Test {
     public static void main(String[] args) {
         ZhangLaosan zhangLaosan = new ZhangLaosan(new ZhangSan());

         zhangLaosan.findLove();
    }
 }/<code>

運行結果:

<code> 張老三開始物色
 兒子要求:膚白貌美大長腿
 開始交往/<code>

但上面的場景有個弊端,就是自己父親只會給自己的子女去物色對象,別人家的孩子是不會管的。

但社會上這項業務發展成了一個產業,出現了媒婆、婚介所等,還有各種各樣的定製套餐。如果還使用靜態代理成本就太高了,需要一個更加通用的解決方案,滿足任何單身人士找對象的需求。

這就是由靜態代理升級到了動態代理。

採用動態代理基本上只要是人(IPerson)就可以提供相親服務。

動態代理的底層實現一般不用我們自己親自去實現,已經有很多現成的API。

在Java生態中,目前最普遍使用的是JDK自帶的代理和Cglib提供的類庫


下面我們首先基於JDK的動態代理支持如來升級一下代碼。

首先,創建媒婆(婚介所)類JdkMeipo:

<code> public class JdkMeipo implements InvocationHandler {
     
     private IPerson target;
     
     // 反射獲取
     public IPerson getInstance(IPerson target){
         this.target = target;
         Class> clazz =  target.getClass();
         return (IPerson) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }
 ​
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         before();
         Object result = method.invoke(this.target,args);
         after();
         return result;
    }
 ​
     private void after() {
         System.out.println("雙方同意,開始交往");
    }
 ​
     private void before() {
         System.out.println("我是媒婆,已經收集到你的需求,開始物色");
    }
 }/<code>

再創建一個類ZhaoLiu:

<code> public class ZhaoLiu implements IPerson {
 ​
     public void findLove() {
         System.out.println("趙六要求:有車有房學歷高");

    }
 ​
     public void buyInsure() {
    }
 }/<code>

測試代碼如下:

<code> public class Test {
     public static void main(String[] args) {
         JdkMeipo jdkMeipo = new JdkMeipo();
         IPerson zhangsan = jdkMeipo.getInstance(new Zhangsan());
         zhangsan.findLove();
         
         IPerson zhaoliu = jdkMeipo.getInstance(new ZhaoLiu());
         zhaoliu.findLove();
    }
 }/<code>

運行效果如下:

<code> 我是媒婆,已經收集到你的需求,開始物色
 張三要求:膚白貌美大長腿
 雙方同意,開始交往
 我是媒婆,已經收集到你的需求,開始物色
 趙六要求:有車有房學歷高
 雙方同意,開始交往/<code>

7.7.靜態模式在業務中的應用

這裡“小夥伴們”可能會覺得還是不知道如何將代理模式應用到業務場景中,我們來看一個實際的業務場景。

在分佈式業務場景中,通常會對數據庫進行分庫分表,分庫分表之後使用 Java操作時就可能需要

配置多個數據源,我們通過設置數據源路由來動態切換數據源

先創建Order訂單類:

<code> @Data
 public class Order {
     private Object orderInfo;
     //訂單創建時間進行按年分庫
     private Long createTime;
     private String id;
 }/<code>

創建OrderDao持久層操作類:

<code> public class OrderDao {
     public int insert(Order order){
         System.out.println("OrderDao創建Order成功!");
         return 1;
    }
 }/<code>

創建IOrderService接口:

<code> public interface IOrderService {
     int createOrder(Order order);
 }/<code>

創建OrderService實現類:

<code> public class OrderService implements IOrderService {
     private OrderDao orderDao;
 ​
     public OrderService(){
         //如果使用Spring應該是自動注入的
         //我們為了使用方便,在構造方法中將orderDao直接初始化了
         orderDao = new OrderDao();
    }

 ​
     public int createOrder(Order order) {
         System.out.println("OrderService調用orderDao創建訂單");
         return orderDao.insert(order);
    }
 }/<code>

接下來使用靜態代理,主要完成的功能是:根據訂單創建時間自動按年進行分庫

根據開閉原則,我們修改原來寫好的代碼邏輯,通過代理對象來完成。

先創建數據源路由對象,使用ThreadLocal的單例實現DynamicDataSourceEntity類:

<code> public class DynamicDataSourceEntity {
 ​
     public final static String DEFAULE_SOURCE = null;
 ​
     private final static ThreadLocal<string> local = new ThreadLocal<string>();
 ​
     private DynamicDataSourceEntity(){}
 ​
     public static String get(){return  local.get();}
 ​
     public static void restore(){
          local.set(DEFAULE_SOURCE);
    }
 ​
     //DB_2018
     //DB_2019
     public static void set(String source){local.set(source);}
 ​
     public static void set(int year){local.set("DB_" + year);}
 }/<string>/<string>/<code>

創建切換數據源的代理類OrderServiceSaticProxy:

<code> public class OrderServiceStaticProxy implements IOrderService {
     private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
 ​
     private IOrderService orderService;
     public OrderServiceStaticProxy(IOrderService orderService) {
         this.orderService = orderService;
    }
 ​
     public int createOrder(Order order) {
         before();
         Long time = order.getCreateTime();
         Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
         System.out.println("靜態代理類自動分配到【DB_" +  dbRouter + "】數據源處理數據" );
         DynamicDataSourceEntity.set(dbRouter);
         this.orderService.createOrder(order);
         DynamicDataSourceEntity.restore();
         after();
         return 0;
    }
 ​
     private void before(){ System.out.println("Proxy before method."); }
     private void after(){ System.out.println("Proxy after method."); }
 }/<code>

來看測試代碼:

<code> public class DbRouteProxyTest {
     public static void main(String[] args) {
         try {
             Order order = new Order();
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
             Date date = sdf.parse("2020/03/01");
             order.setCreateTime(date.getTime());
             IOrderService orderService = new OrderServiceStaticProxy(new OrderService());
             orderService.createOrder(order);
        } catch (Exception e) {
             e.printStackTrace();
        }
    }
 }/<code>

運行結果如下:

<code> Proxy before method.
 靜態代理類自動分配到【DB_2020】數據源處理數據
 OrderService調用orderDao創建訂單

 OrderDao創建Order成功!
 Proxy after method./<code>

結果符合我們的預期。現在再來回顧一下類圖,看是不是和我們最先畫的一致,如下圖所示。

五、代理模式詳解

動態代理和靜態代理的基本思路是一致的,只不過動態代理功能更加強大,隨著業務的擴展適應性更強。

7.8.動態代理在業務中的應用

上面的案例理解了,我們再來看數據源動態路由業務,幫助“小夥伴們”加深對動態代理的印象。

創建動態代理的類OrderServiceDynamicProxy:

<code> package com.gupaoedu.vip.pattern.proxy.dbroute.proxy;
 ​
 import com.gupaoedu.vip.pattern.proxy.dbroute.db.DynamicDataSourceEntity;
 ​
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 ​
 public class OrderServiceDynamicProxy implements InvocationHandler {
     private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
     private Object target;
 ​
     public Object getInstance(Object target) {
         this.target = target;
         Class> clazz = target.getClass();
         return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }
 ​
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         before(args[0]);
         Object object = method.invoke(target, args);
         after();
         return object;
    }

 ​
     private void before(Object target) {
         try {
             System.out.println("Proxy before method.");
             Long time = (Long) target.getClass().getMethod("getCreateTime").invoke(target);
             Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
             System.out.println("動態代理類自動分配到【DB_" + dbRouter + "】數據源處理數據");
             DynamicDataSourceEntity.set(dbRouter);
        } catch (Exception e) {
             e.printStackTrace();
        }
    }
 ​
     private void after() {
         System.out.println("Proxy after method.");
    }
 }/<code>

測試代碼如下:

<code> public class DbRouteProxyTest {
     public static void main(String[] args) {
         try {
             Order order = new Order();
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
             Date date = sdf.parse("2020/03/01");
             order.setCreateTime(date.getTime());
             IOrderService orderService = (IOrderService) new OrderServiceDynamicProxy().getInstance(new OrderService());
             orderService.createOrder(order);
        } catch (Exception e) {
             e.printStackTrace();
        }
    }
 }/<code>

運行效果如下:

<code> Proxy before method.
 靜態代理類自動分配到【DB_2020】數據源處理數據
 OrderService調用orderDao創建訂單
 OrderDao創建Order成功!
 Proxy after method./<code>

依然能夠達到相同運行效果。但是,使用動態代理實現之後,我們不僅能實現 Order的數據源動態 路由,還可以實現其他任何類的數據源路由。當然,有個比較重要的約定,必須實現getCreateTime() 方法,因為路由規則是根據時間來運算的。我們可以通過接口規範來達到約束的目的,在此就不再舉例。

7.9.手寫JDK動態代理實現原理

不僅知其然,還得知其所以然。既然JDK動態代理功能如此強大,那麼它是如何實現的呢?我們現 在來探究一下原理,並模仿JDK動態代理動手寫一個屬於自己的動態代理。

我們都知道JDK動態代理採用字節重組,重新生成對象來替代原始對象,以達到動態代理的目的。

JDK動態代理的實現原理

  1. 獲取被代理對象的引用,並且獲取它的所有接口(反射獲取)。
  2. JDK Proxy類重新生成一個新的類,實現了被代理類所有接口的方法。
  3. 動態生成Java代碼,把增強邏輯加入到新生成代碼中。
  4. 編譯生成新的Java代碼的class文件。
  5. 加載並重新運行新的class,得到類就是全新類。

CGLib動態代理容易踩的坑

  1. 無法代理final修飾的方法。

以上過程就叫字節碼重組。JDK中有一個規範,在ClassPath下只要是$開頭的.class文件,一般都是自動生成的。那麼我們有沒有辦法看到代替後的對象的“真容”呢?做一個這樣測試,我們將內存中 的對象字節碼通過文件流輸出到一個新的.class文件,然後利用反編譯工具查看其源代碼。

<code> public class Test {
     public static void main(String[] args) {
         JdkMeipo jdkMeipo = new JdkMeipo();
         IPerson zhangsan = jdkMeipo.getInstance(new Zhangsan());
         zhangsan.findLove();
         // 通過反編譯工具可以查看源代碼
         try {
             byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{IPerson.class});
             FileOutputStream os = new FileOutputStream("F://$Proxy0.class");
             os.write(bytes);
             os.close();
        } catch (IOException e) {
             e.printStackTrace();
        }
    }
 }/<code>

運行以上代碼,我們能在E盤下找到一個$Proxy0.class 文件。使用Jad反編譯,得到$Proxy0.jad 文件,打開它可以看到如下內容:

<code> import com.gupaoedu.vip.pattern.proxy.dynamicproxy.jdkproxy.IPerson;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.lang.reflect.UndeclaredThrowableException;
 ​
 public final class $Proxy0 extends Proxy implements IPerson {
     private static Method m1;

     private static Method m3;
     private static Method m2;
     private static Method m4;
     private static Method m0;
 ​
     public $Proxy0(InvocationHandler var1) throws {
         super(var1);
    }
 ​
     public final boolean equals(Object var1) throws {
         try {
             return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
             throw var3;
        } catch (Throwable var4) {
             throw new UndeclaredThrowableException(var4);
        }
    }
 ​
     public final void findLove() throws {
         try {
             super.h.invoke(this, m3, (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 void buyInsure() throws {
         try {
             super.h.invoke(this, m4, (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);
        } catch (RuntimeException | Error var2) {
             throw var2;
        } catch (Throwable var3) {
             throw new UndeclaredThrowableException(var3);
        }
    }
 ​
     static {
         try {
             m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
             m3 = Class.forName("com.gupaoedu.vip.pattern.proxy.dynamicproxy.jdkproxy.IPerson").getMethod("findLove");
             m2 = Class.forName("java.lang.Object").getMethod("toString");
             m4 = Class.forName("com.gupaoedu.vip.pattern.proxy.dynamicproxy.jdkproxy.IPerson").getMethod("buyInsure");
             m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
             throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
             throw new NoClassDefFoundError(var3.getMessage());
        }
    }
 }/<code>

我們發現,$Proxy0繼承了Proxy類,同時還實現了Person接口,而且重寫了findLove()等方法。 在靜態塊中用反射查找到了目標對象的所有方法,而且保存了所有方法的引用,重寫的方法用反射調用 目標對象的方法。“小夥伴們”此時一定會好奇:這些代碼是哪裡來的呢?其實是JDK幫我們自動生成 的。現在我們不依賴JDK,自己來動態生成源代碼、動態完成編譯,然後替代目標對象並執行。

創建GPInvocationHandler接口:

<code> public interface GPInvocationHandler {
     Object invoke(Object proxy, Method method, Object[] args)
             throws Throwable;
 }/<code>

創建GPProxy類:

<code> /**
  * 用來生成源代碼的工具類
  */
 public class GPProxy {
 ​
     public static final String ln = "\\r\\n";
 ​
     public static Object newProxyInstance(GPClassLoader classLoader, Class> [] interfaces, GPInvocationHandler h){
        try {
            //1、動態生成源代碼.java文件
            String src = generateSrc(interfaces);
 ​
    //System.out.println(src);
            //2、Java文件輸出磁盤
            String filePath = GPProxy.class.getResource("").getPath();
    //System.out.println(filePath);
            File f = new File(filePath + "$Proxy0.java");
            FileWriter fw = new FileWriter(f);
            fw.write(src);
            fw.flush();
            fw.close();
 ​
            //3、把生成的.java文件編譯成.class文件
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manage = compiler.getStandardFileManager(null,null,null);
            Iterable iterable = manage.getJavaFileObjects(f);
 ​
           JavaCompiler.CompilationTask task = compiler.getTask(null,manage,null,null,null,iterable);
           task.call();
           manage.close();
 ​
            //4、編譯生成的.class文件加載到JVM中來
           Class proxyClass =  classLoader.findClass("$Proxy0");
           Constructor c = proxyClass.getConstructor(GPInvocationHandler.class);
           f.delete();
 ​
            //5、返回字節碼重組以後的新的代理對象
            return c.newInstance(h);
        }catch (Exception e){
            e.printStackTrace();
        }
         return null;

    }
 ​
     private static String generateSrc(Class>[] interfaces){
             StringBuffer sb = new StringBuffer();
             sb.append(GPProxy.class.getPackage() + ";" + ln);
             sb.append("import " + interfaces[0].getName() + ";" + ln);
             sb.append("import java.lang.reflect.*;" + ln);
             sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
                 sb.append("GPInvocationHandler h;" + ln);
                 sb.append("public $Proxy0(GPInvocationHandler h) { " + ln);
                     sb.append("this.h = h;");
                 sb.append("}" + ln);
                 for (Method m : interfaces[0].getMethods()){
                     Class>[] params = m.getParameterTypes();
 ​
                     StringBuffer paramNames = new StringBuffer();
                     StringBuffer paramValues = new StringBuffer();
                     StringBuffer paramClasses = new StringBuffer();
 ​
                     for (int i = 0; i < params.length; i++) {
                         Class clazz = params[i];
                         String type = clazz.getName();
                         String paramName = toLowerFirstCase(clazz.getSimpleName());
                         paramNames.append(type + " " +  paramName);
                         paramValues.append(paramName);
                         paramClasses.append(clazz.getName() + ".class");
                         if(i > 0 && i < params.length-1){
                             paramNames.append(",");
                             paramClasses.append(",");
                             paramValues.append(",");
                        }
                    }
 ​
                     sb.append("public " + m.getReturnType().getName() + " " + m.getName() + "(" + paramNames.toString() + ") {" + ln);
                         sb.append("try{" + ln);
                             sb.append("Method m = " + interfaces[0].getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{" + paramClasses.toString() + "});" + ln);
                             sb.append((hasReturnValue(m.getReturnType()) ? "return " : "") + getCaseCode("this.h.invoke(this,m,new Object[]{" + paramValues + "})",m.getReturnType()) + ";" + ln);
                         sb.append("}catch(Error _ex) { }");
                         sb.append("catch(Throwable e){" + ln);
                         sb.append("throw new UndeclaredThrowableException(e);" + ln);
                         sb.append("}");
                         sb.append(getReturnEmptyCode(m.getReturnType()));
                     sb.append("}");
                }
             sb.append("}" + ln);
             return sb.toString();
    }
 ​
 ​
     private static Map<class> mappings = new HashMap<class>();

     static {
         mappings.put(int.class,Integer.class);
    }
 ​
     private static String getReturnEmptyCode(Class> returnClass){
         if(mappings.containsKey(returnClass)){
             return "return 0;";
        }else if(returnClass == void.class){
             return "";
        }else {
             return "return null;";
        }
    }
 ​
     private static String getCaseCode(String code,Class> returnClass){
         if(mappings.containsKey(returnClass)){
             return "((" + mappings.get(returnClass).getName() +  ")" + code + ")." + returnClass.getSimpleName() + "Value()";
        }
         return code;
    }
 ​
     private static boolean hasReturnValue(Class> clazz){
         return clazz != void.class;
    }
 ​
     private static String toLowerFirstCase(String src){
         char [] chars = src.toCharArray();
         chars[0] += 32;
         return String.valueOf(chars);
    }
 ​
 }/<class>/<class>/<code>

創建GPClassLoader類:

<code> public class GPClassLoader extends ClassLoader {
 ​
     private File classPathFile;
     public GPClassLoader(){
         String classPath = GPClassLoader.class.getResource("").getPath();
         this.classPathFile = new File(classPath);
    }
 ​
     @Override
     protected Class> findClass(String name) throws ClassNotFoundException {
 ​
         String className = GPClassLoader.class.getPackage().getName() + "." + name;
         if(classPathFile  != null){
             File classFile = new File(classPathFile,name.replaceAll("\\\\.","/") + ".class");
             if(classFile.exists()){

                 FileInputStream in = null;
                 ByteArrayOutputStream out = null;
                 try{
                     in = new FileInputStream(classFile);
                     out = new ByteArrayOutputStream();
                     byte [] buff = new byte[1024];
                     int len;
                     while ((len = in.read(buff)) != -1){
                         out.write(buff,0,len);
                    }
                     return defineClass(className,out.toByteArray(),0,out.size());
                }catch (Exception e){
                     e.printStackTrace();
                }
            }
        }
         return null;
    }
 }/<code>

創建GPMeipo類:

<code> public class GpMeipo implements GPInvocationHandler {
     private IPerson target;
     public IPerson getInstance(IPerson target){
         this.target = target;
         Class> clazz =  target.getClass();
         return (IPerson) GPProxy.newProxyInstance(new GPClassLoader(),clazz.getInterfaces(),this);
    }
 ​
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         before();
         Object result = method.invoke(this.target,args);
         after();
         return result;
    }
 ​
     private void after() {
         System.out.println("雙方同意,開始交往");
    }
 ​
     private void before() {
         System.out.println("我是媒婆,已經收集到你的需求,開始物色");
    }
 }/<code>

客戶端測試代碼如下:

<code> public class Test {
     public static void main(String[] args) {
         GpMeipo gpMeipo = new GpMeipo();
         IPerson zhangsan = gpMeipo.getInstance(new Zhangsan());
         zhangsan.findLove();
    }
 }/<code>

運行效果如下:

<code> 我是媒婆,已經收集到你的需求,開始物色
 張三要求:膚白貌美大長腿
 雙方同意,開始交往/<code>

到此,手寫JDK動態代理就完成了。“小夥伴們”是不是又多了一個面試用的“撒手鐧”呢?

7.10.CGLib代理調用API及原理分析

簡單看一下CGLib代理的使用,還是以媒婆為例,創建CglibMeipo類:

<code> public class CGlibMeipo implements MethodInterceptor {
 ​
     public Object getInstance(Class> clazz) throws Exception{
         //相當於Proxy,代理的工具類
         Enhancer enhancer = new Enhancer();
         enhancer.setSuperclass(clazz);
         enhancer.setCallback(this);
         return enhancer.create();
    }
 ​
     public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
         before();
         Object obj = methodProxy.invokeSuper(o,objects);
         after();
         return obj;
    }
 ​

     private void before(){
         System.out.println("我是媒婆,我要給你找對象,現在已經確認你的需求");
         System.out.println("開始物色");
    }
 ​
     private void after(){
         System.out.println("OK的話,準備辦事");
    }
 }/<code>

創建單身客戶類Customer:

<code> public class Customer {
     public void findLove(){
         System.out.println("兒子要求:膚白貌美大長腿");
    }
 }/<code>

有個小細節,CGLib代理的目標對象不需要實現任何接口,它是通過動態繼承目標對象實現動態代 理的。來看測試代碼:

<code> public class CglibTest {
     public static void main(String[] args) {
         try {
             Customer obj = (Customer)new CGlibMeipo().getInstance(Customer.class);
             obj.findLove();        
        } catch (Exception e) {
             e.printStackTrace();
        }
    }
 }/<code>

CGLib 代理的實現原理又是怎樣的呢?我們可以在測試代碼中加上一句代碼,將 CGLib 代理後 的.class文件寫入磁盤,然後反編譯來一探究竟,代碼如下:

<code> public class CglibTest { 

     public static void main(String[] args) {
         try {
             //JDK是採用讀取接口的信息
             //CGLib覆蓋父類方法
             //目的:都是生成一個新的類,去實現增強代碼邏輯的功能
 ​
             //JDK Proxy 對於用戶而言,必須要有一個接口實現,目標類相對來說複雜
             //CGLib 可以代理任意一個普通的類,沒有任何要求
 ​
             //CGLib 生成代理邏輯更復雜,效率,調用效率更高,生成一個包含了所有的邏輯的FastClass,不再需要反射調用
             //JDK Proxy生成代理的邏輯簡單,執行效率相對要低,每次都要反射動態調用
 ​
             //CGLib 有個坑,CGLib不能代理final的方法
             System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"E://cglib_proxy_classes");
             Customer obj = (Customer) new CGlibMeipo().getInstance(Customer.class);
             System.out.println(obj);
             obj.findLove();
        } catch (Exception e) {
             e.printStackTrace();
        }
    }
 }/<code>

重新執行代碼,我們會發現在E://cglib_proxy_class目錄下多了三個.class文件,如下圖所示。

五、代理模式詳解

通過調試跟蹤發現,Customer$$EnhancerByCGLIB$$3feeb52a.class 就是 CGLib 代理生成的代 理類,繼承了Customer類。

<code> package com.gupaoedu.vip.pattern.proxy.dynamicproxy.cglibproxy;
 ​
 import java.lang.reflect.Method;
 import net.sf.cglib.core.ReflectUtils;
 import net.sf.cglib.core.Signature;
 import net.sf.cglib.proxy.Callback;
 import net.sf.cglib.proxy.Factory;
 import net.sf.cglib.proxy.MethodInterceptor;
 import net.sf.cglib.proxy.MethodProxy;
 ​
 public class Customer$$EnhancerByCGLIB$$6d99cfc2 extends Customer implements Factory {
    ...
     final void CGLIB$findLove$0() {
         super.findLove();
    }
 ​
     public final void findLove() {
         MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
         if (var10000 == null) {
             CGLIB$BIND_CALLBACKS(this);
             var10000 = this.CGLIB$CALLBACK_0;
        }
 ​
         if (var10000 != null) {
             var10000.intercept(this, CGLIB$findLove$0$Method, CGLIB$emptyArgs, CGLIB$findLove$0$Proxy);
        } else {
             super.findLove();
        }
    }
    ...
 }/<code>

我們重寫了Customer類的所有方法,通過代理類的源碼可以看到,代理類會獲得所有從父類繼承 來的方法,並且會有 MethodProxy 與之對應,比如 Method CGLIB$findLove$0$Method、 MethodProxy CGLIB$findLove$0$Proxy這些方法在代理類的findLove()方法中都有調用。

<code> //代理方法(methodProxy.invokeSuper()方法會調用) 
 final void CGLIB$findLove$0() {

     super.findLove();
 }
 ​
 //被代理方法(methodProxy.invoke()方法會調用,這就是為什麼在攔截器中調用 methodProxy.invoke 會發生死循環,一直在調用攔截器)
 public final void findLove() {
     MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
     if (var10000 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
    }
 ​
     if (var10000 != null) {
         var10000.intercept(this, CGLIB$findLove$0$Method, CGLIB$emptyArgs, CGLIB$findLove$0$Proxy);
    } else {
         super.findLove();
    }
 }/<code>

調用過程為:代理對象調用 this.findLove()方法→調用攔截器→methodProxy.invokeSuper→ CGLIB$findLove$0→被代理對象findLove()方法。

此時,我們發現攔截器 MethodInterceptor中就是由 MethodProxy的invokeSuper()方法調用代 理方法的,MethodProxy非常關鍵,我們分析一下它具體做了什麼。

<code> package net.sf.cglib.proxy;
 ​
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import net.sf.cglib.core.AbstractClassGenerator;
 import net.sf.cglib.core.CodeGenerationException;
 import net.sf.cglib.core.GeneratorStrategy;
 import net.sf.cglib.core.NamingPolicy;
 import net.sf.cglib.core.Signature;
 import net.sf.cglib.reflect.FastClass;
 import net.sf.cglib.reflect.FastClass.Generator;
 ​
 public class MethodProxy {
     private Signature sig1;
     private Signature sig2;
     private MethodProxy.CreateInfo createInfo;
     private final Object initLock = new Object();

     private volatile MethodProxy.FastClassInfo fastClassInfo;
 ​
     public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
         MethodProxy proxy = new MethodProxy();
         proxy.sig1 = new Signature(name1, desc);
         proxy.sig2 = new Signature(name2, desc);
         proxy.createInfo = new MethodProxy.CreateInfo(c1, c2);
         return proxy;
    }
    ...
     private static class CreateInfo {
         Class c1;
         Class c2;
         NamingPolicy namingPolicy;
         GeneratorStrategy strategy;
         boolean attemptLoad;
 ​
         public CreateInfo(Class c1, Class c2) {
             this.c1 = c1;
             this.c2 = c2;
             AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();
             if (fromEnhancer != null) {
                 this.namingPolicy = fromEnhancer.getNamingPolicy();
                 this.strategy = fromEnhancer.getStrategy();
                 this.attemptLoad = fromEnhancer.getAttemptLoad();
            }
 ​
        }
    }
  ...
 }/<code>

繼續看invokeSuper()方法:

<code>     public Object invokeSuper(Object obj, Object[] args) throws Throwable {
         try {
             init();
             FastClassInfo fci = fastClassInfo;
             return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException e) {
             throw e.getTargetException();
        }
    }
 ​
     private static class FastClassInfo
    {
         FastClass f1;
         FastClass f2;
         int i1;
         int i2;

    }/<code>

上面的代碼調用就是獲取代理類對應的FastClass,並執行代理方法。還記得之前生成的三個.class 文件嗎?Customer$$EnhancerByCGLIB$$3feeb52a$$FastClassByCGLIB$$6aad62f1.class 就是代理類的FastClass,Customer$$FastClassByCGLIB$$2669574a.class 就是被代理類的FastClass。

CGLib代理執行代理方法的效率之所以比JDK的高,是因為CGlib採用了FastClass機制,它的原 理簡單來說就是:為代理類和被代理類各生成一個類,這個類會為代理類或被代理類的方法分配一個 index(int類型);這個index當作一個入參,FastClass就可以直接定位要調用的方法並直接進行調 用,省去了反射調用,所以調用效率比JDK代理通過反射調用高。下面我們反編譯一個FastClass看看:

<code>     public Object invokeSuper(Object obj, Object[] args) throws Throwable {
         try {
             init();
             FastClassInfo fci = fastClassInfo;
             return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException e) {
             throw e.getTargetException();
        }
    }
 ​
     private static class FastClassInfo
    {
         FastClass f1;
         FastClass f2;
         int i1;
         int i2;
    }/<code>

CGLib代理執行代理方法的效率之所以比JDK的高,是因為CGlib採用了FastClass機制,它的原理簡單來說就是:為代理類和被代理類各生成一個類,這個類會為代理類或被代理類的方法分配一個 index(int類型);這個index當作一個入參,FastClass就可以直接定位要調用的方法並直接進行調 用,省去了反射調用,所以調用效率比JDK代理通過反射調用高。下面我們反編譯一個FastClass看看:

<code> public class test {
     public int getIndex(Signature signature) {
         String s = signature.toString();
         s;
         s.hashCode();
         JVM INSTR lookupswitch 11:default 223
         …
         JVM INSTR pop;
         return -1;
    }
 ​
     //部分代碼省略
     //根據 index 直接定位執行方法
     public Object invoke(int i, Object obj, Object[] aobj) throws InvocationTargetException {
        (Customer) obj;
         i;
         JVM INSTR tableswitch 0 10:default
         161 goto _L1 _L2 _L3 _L4 _L5 _L6 _L7 _L8 _L9 _L10 _L11 _L12 _L2:
         eat();
         return null;
         _L3:
         findLove();
         return null; …throw new IllegalArgumentException("Cannot find matching method/constructor");
    }
 }/<code>

FastClass 並不是跟代理類一起生成的,而是在第一次執行 MethodProxy 的 invoke()或 invokeSuper()方法時生成的,並放在了緩存中。

<code> //MethodProxy 的 invoke()或 invokeSuper()方法都調用了 init()方法
 private void init()
 {
     /*
      * Using a volatile invariant allows us to initialize the FastClass and
      * method index pairs atomically.
      *
      * Double-checked locking is safe with volatile in Java 5. Before 1.5 this
      * code could allow fastClassInfo to be instantiated more than once, which
      * appears to be benign.
      */
     if (fastClassInfo == null)
    {
         synchronized (initLock)
        {

             if (fastClassInfo == null)
            {
                 CreateInfo ci = createInfo;
 ​
                 FastClassInfo fci = new FastClassInfo();
                 fci.f1 = helper(ci, ci.c1);
                 fci.f2 = helper(ci, ci.c2);
                 fci.i1 = fci.f1.getIndex(sig1);
                 fci.i2 = fci.f2.getIndex(sig2);
                 fastClassInfo = fci;
            }
        }
    }
 }/<code>

至此,CGLib代理的原理我們就基本搞清楚了,對代碼細節有興趣的“小夥伴”可以自行深入研究。

7.11.CGLib和JDK動態代理對比

(1)JDK動態代理實現了被代理對象的接口,CGLib代理繼承了被代理對象

(2) JDK動態代理和CGLib代理都在運行期生成字節碼, JDK動態代理直接寫Class字節碼, CGLib 代理使用ASM框架寫Class字節碼,CGlib代理實現更復雜,生成代理類比JDK動態代理效率低

(3)JDK動態代理調用代理方法是通過反射機制調用的,CGLib代理是通過FastClass機制直接調用方法的,CGLib代理的

執行效率更高

7.12.代理模式與Spring生態

1、代理模式在Spring中的應用

先看ProxyFactoryBean核心方法getObject(),源碼如下:

<code> @Nullable
 public Object getObject() throws BeansException {
     this.initializeAdvisorChain();
     if (this.isSingleton()) {
         return this.getSingletonInstance();
    } else {
         if (this.targetName == null) {
             this.logger.info("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the 'targetName' property.");
        }
 ​
         return this.newPrototypeInstance();
    }
 }/<code>

在getObject()方法中,主要調用 getSingletonInstance()和 newPrototypeInstance()。在 Spring 的配置中如果不做任何設置,那麼 Spring 代理生成的 Bean 都是單例對象。如果修改 scope,則每次 創建一個新的原型對象。newPrototypeInstance()裡面的邏輯比較複雜,我們後面再做深入研究,這裡 先做簡單瞭解。

Spring 利用動態代理實現 AOP 時有兩個非常重要的類:JdkDynamicAopProxy 類和CglibAopProxy類,來看一下類圖,如下圖所示。

五、代理模式詳解

7.13.靜態代理和動態代理的本質區別

(1)靜態代理只能通過手動完成代理操作,如果被代理類增加了新的方法,代理類需要同步增加, 違背開閉原則。

(2)動態代理採用在運行時動態生成代碼的方式,取消了對被代理類的擴展限制,遵循開閉原則。

(3)若動態代理要對目標類的增強邏輯進行擴展,結合策略模式,只需要新增策略類便可完成,無須修改代理類的代碼。

7.14.代理模式的優缺點

代理模式具有以下優點:

(1)代理模式能將代理對象與真實被調用目標對象分離。

(2)在一定程度上降低了系統的耦合性,擴展性好。

(3)可以起到保護目標對象的作用。

(4)可以增強目標對象的功能。

當然,代理模式也有缺點:

(1)代理模式會造成系統設計中類的數量增加。

(2)在客戶端和目標對象中增加一個代理對象,會導致請求處理速度變慢。

(3)增加了系統的複雜度。

7.15.Spring中的代理選擇原則

(1)當Bean有實現接口時,Spring就會用JDK動態代理。

(2)當Bean沒有實現接口時,Spring會選擇CGLib代理。

(3)Spring可以通過配置強制使用CGLib代理,只需在Spring的配置文件中加入如下代碼:

<code> <aspectj-autoproxy>/<code>

7.16.作業

1、請總結靜態代理和動態代理的根本區別。

靜態代理是硬編碼,動態代理是動態生成。

2、繼續完成手寫Proxy類中帶參數方法的代理實現。

Zhangsan添加一個新方法

<code> public void setAge(int age) {
     System.out.println("年齡要求" + age);
 }/<code>

生成字節碼

<code>     private static Method m3;
 ​
  public final void setAge(int var1) throws {
         try {
             super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
             throw var3;
        } catch (Throwable var4) {
             throw new UndeclaredThrowableException(var4);
        }
    }
 ​
     static {
         try {        
             m3 = Class.forName("com.gupaoedu.vip.pattern.proxy.dynamicproxy.jdkproxy.IPerson").getMethod("setAge", Integer.TYPE);        
        } catch (NoSuchMethodException var2) {
             throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
             throw new NoClassDefFoundError(var3.getMessage());
        }
    }/<code>



分享到:


相關文章: