五良价、代理模式詳解

7.代理模式

7.1.課程目標

1寝殴、掌握代理模式的應用場景和實現(xiàn)原理。

2明垢、了解靜態(tài)代理和動態(tài)代理的區(qū)別蚣常。

3、了解CGLib和JDK Proxy的根本區(qū)別痊银。

4抵蚊、手寫實現(xiàn)定義的動態(tài)代理。

7.2.內容定位

都知道 SpringAOP 是用代理模式實現(xiàn)溯革,到底是怎么實現(xiàn)的贞绳?我們來一探究竟,并且自己仿真手寫
還原部分細節(jié)致稀。

7.3.代理模式定義

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

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

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

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

<img src="https://gitee.com/woshiamiaojiang/image-hosting/raw/master/image-20200302204930872.png" alt="image-20200302204930872" style="zoom: 50%;" />

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

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

真實主題角色(RealSubject):該類也被稱為被代理類,該類定義了代理所表示的真實對象羹应,是負責執(zhí)行系統(tǒng)真正的邏輯業(yè)務對象揽碘;

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

在代碼中供汛,一般代理會被理解為代碼增強枪汪,實際上就是在原代碼邏輯前后增加一些代碼邏輯,而使調用者無感知怔昨。代理模式屬于結構型模式雀久,分為靜態(tài)代理動態(tài)代理

7.4.代理模式的應用場景

生活中的租房中介趁舀、售票黃牛赖捌、婚介、經紀人矮烹、快遞越庇、事務代理、非侵入式日志監(jiān)聽等奉狈,都是代理
模式的實際體現(xiàn)卤唉。當無法或不想直接引用某個對象或訪問某個對象存在困難時,可以通過也給代理對象
來間接訪問嘹吨。使用代理模式主要有兩個目的:一是保護目標對象搬味,二是增強目標對象。

7.5.代理模式的通用寫法

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

首先創(chuàng)建代理主題角色ISubject類:

public interface ISubject {
    void request();
}

創(chuàng)建真實主題角色RealSubject類:

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

創(chuàng)建代理主題角色Proxy類:

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().");
    }
}

客戶端調用代碼:

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

運行結果

called before request().
real service is called.
called after request().

7.6.從靜態(tài)代理到動態(tài)代理

舉個例子碰纬,有些人到了適婚年齡,其父母總是迫不及待地希望早點抱孫子问芬。而現(xiàn)在在各種壓力之下悦析,
很多人都選擇晚婚晚育。于是著急的父母就開始到處為自己的子女相親此衅,比子女自己還著急强戴。下面來看代碼實現(xiàn)亭螟。

靜態(tài)代理:

創(chuàng)建頂層接口IPerson的代碼如下:

public interface IPerson {
    void findLove();
}

兒子張三要找對象,實現(xiàn)ZhangSan類:

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

父親張老三要幫兒子張三相親骑歹,實現(xiàn)Father類:

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("開始交往");
    }
}

來看測試代碼:

public class Test {
    public static void main(String[] args) {
        ZhangLaosan zhangLaosan = new ZhangLaosan(new ZhangSan());
        zhangLaosan.findLove();
    }
}

運行結果:

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

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

但社會上這項業(yè)務發(fā)展成了一個產業(yè)扁掸,出現(xiàn)了媒婆、婚介所等,還有各種各樣的定制套餐。如果還使用靜態(tài)代理成本就太高了箫柳,需要一個更加通用的解決方案,滿足任何單身人士找對象的需求牺蹄。

這就是由靜態(tài)代理升級到了動態(tài)代理。

采用動態(tài)代理基本上只要是人(IPerson)就可以提供相親服務薄翅。

動態(tài)代理的底層實現(xiàn)一般不用我們自己親自去實現(xiàn)沙兰,已經有很多現(xiàn)成的API。

在Java生態(tài)中匿刮,目前最普遍使用的是JDK自帶的代理和Cglib提供的類庫僧凰。

下面我們首先基于JDK的動態(tài)代理支持如來升級一下代碼探颈。

