JDK動態(tài)代理和CGLIB動態(tài)代理淺析

代理模式淺析

1.什么是代理模式

代理模式.png

什么是代理模式呢码俩,首先舉個簡單的例子够傍,張三(用戶)想要一張演唱會的門票但是自己沒有渠道購買拂苹,這時他的朋友李四(代理人)說他能夠買到演唱會的門票(被代理方法)安聘,張三只需要請李四幫忙購買門票即可,至于李四是怎么買的通過什么方式買的張三并不需要知道瓢棒。代理模式的定義:為其他對象提供一種代理以控制對這個對象的訪問浴韭。在某些情況下,一個對象不適合或者不能直接引用另一個對象脯宿,而代理對象可以在客戶端和目標對象之間起到中介的作用念颈。使用代理模式主要有兩個目的:一是保護目標對象窟感,而是增強目標對象蜜自。

代理模式類圖

Snipaste_2021-06-18_15-42-05.png

Subject 是頂層接口,RealSubject 是真實對象(被代理對象)暮蹂,Proxy 是代理對象棠枉,代理對象持有被代理對象的引用,客戶端調(diào)用代理對象方法,同時也調(diào)用被代理對象的方法钦无,但是在代理對象前后增加一些處理。在代碼中索昂,我們想到代理秉宿,就會理解為是代碼增強韵丑,其實就是在原本邏輯前后增加一些邏輯轴合,而調(diào)用者無感知。代理模式屬于結(jié)構(gòu)型模式鸯隅,有靜態(tài)代理和動態(tài)代理蝌以。

2.靜態(tài)代理

什么是靜態(tài)代理虱痕,其實這個動靜區(qū)別是對于程序來說的蛔翅,所謂靜態(tài)也就是在程序運行之前就已經(jīng)存在代理類的字節(jié)碼文件仅讽,代理類和委托類的關系在運行前就已經(jīng)確定了陶缺。下面用代碼寫一個簡單的靜態(tài)代理示例。

public class Order {
    private Object orderInfo;
    private Long createTime;
    private String id;

    public Object getOrderInfo() {
        return orderInfo;
    }

    public void setOrderInfo(Object orderInfo) {
        this.orderInfo = orderInfo;
    }

    public Long getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Long createTime) {
        this.createTime = createTime;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}
/**
 * @author: Winston
 * @createTime: 2021/6/15
 */
public class OrderDao {

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

public interface IOrderService {
    int createOrder(Order order);
}
public class OrderService implements IOrderService {
    private OrderDao orderDao;

    public OrderService() {
        //如果使用 Spring 應該是自動注入的
        //我們?yōu)榱耸褂梅奖憬嗔椋跇?gòu)造方法中將 orderDao 直接初始化了
        orderDao = new OrderDao();
    }

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

有個IOrderService接口饱岸,里面有個創(chuàng)建訂單的方法createOrder,OrderService實現(xiàn)了IOrderService接口徽千。假設現(xiàn)在在分布式環(huán)境中苫费,需要對數(shù)據(jù)庫進行分庫分表,分庫分表進行完之后使用JAVA操作時双抽,就需要配置多個數(shù)據(jù)源百框,我們通過設置數(shù)據(jù)源路由來動態(tài)切換數(shù)據(jù)源。根據(jù)開閉原則牍汹,已經(jīng)寫好的邏輯不改動铐维,通過代理對象的方式進行修改,我們主要完成的功能是根據(jù)訂單創(chuàng)建時間自動按年進行分庫慎菲。通過ThreadLocal編寫DynamicDataSourceEntry(被代理類)動態(tài)切換數(shù)據(jù)源的類嫁蛇。

/**
 * @author: Winston
 * @createTime: 2021/6/15
 * 動態(tài)切換數(shù)據(jù)源
 */
public class DynamicDataSourceEntry {
    // 默認數(shù)據(jù)源
    public final static String DEFAULT_SOURCE = null;
    private final static ThreadLocal<String> local = new ThreadLocal<String>();

    private DynamicDataSourceEntry(){}

    /**
     * 清空數(shù)據(jù)源
     */
    public static void clear(){
        local.remove();
    }

    /**
     * 獲取當前正在使用的數(shù)據(jù)源名稱
     * @return
     */
    public static String get(){
       return local.get();
    }

    /**
     *還原當前切面的數(shù)據(jù)源
     */
    public static void restore(){
        local.set(DEFAULT_SOURCE);
    }

    /**
     * 設置已知名字的數(shù)據(jù)源
     * @param source
     */
    public static void set(String source){
        local.set(source);
    }