首先熟丸,創(chuàng)建媒婆(婚介所)類JdkMeipo:

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("我是媒婆伪节,已經收集到你的需求光羞,開始物色");
    }
}

再創(chuàng)建一個類ZhaoLiu:

public class ZhaoLiu implements IPerson {

    public void findLove() {
        System.out.println("趙六要求:有車有房學歷高");
    }

    public void buyInsure() {
    }
}

測試代碼如下:

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

運行效果如下:

我是媒婆,已經收集到你的需求怀大,開始物色
張三要求:膚白貌美大長腿
雙方同意纱兑,開始交往
我是媒婆,已經收集到你的需求化借,開始物色
趙六要求:有車有房學歷高
雙方同意潜慎,開始交往

7.7.靜態(tài)模式在業(yè)務中的應用

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

在分布式業(yè)務場景中铐炫,通常會對數(shù)據(jù)庫進行分庫分表,分庫分表之后使用 Java操作時就可能需要配置多個數(shù)據(jù)源蒜焊,我們通過設置數(shù)據(jù)源路由來動態(tài)切換數(shù)據(jù)源倒信。

先創(chuàng)建Order訂單類:

@Data
public class Order {
    private Object orderInfo;
    //訂單創(chuàng)建時間進行按年分庫
    private Long createTime;
    private String id;
}

創(chuàng)建OrderDao持久層操作類:

public class OrderDao {
    public int insert(Order order){
        System.out.println("OrderDao創(chuàng)建Order成功!");
        return 1;
    }
}

創(chuàng)建IOrderService接口:

public interface IOrderService {
    int createOrder(Order order);
}

創(chuàng)建OrderService實現(xiàn)類:

public class OrderService implements IOrderService {
    private OrderDao orderDao;

    public OrderService(){
        //如果使用Spring應該是自動注入的
        //我們?yōu)榱耸褂梅奖悖跇嬙旆椒ㄖ袑rderDao直接初始化了
        orderDao = new OrderDao();
    }

    public int createOrder(Order order) {
        System.out.println("OrderService調用orderDao創(chuàng)建訂單");
        return orderDao.insert(order);
    }
}

接下來使用靜態(tài)代理泳梆,主要完成的功能是:根據(jù)訂單創(chuàng)建時間自動按年進行分庫鳖悠。

根據(jù)開閉原則榜掌,我們修改原來寫好的代碼邏輯,通過代理對象來完成乘综。

先創(chuàng)建數(shù)據(jù)源路由對象憎账,使用ThreadLocal的單例實現(xiàn)DynamicDataSourceEntity類:

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

創(chuàng)建切換數(shù)據(jù)源的代理類OrderServiceSaticProxy:

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("靜態(tài)代理類自動分配到【DB_" +  dbRouter + "】數(shù)據(jù)源處理數(shù)據(jù)" );
        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."); }
}

來看測試代碼:

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

運行結果如下:

Proxy before method.
靜態(tài)代理類自動分配到【DB_2020】數(shù)據(jù)源處理數(shù)據(jù)
OrderService調用orderDao創(chuàng)建訂單
OrderDao創(chuàng)建Order成功!
Proxy after method.

結果符合我們的預期。現(xiàn)在再來回顧一下類圖卡辰,看是不是和我們最先畫的一致鼠哥,如下圖所示。

<img src="https://gitee.com/woshiamiaojiang/image-hosting/raw/master/image-20200303131528511.png" alt="image-20200303131528511" style="zoom: 50%;" />

動態(tài)代理和靜態(tài)代理的基本思路是一致的看政,只不過動態(tài)代理功能更加強大朴恳,隨著業(yè)務的擴展適應性更強。

7.8.動態(tài)代理在業(yè)務中的應用

上面的案例理解了允蚣,我們再來看數(shù)據(jù)源動態(tài)路由業(yè)務于颖,幫助“小伙伴們”加深對動態(tài)代理的印象。

創(chuàng)建動態(tài)代理的類OrderServiceDynamicProxy:

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("動態(tài)代理類自動分配到【DB_" + dbRouter + "】數(shù)據(jù)源處理數(shù)據(jù)");
            DynamicDataSourceEntity.set(dbRouter);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void after() {
        System.out.println("Proxy after method.");
    }
}

測試代碼如下:

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

運行效果如下:

Proxy before method.
靜態(tài)代理類自動分配到【DB_2020】數(shù)據(jù)源處理數(shù)據(jù)
OrderService調用orderDao創(chuàng)建訂單
OrderDao創(chuàng)建Order成功!
Proxy after method.

依然能夠達到相同運行效果嚷兔。但是森渐,使用動態(tài)代理實現(xiàn)之后,我們不僅能實現(xiàn) Order的數(shù)據(jù)源動態(tài)
路由冒晰,還可以實現(xiàn)其他任何類的數(shù)據(jù)源路由同衣。當然,有個比較重要的約定壶运,必須實現(xiàn)getCreateTime()
方法耐齐,因為路由規(guī)則是根據(jù)時間來運算的。我們可以通過接口規(guī)范來達到約束的目的蒋情,在此就不再舉例埠况。

7.9.手寫JDK動態(tài)代理實現(xiàn)原理

不僅知其然,還得知其所以然棵癣。既然JDK動態(tài)代理功能如此強大辕翰,那么它是如何實現(xiàn)的呢?我們現(xiàn)
在來探究一下原理狈谊,并模仿JDK動態(tài)代理動手寫一個屬于自己的動態(tài)代理喜命。

我們都知道JDK動態(tài)代理采用字節(jié)重組,重新生成對象來替代原始對象河劝,以達到動態(tài)代理的目的壁榕。

JDK動態(tài)代理的實現(xiàn)原理

  1. 獲取被代理對象的引用,并且獲取它的所有接口(反射獲壬ゲ谩)护桦。

  2. JDK Proxy類重新生成一個新的類,實現(xiàn)了被代理類所有接口的方法煎娇。

  3. 動態(tài)生成Java代碼二庵,把增強邏輯加入到新生成代碼中贪染。

  4. 編譯生成新的Java代碼的class文件。

  5. 加載并重新運行新的class催享,得到類就是全新類杭隙。

CGLib動態(tài)代理容易踩的坑

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

以上過程就叫字節(jié)碼重組因妙。JDK中有一個規(guī)范痰憎,在ClassPath下只要是$開頭的.class文件,一般都是自動生成的攀涵。那么我們有沒有辦法看到代替后的對象的“真容”呢铣耘?做一個這樣測試,我們將內存中
的對象字節(jié)碼通過文件流輸出到一個新的.class文件以故,然后利用反編譯工具查看其源代碼蜗细。

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

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

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

我們發(fā)現(xiàn)昆烁,$Proxy0繼承了Proxy類吊骤,同時還實現(xiàn)了Person接口,而且重寫了findLove()等方法静尼。
在靜態(tài)塊中用反射查找到了目標對象的所有方法白粉,而且保存了所有方法的引用,重寫的方法用反射調用
目標對象的方法茅郎∥显“小伙伴們”此時一定會好奇:這些代碼是哪里來的呢或渤?其實是JDK幫我們自動生成
的∠等撸現(xiàn)在我們不依賴JDK,自己來動態(tài)生成源代碼薪鹦、動態(tài)完成編譯掌敬,然后替代目標對象并執(zhí)行。

創(chuàng)建GPInvocationHandler接口:

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

創(chuàng)建GPProxy類:

/**
 * 用來生成源代碼的工具類
 */
public class GPProxy {

    public static final String ln = "\r\n";

    public static Object newProxyInstance(GPClassLoader classLoader, Class<?> [] interfaces, GPInvocationHandler h){
       try {
           //1池磁、動態(tài)生成源代碼.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华临、返回字節(jié)碼重組以后的新的代理對象
           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,Class> mappings = new HashMap<Class, 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);
    }

}

創(chuàng)建GPClassLoader類:

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

創(chuàng)建GPMeipo類:

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("我是媒婆端考,已經收集到你的需求雅潭,開始物色");
    }
}

客戶端測試代碼如下:

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

運行效果如下:

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

到此筛圆,手寫JDK動態(tài)代理就完成了〈慌ǎ“小伙伴們”是不是又多了一個面試用的“撒手锏”呢太援?

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