    /**
     * 根據(jù)年份動態(tài)設置數(shù)據(jù)源
     * @param year
     */
    public static void set(int year) {
        local.set("DB_" + year);
    }
}

接下來是編寫代理類實現(xiàn)IOrderService接口,通過這個代理類調(diào)用被代理類同時完成對被代理類的方法調(diào)用钧嘶,在已有功能的基礎上棠众,不將業(yè)務代碼修改,完成數(shù)據(jù)源切換的工作有决。

/**
 * @author: Winston
 * @createTime: 2021/6/15
 */
public class OrderServiceStaticProxy implements IOrderService {
    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
    private IOrderService orderService;

    public OrderServiceStaticProxy(IOrderService orderService){
        this.orderService = orderService;
    }

    @Override
    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ù)闸拿。");
        DynamicDataSourceEntry.set(dbRouter);
        orderService.createOrder(order);
        after();
        return 0;
    }

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

測試代碼

/**
 * @author: Winston
 * @createTime: 2021/6/15
 */
public class StaticProxyTest {
    public static void main(String[] args) throws ParseException {
        Order order = new Order();
// Date today = new Date();
// order.setCreateTime(today.getTime());
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        Date date = sdf.parse("2017/02/01");
        order.setCreateTime(date.getTime());
        IOrderService orderService = new OrderServiceStaticProxy(new OrderService());
        orderService.createOrder(order);

    }
}

運行結(jié)果

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

靜態(tài)代理小結(jié)

優(yōu)點:不需要修改業(yè)務類的代碼书幕,業(yè)務類只需要關注業(yè)務本身新荤,這是代理共有的優(yōu)點

缺點:

  1. 代理對象的一個接口只服務于一種類型的對象,如果要代理的方法很多台汇,勢必要為每一種方法都進行代理苛骨,比如要給所有方法都加上打印日志這一操作篱瞎,那么需要對所有方法都要進行代理,因此靜態(tài)代理在程序規(guī)模稍大時就無法勝任了痒芝。
  2. 如果接口新增一個方法俐筋,除了所有實現(xiàn)類要實現(xiàn)該方法外,所有代理類也要實現(xiàn)該方法严衬,增加了代碼維護的難度澄者。

2.動態(tài)代理

動態(tài)代理類的源碼是在程序運行期間由JVM根據(jù)反射等機制動態(tài)生成的,所以不存在代理類的字節(jié)碼文件请琳。代理類和委托類的關系是在程序運行時確定粱挡。如果以找對象為例,父親給兒子找對象俄精,父親就好比是靜態(tài)代理询筏,只為兒子找對象,不管別人是否要找對象竖慧,而使用動態(tài)代理能夠適應復雜的業(yè)務場景嫌套,媒婆就好比是動態(tài)代理,能夠為任何單身人士尋找對象不僅限于兒子测蘑。同樣我們用代碼寫一個動態(tài)代理的示例.

JDK動態(tài)代理

/**
 * @author: Winston
 * @createTime: 2021/6/16
 */
public interface Person {

    /**
     * 相親方法
     */
    public void findLove();
}

/**
 * @author: Winston
 * @createTime: 2021/6/16
 */
public class Girl implements  Person{

    /**
     * 找對象要求
     */
    @Override
    public void findLove() {
        System.out.println("高富帥");
        System.out.println("身高180cm");
        System.out.println("體重73kg");
        System.out.println("家里兩套房");
    }
}
/**
 * @author: Winston
 * @createTime: 2021/6/16
 */
public class JDKMeipo implements InvocationHandler {

    /**
     * 被代理對象
     */
    private Object target;

    public Object getInstance(Object target){
        this.target = target;
        // 獲取class信息
        Class clazz = target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }

    /**
     * 前置方法
     */
    public void before(){
        System.out.println("我是媒婆灌危,現(xiàn)在準備開始為你尋找條件合適的相親對象");
    }

    /**
     * 后置方法
     */
    public void after(){
        System.out.println("條件合適的話就準備嘿嘿嘿");
    }
}

測試方法

/**
 * @author: Winston
 * @createTime: 2021/6/16
 */
public class JdkDynamicProxyTest {
    public static void main(String[] args) {

        Person person = (Person) new JDKMeipo().getInstance(new Girl());
        person.findLove();
    }
}

輸出結(jié)果

我是媒婆,現(xiàn)在準備開始為你尋找條件合適的相親對象
高富帥
身高180cm
體重73kg
家里兩套房
條件合適的話就準備嘿嘿嘿

通過結(jié)果我們可以看到碳胳,調(diào)用了前置方法勇蝙、findLove方法、后置方法挨约,但是JDKMeipo中沒有findLove方法味混,person中沒有前置后置方法,那么是哪個類調(diào)用的呢诫惭?

Snipaste_2021-06-18_17-06-24.png

通過debug模式翁锡,我們可以看到是一個全新的類$Proxy0,我們生成這個類的字節(jié)碼文件夕土,同時使用jad將它反編譯看看寫了什么馆衔。

/**
 * @author: Winston
 * @createTime: 2021/6/16
 */
public class JdkDynamicProxyTest {
    public static void main(String[] args) {

        Person person = (Person) new JDKMeipo().getInstance(new Girl());
        person.findLove();

        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
        try {
            FileOutputStream fileOutputStream = new FileOutputStream("H://$Proxy0.class");
            fileOutputStream.write(bytes);
            fileOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }
}

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 

import com.gupaoedu.vip.pattern.proxy.service.Person;
import java.lang.reflect.*;

public final class $Proxy0 extends Proxy
    implements Person
{

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

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

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

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

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

    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    static 
    {
        try
        {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
                Class.forName("java.lang.Object")
            });
            m3 = Class.forName("com.gupaoedu.vip.pattern.proxy.service.Person").getMethod("findLove", 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 nosuchmethodexception)
        {
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());
        }
        catch(ClassNotFoundException classnotfoundexception)
        {
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());
        }
    }
}

通過看 proxy0反編譯后的代碼我們可以看到,有m1,m3,m2,m0四個方法怨绣,這四個方法是通過反射掃描出來的角溃,proxy0繼承了Proxy類同時實現(xiàn)了Person接口,而m3就是Person類的findLove方法篮撑,同時$Proxy0又實現(xiàn)了findLove方法减细,我們看代碼。

  public final void findLove()
    {
        try
        {
            super.h.invoke(this, m3, null);
            return;
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(thr  owable);
        }
    }

findLove方法調(diào)用了super.h.invoke(this, m3, null);赢笨,而這個h是Proxy中的InvocationHandler接口未蝌,由于JDKMeipo實現(xiàn)了InvocationHandler接口驮吱,因此,super.h.invoke(this, m3, null);實際上調(diào)用的就是JDKMeipo的invoke方法萧吠。

JDK動態(tài)代理小結(jié)

實際上JDK動態(tài)代理是采用字節(jié)重組左冬,重新生成對象來代替原始對象以達到動態(tài)代理的目的。JDK Proxy生成對象的步驟如下以媒婆為示例纸型。

  1. 拿到被代理對象(Girl)的引用(Person)又碌,并且獲取到它所有的接口(本例中的findLove方法),反射獲取绊袋。(這也是為什么JDK動態(tài)代理需要代理類和被代理類都要事先同一接口的原因)
  2. JDK Proxy類重新生成一個新的類,這個新的類需要實現(xiàn)被代理類(Girl)的所有實現(xiàn)的所有接口(findLove)
  3. 動態(tài)生成java代碼铸鹰,把新加的業(yè)務邏輯方法由一定的邏輯代碼去調(diào)用(在代碼中體現(xiàn))
  4. 編譯新生成的java代碼.class
  5. 再重新加載到JVM中運行

以上這個過程就叫字節(jié)碼重組癌别。JDK中有一個規(guī)范,在ClassPath下只要是$開頭的class文件一般都是自動生成的蹋笼。

動態(tài)切換數(shù)據(jù)源案例改造

前面動態(tài)數(shù)據(jù)源切換案例我們使用的是使用靜態(tài)代理實現(xiàn)的展姐,我們現(xiàn)在將他改造成動態(tài)代理。


/**
 * @Author winston
 * @Date 2021/6/20
 * 將訂單數(shù)據(jù)源切換案例由靜態(tài)代理改成JDK動態(tài)代理
 */
public class JDKOrderProxy implements InvocationHandler {
    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");

    /**
     * 被代理對象
     */
    private Object proxyObj;

    public Object getInstance(Object proxyObj) {
        // 賦值被代理對象
        this.proxyObj = proxyObj;
        // 獲取class信息
        Class<?> clazz = proxyObj.getClass();
        // 返回對象
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before(args[0]);
        Object result = method.invoke(proxyObj, args);
        after();
        return result;
    }