簡單看一下CGLib代理的使用,還是以媒婆為例扳碍,創(chuàng)建CglibMeipo類:

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("我是媒婆,我要給你找對象笋敞,現(xiàn)在已經確認你的需求");
        System.out.println("開始物色");
    }

    private void after(){
        System.out.println("OK的話唧垦,準備辦事");
    }
}

創(chuàng)建單身客戶類Customer:

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

有個小細節(jié),CGLib代理的目標對象不需要實現(xiàn)任何接口液样,它是通過動態(tài)繼承目標對象實現(xiàn)動態(tài)代
理的振亮。來看測試代碼:

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

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

public class CglibTest {
    public static void main(String[] args) {
        try {
            //JDK是采用讀取接口的信息
            //CGLib覆蓋父類方法
            //目的:都是生成一個新的類澎怒,去實現(xiàn)增強代碼邏輯的功能

            //JDK Proxy 對于用戶而言褒搔,必須要有一個接口實現(xiàn),目標類相對來說復雜
            //CGLib 可以代理任意一個普通的類喷面,沒有任何要求

            //CGLib 生成代理邏輯更復雜星瘾,效率,調用效率更高,生成一個包含了所有的邏輯的FastClass惧辈,不再需要反射調用
            //JDK Proxy生成代理的邏輯簡單琳状,執(zhí)行效率相對要低,每次都要反射動態(tài)調用

            //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();
        }
    }
}

重新執(zhí)行代碼念逞,我們會發(fā)現(xiàn)在E://cglib_proxy_class目錄下多了三個.class文件,如下圖所示边翁。

image-20200303213605773

通過調試跟蹤發(fā)現(xiàn)翎承,Customer$$EnhancerByCGLIB$$3feeb52a.class 就是 CGLib 代理生成的代
理類,繼承了Customer類符匾。

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();
        }
    }
    ...
}

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

//代理方法(methodProxy.invokeSuper()方法會調用) 
final void CGLIB$findLove$0() {
    super.findLove();
}

//被代理方法(methodProxy.invoke()方法會調用痴晦,這就是為什么在攔截器中調用 methodProxy.invoke 會發(fā)生死循環(huán)南吮,一直在調用攔截器) 
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();
    }
}

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

此時誊酌,我們發(fā)現(xiàn)攔截器 MethodInterceptor中就是由 MethodProxy的invokeSuper()方法調用代
理方法的部凑,MethodProxy非常關鍵,我們分析一下它具體做了什么碧浊。

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

        }
    }
    ...
}

繼續(xù)看invokeSuper()方法:

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

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

CGLib代理執(zhí)行代理方法的效率之所以比JDK的高驹止,是因為CGlib采用了FastClass機制浩聋,它的原
理簡單來說就是:為代理類和被代理類各生成一個類,這個類會為代理類或被代理類的方法分配一個
index(int類型)臊恋;這個index當作一個入參衣洁,F(xiàn)astClass就可以直接定位要調用的方法并直接進行調
用,省去了反射調用抖仅,所以調用效率比JDK代理通過反射調用高坊夫。下面我們反編譯一個FastClass看看:

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

CGLib代理執(zhí)行代理方法的效率之所以比JDK的高,是因為CGlib采用了FastClass機制撤卢,它的原理簡單來說就是:為代理類和被代理類各生成一個類环凿,這個類會為代理類或被代理類的方法分配一個
index(int類型);這個index當作一個入參放吩,F(xiàn)astClass就可以直接定位要調用的方法并直接進行調
用智听,省去了反射調用,所以調用效率比JDK代理通過反射調用高屎慢。下面我們反編譯一個FastClass看看:

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

    //部分代碼省略
    //根據(jù) index 直接定位執(zhí)行方法
    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");
    }
}

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

//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;
            }
        }
    }
}

至此,CGLib代理的原理我們就基本搞清楚了欲虚,對代碼細節(jié)有興趣的“小伙伴”可以自行深入研究集灌。

7.11.CGLib和JDK動態(tài)代理對比

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