    /**
     * 切換數(shù)據(jù)源
     *
     * @param target 應該是訂單對象
     */
    private void before(Object target) {
        try {
            System.out.println("切換數(shù)據(jù)源");
            // 約定優(yōu)于配置剖毯,所以我們約定存在getCreateTime方法圾笨,通過反射得到
            Method method = target.getClass().getMethod("getCreateTime");
            Long time = (Long) method.invoke(target);
            Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
            System.out.println("靜態(tài)代理類自動分配到【DB_" + dbRouter + "】數(shù)據(jù)源處理數(shù)據(jù)。");
            DynamicDataSourceEntry.set(dbRouter);
//            orderService.createOrder(order);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 重置數(shù)據(jù)源
     *
     */
    private void after() {
        System.out.println("重置數(shù)據(jù)源");
        DynamicDataSourceEntry.restore();
    }
}

測試代碼

/**
 * @author: Winston
 * @createTime: 2021/6/16
 */
public class JdkDynamicProxyOrderTest {
    public static void main(String[] args) throws ParseException {
        Order order = new Order();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        Date date = sdf.parse("2018/02/01");
        order.setCreateTime(date.getTime());
        IOrderService orderService = (IOrderService) new JDKOrderProxy().getInstance(new OrderService());
        orderService.createOrder(order);
    }
}

CGLIB動態(tài)代理

參考博客:CGLIB動態(tài)代理詳解

CGLIB動態(tài)代理的結(jié)構(gòu)圖如下逊谋,我們可以看出代理類去繼承目標類擂达,每次調(diào)用代理類的方法都會被方法攔截器攔截,在攔截器中才是調(diào)用目標類的該方法的邏輯胶滋。

cglib動態(tài)代理結(jié)構(gòu)圖.png

首先我們通過代碼來感受一下CGLIB動態(tài)代理板鬓,同樣我們以媒婆案例進行改造。

/**
 * @author: Winston
 * @createTime: 2021/6/22
 */
public class CglibMeipoProxy implements MethodInterceptor {

    /**
     * 獲取代理對象究恤,這里的參數(shù)是Class類型
     * 為啥是class類型俭令,因為這里接收的參數(shù)是父類的class,我們需要繼承這個父類
     * 重寫方法生成新的類
     * @param clazz
     * @return
     */
    public Object getInstance(Class clazz) {
        //創(chuàng)建Enhancer對象部宿,類似于JDK動態(tài)代理的Proxy類抄腔,下一步就是設置幾個參數(shù)
        Enhancer enhancer = new Enhancer();
        //設置目標類
        enhancer.setSuperclass(clazz);
        // 設置攔截器,也就是這個類(CglibMeipoProxy)的intercept方法
        enhancer.setCallback(this);
        // 生成代理類并返回一個實例
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        // 調(diào)用被代理方法理张,注意這里是方法調(diào)用并不是用反射
        Object result = methodProxy.invokeSuper(o, objects);
        after();
        return result;
    }


    /**
     * 前置方法
     */
    public void before(){
        System.out.println("我是媒婆赫蛇,現(xiàn)在準備開始為你尋找條件合適的相親對象");
    }

    /**
     * 后置方法
     */
    public void after(){
        System.out.println("條件合適的話就準備嘿嘿嘿");
    }
}

測試類

/**
 * @author: Winston
 * @createTime: 2021/6/22
 */
public class CglibMeipoProxyTest {
    public static void main(String[] args) {
        //利用 cglib 的代理類可以將內(nèi)存中的 class 文件寫入本地磁盤
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"H://cglib_proxy_class");
        Boy boy = (Boy) new CglibMeipoProxy().getInstance(Boy.class);
        boy.findLove();
    }

}

運行測試類后我們到指定目錄下看一下,發(fā)現(xiàn)生成有三個字節(jié)碼文件涯穷,一個是代理類的FastClass(Boy$$EnhancerByCGLIB$$71710d07$$FastClassByCGLIB$$e2a848ac.class)棍掐,一個是代理類(Boy$$EnhancerByCGLIB$$71710d07.class),一個是目標類的FastClass(Boy$$FastClassByCGLIB$$364eb7e9.class)拷况,這里簡單說一下什么是FastClass作煌,就是給每個方法編號掘殴,通過編號找到方法,這樣可以避免頻繁使用反射導致效率比較低粟誓。

cglib動態(tài)代理字節(jié)碼目錄.png

我們通過使用jad工具將代理類的class文件反編譯成java文件奏寨,我們來簡單看一下文件內(nèi)容。我們可以看到對于findLove方法鹰服,在這個代理類中對應會有findLoveCGLIB$findLove$0這兩個方法病瞳;其中前者則是我們使用代理類時候調(diào)用的方法,后者是在方法攔截器里面調(diào)用的悲酷,換句話來說當我們代碼調(diào)用代理對象的findLove方法套菜,然后會到方法攔截器中調(diào)用intercept方法,該方法內(nèi)則通過proxy.invokeSuper調(diào)用CGLIB$findLove$0這個方法设易。

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   <generated>

package com.gupaoedu.vip.pattern.proxy.service;

import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.*;

// Referenced classes of package com.gupaoedu.vip.pattern.proxy.service:
//            Boy

// 這個代理類是繼承我們的目標類Boy逗柴,并且實現(xiàn)了Factory接口,這個接口就是一些設置回調(diào)函數(shù)和返回實例化對象的方法
public class Boy$$EnhancerByCGLIB$$71710d07 extends Boy
    implements Factory
{
    static void CGLIB$STATICHOOK1()
    {
        //注意下面這兩個Method數(shù)組顿肺,用于保存反射獲取的Method對象戏溺,避免每次都用反射去獲取Method對象
        Method amethod[];
        Method amethod1[];
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        //獲取目標類的字節(jié)碼文件
        Class class1 = Class.forName("com.gupaoedu.vip.pattern.proxy.service.Boy$$EnhancerByCGLIB$$71710d07");
        //代理類的字節(jié)碼文件
        Class class2;
        //ReflectUtils是一個包裝各種反射操作的工具類,通過這個工具類來獲取各個方法的Method對象屠尊,然后保存到上述的Method數(shù)組中
        amethod = ReflectUtils.findMethods(new String[] {
            "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"
        }, (class2 = Class.forName("java.lang.Object")).getDeclaredMethods());
        Method[] _tmp = amethod;
        //為目標類的每一個方法都建立索引旷祸,可以想象成記錄下來目標類中所有方法的地址,需要用調(diào)用目標類方法的時候根據(jù)地址就能直接找到該方法
        //這就是此處CGLIB$xxxxxx$$Proxy的作用讼昆。托享。。
        CGLIB$equals$1$Method = amethod[0];
        CGLIB$equals$1$Proxy = MethodProxy.create(class2, class1, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
        CGLIB$toString$2$Method = amethod[1];
        CGLIB$toString$2$Proxy = MethodProxy.create(class2, class1, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
        CGLIB$hashCode$3$Method = amethod[2];
        CGLIB$hashCode$3$Proxy = MethodProxy.create(class2, class1, "()I", "hashCode", "CGLIB$hashCode$3");
        CGLIB$clone$4$Method = amethod[3];
        CGLIB$clone$4$Proxy = MethodProxy.create(class2, class1, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
        amethod1 = ReflectUtils.findMethods(new String[] {
            "findLove", "()V"
        }, (class2 = Class.forName("com.gupaoedu.vip.pattern.proxy.service.Boy")).getDeclaredMethods());
        Method[] _tmp1 = amethod1;
        CGLIB$findLove$0$Method = amethod1[0];
        CGLIB$findLove$0$Proxy = MethodProxy.create(class2, class1, "()V", "findLove", "CGLIB$findLove$0");
    }
    //這個方法就是調(diào)用目標類的的findLove方法
    final void CGLIB$findLove$0()
    {
        super.findLove();
    }

    //這個方法是我們是我們要調(diào)用的浸赫,在前面的例子中調(diào)用代理對象的findLove方法就會到這個方法中
    public final void findLove()
    {
        CGLIB$CALLBACK_0;
        if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
        JVM INSTR pop ;
        CGLIB$BIND_CALLBACKS(this);
        CGLIB$CALLBACK_0;
_L2:
        JVM INSTR dup ;
        JVM INSTR ifnull 37;
           goto _L3 _L4
_L3:
        break MISSING_BLOCK_LABEL_21;
_L4:
        break MISSING_BLOCK_LABEL_37;
        this;
        CGLIB$findLove$0$Method;
        CGLIB$emptyArgs;
        CGLIB$findLove$0$Proxy;
        //這里就是調(diào)用方法攔截器的intecept()方法
        intercept();
        return;
        super.findLove();
        return;
    }

    final boolean CGLIB$equals$1(Object obj)
    {
        return super.equals(obj);
    }

    public final boolean equals(Object obj)
    {
        CGLIB$CALLBACK_0;
        if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
        JVM INSTR pop ;
        CGLIB$BIND_CALLBACKS(this);
        CGLIB$CALLBACK_0;
_L2:
        JVM INSTR dup ;
        JVM INSTR ifnull 57;
           goto _L3 _L4
_L3:
        this;
        CGLIB$equals$1$Method;
        new Object[] {
            obj
        };
        CGLIB$equals$1$Proxy;
        intercept();
        JVM INSTR dup ;
        JVM INSTR ifnonnull 50;
           goto _L5 _L6
_L5:
        JVM INSTR pop ;
        false;
          goto _L7
_L6:
        (Boolean);
        booleanValue();
_L7:
        return;
_L4:
        return super.equals(obj);
    }

    final String CGLIB$toString$2()
    {
        return super.toString();
    }

    public final String toString()
    {
        CGLIB$CALLBACK_0;
        if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
        JVM INSTR pop ;
        CGLIB$BIND_CALLBACKS(this);
        CGLIB$CALLBACK_0;
_L2:
        JVM INSTR dup ;
        JVM INSTR ifnull 40;
           goto _L3 _L4
_L3:
        this;
        CGLIB$toString$2$Method;
        CGLIB$emptyArgs;
        CGLIB$toString$2$Proxy;
        intercept();
        (String);
        return;
_L4:
        return super.toString();
    }

    final int CGLIB$hashCode$3()
    {
        return super.hashCode();
    }

    public final int hashCode()
    {
        CGLIB$CALLBACK_0;
        if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
        JVM INSTR pop ;
        CGLIB$BIND_CALLBACKS(this);
        CGLIB$CALLBACK_0;
_L2:
        JVM INSTR dup ;
        JVM INSTR ifnull 52;
           goto _L3 _L4
_L3:
        this;
        CGLIB$hashCode$3$Method;
        CGLIB$emptyArgs;
        CGLIB$hashCode$3$Proxy;
        intercept();
        JVM INSTR dup ;
        JVM INSTR ifnonnull 45;
           goto _L5 _L6
_L5:
        JVM INSTR pop ;
        0;
          goto _L7
_L6:
        (Number);
        intValue();
_L7:
        return;
_L4:
        return super.hashCode();
    }

    final Object CGLIB$clone$4()
        throws CloneNotSupportedException
    {
        return super.clone();
    }

    protected final Object clone()
        throws CloneNotSupportedException
    {
        CGLIB$CALLBACK_0;
        if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
        JVM INSTR pop ;
        CGLIB$BIND_CALLBACKS(this);
        CGLIB$CALLBACK_0;
_L2:
        JVM INSTR dup ;
        JVM INSTR ifnull 37;
           goto _L3 _L4
_L3:
        this;
        CGLIB$clone$4$Method;
        CGLIB$emptyArgs;
        CGLIB$clone$4$Proxy;
        intercept();
        return;
_L4:
        return super.clone();
    }

    public static MethodProxy CGLIB$findMethodProxy(Signature signature)
    {
        String s = signature.toString();
        s;
        s.hashCode();
        JVM INSTR lookupswitch 5: default 120
    //                   -508378822: 60
    //                   1192015562: 72
    //                   1826985398: 84
    //                   1913648695: 96
    //                   1984935277: 108;
           goto _L1 _L2 _L3 _L4 _L5 _L6
_L2:
        "clone()Ljava/lang/Object;";
        equals();
        JVM INSTR ifeq 121;
           goto _L7 _L8
_L8:
        break MISSING_BLOCK_LABEL_121;
_L7:
        return CGLIB$clone$4$Proxy;
_L3:
        "findLove()V";
        equals();
        JVM INSTR ifeq 121;
           goto _L9 _L10
_L10:
        break MISSING_BLOCK_LABEL_121;
_L9:
        return CGLIB$findLove$0$Proxy;
_L4:
        "equals(Ljava/lang/Object;)Z";
        equals();
        JVM INSTR ifeq 121;
           goto _L11 _L12
_L12:
        break MISSING_BLOCK_LABEL_121;
_L11:
        return CGLIB$equals$1$Proxy;
_L5:
        "toString()Ljava/lang/String;";
        equals();
        JVM INSTR ifeq 121;
           goto _L13 _L14
_L14:
        break MISSING_BLOCK_LABEL_121;
_L13:
        return CGLIB$toString$2$Proxy;
_L6:
        "hashCode()I";
        equals();
        JVM INSTR ifeq 121;
           goto _L15 _L16
_L16:
        break MISSING_BLOCK_LABEL_121;
_L15:
        return CGLIB$hashCode$3$Proxy;
_L1:
        JVM INSTR pop ;
        return null;
    }

    public static void CGLIB$SET_THREAD_CALLBACKS(Callback acallback[])
    {
        CGLIB$THREAD_CALLBACKS.set(acallback);
    }

    public static void CGLIB$SET_STATIC_CALLBACKS(Callback acallback[])
    {
        CGLIB$STATIC_CALLBACKS = acallback;
    }
    //此方法在靜態(tài)代碼塊中被調(diào)用
    private static final void CGLIB$BIND_CALLBACKS(Object obj)
    {
        Boy$$EnhancerByCGLIB$$71710d07 boy$$enhancerbycglib$$71710d07 = (Boy$$EnhancerByCGLIB$$71710d07)obj;
        if(boy$$enhancerbycglib$$71710d07.CGLIB$BOUND) goto _L2; else goto _L1
_L1:
        Object obj1;
        boy$$enhancerbycglib$$71710d07.CGLIB$BOUND = true;
        obj1 = CGLIB$THREAD_CALLBACKS.get();
        obj1;
        if(obj1 != null) goto _L4; else goto _L3
_L3:
        JVM INSTR pop ;
        CGLIB$STATIC_CALLBACKS;
        if(CGLIB$STATIC_CALLBACKS != null) goto _L4; else goto _L5
_L5:
        JVM INSTR pop ;
          goto _L2
_L4:
        (Callback[]);
        boy$$enhancerbycglib$$71710d07;
        JVM INSTR swap ;
        0;
        JVM INSTR aaload ;
        (MethodInterceptor);
        CGLIB$CALLBACK_0;
_L2:
    }

    public Object newInstance(Callback acallback[])
    {
        CGLIB$SET_THREAD_CALLBACKS(acallback);
        CGLIB$SET_THREAD_CALLBACKS(null);
        return new Boy$$EnhancerByCGLIB$$71710d07();
    }

    public Object newInstance(Callback callback)
    {
        CGLIB$SET_THREAD_CALLBACKS(new Callback[] {
            callback
        });
        CGLIB$SET_THREAD_CALLBACKS(null);
        return new Boy$$EnhancerByCGLIB$$71710d07();
    }

    public Object newInstance(Class aclass[], Object aobj[], Callback acallback[])
    {
        CGLIB$SET_THREAD_CALLBACKS(acallback);
        JVM INSTR new #2   <Class Boy$$EnhancerByCGLIB$$71710d07>;
        JVM INSTR dup ;
        aclass;
        aclass.length;
        JVM INSTR tableswitch 0 0: default 35
    //                   0 28;
           goto _L1 _L2
_L2:
        JVM INSTR pop ;
        Boy$$EnhancerByCGLIB$$71710d07();
          goto _L3
_L1:
        JVM INSTR pop ;
        throw new IllegalArgumentException("Constructor not found");
_L3:
        CGLIB$SET_THREAD_CALLBACKS(null);
        return;
    }

    public Callback getCallback(int i)
    {
        CGLIB$BIND_CALLBACKS(this);
        this;
        i;
        JVM INSTR tableswitch 0 0: default 30
    //                   0 24;
           goto _L1 _L2
_L2:
        CGLIB$CALLBACK_0;
          goto _L3
_L1:
        JVM INSTR pop ;
        null;
_L3:
        return;
    }

    public void setCallback(int i, Callback callback)
    {
        switch(i)
        {
        case 0: // '\0'
            CGLIB$CALLBACK_0 = (MethodInterceptor)callback;
            break;
        }
    }

    public Callback[] getCallbacks()
    {
        CGLIB$BIND_CALLBACKS(this);
        this;
        return (new Callback[] {
            CGLIB$CALLBACK_0
        });
    }

    public void setCallbacks(Callback acallback[])
    {
        this;
        acallback;
        JVM INSTR dup2 ;
        0;
        JVM INSTR aaload ;
        (MethodInterceptor);
        CGLIB$CALLBACK_0;
    }
    //這里有很多的屬性嫌吠,仔細看一下就是一個方法對應兩個,一個是Method類型掺炭,一個是MethodProxy類型
    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$findLove$0$Method;
    private static final MethodProxy CGLIB$findLove$0$Proxy;
    private static final Object CGLIB$emptyArgs[];
    private static final Method CGLIB$equals$1$Method;
    private static final MethodProxy CGLIB$equals$1$Proxy;
    private static final Method CGLIB$toString$2$Method;
    private static final MethodProxy CGLIB$toString$2$Proxy;
    private static final Method CGLIB$hashCode$3$Method;
    private static final MethodProxy CGLIB$hashCode$3$Proxy;
    private static final Method CGLIB$clone$4$Method;
    private static final MethodProxy CGLIB$clone$4$Proxy;
    //靜態(tài)代碼塊辫诅,調(diào)用下面靜態(tài)方法,這個靜態(tài)方法大概做的就是獲取目標方法中每個方法的MethodProxy對象
    static 
    {
        CGLIB$STATICHOOK1();
    }
    //無參構(gòu)造器
    public Boy$$EnhancerByCGLIB$$71710d07()
    {
        CGLIB$BIND_CALLBACKS(this);
    }
}

FastClass機制分析

為什么要用這種機制呢涧狮?直接用反射多好啊炕矮,但是我們知道反射雖然很好用,但是和直接new對象相比者冤,效率有點慢肤视,于是就有了這種機制,參考這篇博客https://www.cnblogs.com/cruze/p/3865180.html涉枫,有個小例子可以說的非常清楚邢滑;

public class test10 {  //這里,tt可以看作目標對象愿汰,fc可以看作是代理對象困后;首先根據(jù)代理對象的getIndex方法獲取目標方法的索引乐纸,  //然后再調(diào)用代理對象的invoke方法就可以直接調(diào)用目標類的方法,避免了反射
    public static void main(String[] args){
        Test tt = new Test();
        Test2 fc = new Test2();
        int index = fc.getIndex("f()V");
        fc.invoke(index, tt, null);
    }
}

class Test{
    public void f(){
        System.out.println("f method");
    }
    
    public void g(){
        System.out.println("g method");
    }
}
class Test2{
    public Object invoke(int index, Object o, Object[] ol){
        Test t = (Test) o;
        switch(index){
        case 1:
            t.f();
            return null;
        case 2:
            t.g();
            return null;
        }
        return null;
    }
    //這個方法對Test類中的方法建立索引
    public int getIndex(String signature){
        switch(signature.hashCode()){
        case 3078479:
            return 1;
        case 3108270:
            return 2;
        }
        return -1;
    }
}

CGLIB動態(tài)代理小結(jié)

上面我們看了CGLib動態(tài)代理的用法摇予、實際生成的代理類以及FastClass機制汽绢,下面我們就以最前面的那個例子中調(diào)用findLove()方法來看看主要的調(diào)用步驟;

第一步:是經(jīng)過一系列操作實例化出了Enhance對象侧戴,并設置了所需要的參數(shù)然后enhancer.create()成功創(chuàng)建出來了代理對象宁昭,這個就不多說了...

第二步:調(diào)用代理對象的findLove()方法,會進入到方法攔截器的intercept()方法酗宋,在這個方法中會調(diào)用proxy.invokeSuper(obj, args);方法

第三步:invokeSuper中积仗,通過FastClass機制調(diào)用目標類的方法

CGLIB動態(tài)代理和JDK動態(tài)代理比較

  1. JDK動態(tài)代理是實現(xiàn)了被代理對象的接口,CGLIB動態(tài)代理是繼承了被代理對象
  2. JDK和CGLIB都是在運行時生成字節(jié)碼蜕猫,JDK是直接生成Class字節(jié)碼斥扛,CGLIB是使用ASM框架寫Class字節(jié)碼,CGLIB代理實現(xiàn)更復雜丹锹,因此生成代理類比JDK效率低
  3. JDK調(diào)用代理方法是通過反射機制調(diào)用,而CGLIB是通過FastClass機制直接調(diào)用方法芬失,因此CGLIB執(zhí)行效率更高楣黍。

Spring 中的代理選擇的原則

  1. 當Bean有實現(xiàn)接口時,Spring就會使用JDK的動態(tài)代理
  2. 當Bean沒有實現(xiàn)接口時棱烂,Spring使用Cglib動態(tài)代理
  3. Spring可以強制使用Cglib動態(tài)代理租漂,只需要在Spring的配置文件中加如下代碼
<aop:aspectj-autoproxy proxy-target-class="true"/>
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市颊糜,隨后出現(xiàn)的幾起案子哩治,更是在濱河造成了極大的恐慌,老刑警劉巖衬鱼,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件业筏,死亡現(xiàn)場離奇詭異,居然都是意外死亡鸟赫,警方通過查閱死者的電腦和手機蒜胖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抛蚤,“玉大人台谢,你說我怎么就攤上這事∷昃” “怎么了朋沮?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長缀壤。 經(jīng)常有香客問我樊拓,道長纠亚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任骑脱,我火速辦了婚禮菜枷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘叁丧。我一直安慰自己啤誊,他們只是感情好,可當我...
    茶點故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布拥娄。 她就那樣靜靜地躺著蚊锹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪稚瘾。 梳的紋絲不亂的頭發(fā)上牡昆,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天,我揣著相機與錄音摊欠,去河邊找鬼丢烘。 笑死,一個胖子當著我的面吹牛些椒,可吹牛的內(nèi)容都是我干的播瞳。 我是一名探鬼主播,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼免糕,長吁一口氣:“原來是場噩夢啊……” “哼赢乓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起石窑,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤牌芋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后松逊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體躺屁,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年经宏,在試婚紗的時候發(fā)現(xiàn)自己被綠了楼咳。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,643評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡烛恤,死狀恐怖母怜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缚柏,我是刑警寧澤苹熏,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響轨域,放射性物質(zhì)發(fā)生泄漏袱耽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一干发、第九天 我趴在偏房一處隱蔽的房頂上張望朱巨。 院中可真熱鬧,春花似錦枉长、人聲如沸冀续。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽洪唐。三九已至,卻和暖如春吼蚁,著一層夾襖步出監(jiān)牢的瞬間凭需,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工肝匆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留粒蜈,地道東北人。 一個月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓旗国,卻偏偏與公主長得像枯怖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子粗仓,可洞房花燭夜當晚...
    茶點故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內(nèi)容