(2) JDK動態(tài)代理和CGLib代理都在運行期生成字節(jié)碼欣喧, JDK動態(tài)代理直接寫Class字節(jié)碼腌零, CGLib
代理使用ASM框架寫Class字節(jié)碼,CGlib代理實現(xiàn)更復雜唆阿,生成代理類比JDK動態(tài)代理效率低益涧。

(3)JDK動態(tài)代理調用代理方法是通過反射機制調用的,CGLib代理是通過FastClass機制直接調用方法的驯鳖,CGLib代理的執(zhí)行效率更高闲询。

7.12.代理模式與Spring生態(tài)

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

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

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

在getObject()方法中扭弧,主要調用 getSingletonInstance()和 newPrototypeInstance()。在 Spring
的配置中如果不做任何設置记舆,那么 Spring 代理生成的 Bean 都是單例對象鸽捻。如果修改 scope,則每次
創(chuàng)建一個新的原型對象泽腮。newPrototypeInstance()里面的邏輯比較復雜御蒲,我們后面再做深入研究,這里
先做簡單了解诊赊。

Spring 利用動態(tài)代理實現(xiàn) AOP 時有兩個非常重要的類:JdkDynamicAopProxy 類和CglibAopProxy類删咱,來看一下類圖,如下圖所示豪筝。

<img src="https://gitee.com/woshiamiaojiang/image-hosting/raw/master/image-20200303222109133.png" alt="image-20200303222109133" style="zoom:50%;" />

7.13.靜態(tài)代理和動態(tài)代理的本質區(qū)別

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

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

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

7.14.代理模式的優(yōu)缺點

代理模式具有以下優(yōu)點:

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

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

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

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

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

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

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

(3)增加了系統(tǒng)的復雜度。

7.15.Spring中的代理選擇原則

(1)當Bean有實現(xiàn)接口時察署,Spring就會用JDK動態(tài)代理闷游。

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

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

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

7.16.作業(yè)

1、請總結靜態(tài)代理和動態(tài)代理的根本區(qū)別嘶是。

靜態(tài)代理是硬編碼钙勃,動態(tài)代理是動態(tài)生成。

2聂喇、繼續(xù)完成手寫Proxy類中帶參數(shù)方法的代理實現(xiàn)辖源。

Zhangsan添加一個新方法

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

生成字節(jié)碼

    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());
        }
    }
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市希太,隨后出現(xiàn)的幾起案子克饶,更是在濱河造成了極大的恐慌,老刑警劉巖誊辉,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矾湃,死亡現(xiàn)場離奇詭異,居然都是意外死亡堕澄,警方通過查閱死者的電腦和手機邀跃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛙紫,“玉大人拍屑,你說我怎么就攤上這事】痈担” “怎么了僵驰?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長唁毒。 經常有香客問我蒜茴,道長,這世上最難降的妖魔是什么浆西? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任粉私,我火速辦了婚禮,結果婚禮上室谚,老公的妹妹穿的比我還像新娘毡鉴。我一直安慰自己崔泵,他們只是感情好秒赤,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布猪瞬。 她就那樣靜靜地躺著,像睡著了一般入篮。 火紅的嫁衣襯著肌膚如雪陈瘦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天潮售,我揣著相機與錄音痊项,去河邊找鬼。 笑死酥诽,一個胖子當著我的面吹牛鞍泉,可吹牛的內容都是我干的。 我是一名探鬼主播肮帐,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼咖驮,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了训枢?” 一聲冷哼從身側響起托修,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎恒界,沒想到半個月后睦刃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡十酣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年涩拙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耸采。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡兴泥,死狀恐怖,靈堂內的尸體忽然破棺而出洋幻,到底是詐尸還是另有隱情郁轻,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布文留,位于F島的核電站好唯,受9級特大地震影響,放射性物質發(fā)生泄漏燥翅。R本人自食惡果不足惜骑篙,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望森书。 院中可真熱鬧靶端,春花似錦谎势、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至台谍,卻和暖如春须喂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坞生。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留掷伙,地道東北人是己。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像任柜,于是被迫代替她去往敵國和親卒废。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內容