java代理詳解:靜態(tài)代理、jdk動(dòng)態(tài)代理、cglib動(dòng)態(tài)代理

代理是基本的設(shè)計(jì)模式之一,是為了提供額外或不同的操作而插入的用以代替實(shí)際的"對(duì)象"的對(duì)象。代理對(duì)象通常繼承自實(shí)際對(duì)象或?qū)?shí)際對(duì)象作為自己的成員變量,因此能夠在提供額外操作的同時(shí)與"實(shí)際對(duì)象"通信并調(diào)用其原有的功能。

代理模式定義:給某一個(gè)對(duì)象提供一個(gè)代理,并由代理對(duì)象控制對(duì)原對(duì)象的引用。

根據(jù)創(chuàng)建代理類的不同可以分為靜態(tài)代理和動(dòng)態(tài)代理。

靜態(tài)代理

程序員創(chuàng)建或特定代碼生成工具自動(dòng)生成源代碼,在對(duì)其編譯。在程序運(yùn)行前,代理類.class字節(jié)碼文件就已經(jīng)存在。

interface IStar {

    void dance();

    String sing(String song,String song_text);
}
@Data
@AllArgsConstructor
public class BigStar implements IStar {

    private String starName;

    @Override
    public void dance() {
        System.out.println(starName+"正在跳舞~");
    }

    @Override
    public String sing(String song,String song_text) {
        System.out.println(this.starName+"正在唱歌:《"+song+"》");
        return song_text;
    }
}
public class ProxyUtil implements IStar{

    private IStar star;

    public ProxyUtil(IStar star){
        this.star = star;
    }

    @Override
    public void dance() {
        System.out.println("代理安排跳舞產(chǎn)地");
        this.star.dance();
    }

    @Override
    public String sing(String song, String song_text) {
        System.out.println("代理安排唱歌產(chǎn)地");
        String text = this.star.sing(song,song_text);
        System.out.println("歌詞:"+text);
        return "謝謝";
    }
}
public class Test {
    public static void main(String[] args) {
        //創(chuàng)建一個(gè)阿yueyue的代理
        ProxyUtil 阿yueyue的代理 = new ProxyUtil(new BigStar("阿yueyue"));
        阿yueyue的代理.dance();
        阿yueyue的代理.sing("沈園外","歌詞1111");

        //創(chuàng)建一個(gè)虞書欣的代理
        ProxyUtil 虞書欣的代理 = new ProxyUtil(new BigStar("虞書欣"));
        虞書欣的代理.dance();
        虞書欣的代理.sing("如果愛忘了","歌詞2222");
    }
}

上例是一個(gè)靜態(tài)代理的實(shí)現(xiàn),一個(gè)委托類對(duì)應(yīng)一個(gè)代理類,代理類在編譯期間就已經(jīng)確定。如果沒有使用接口,代理類可以通過繼承委托類實(shí)現(xiàn)靜態(tài)代理。

動(dòng)態(tài)代理

代理類在程序運(yùn)行時(shí)利用反射機(jī)制動(dòng)態(tài)創(chuàng)建而成,主要分為jdk動(dòng)態(tài)代理和cglib動(dòng)態(tài)代理。

jdk實(shí)現(xiàn)動(dòng)態(tài)代理需要實(shí)現(xiàn)類通過接口定義業(yè)務(wù)方法,對(duì)于沒有接口的實(shí)現(xiàn)類,可以使用cglib代理。
cglib采用了非常底層的字節(jié)碼技術(shù),原理是通過字節(jié)碼技術(shù)為一個(gè)類創(chuàng)建一個(gè)子類(繼承的方式),
并在子類中使用方法攔截技術(shù)攔截所以父類方法的調(diào)用,順勢(shì)織入橫切邏輯,兩種代理都是實(shí)現(xiàn)Spring Aop的基礎(chǔ)。

jdk動(dòng)態(tài)代理

JDK 動(dòng)態(tài)代理類的字節(jié)碼在程序運(yùn)行時(shí)由 Java 反射機(jī)制動(dòng)態(tài)生成,無需手工編寫它的源代碼。
動(dòng)態(tài)代理類不僅簡(jiǎn)化了編程工作,而且提高了軟件系統(tǒng)的可擴(kuò)展性,因?yàn)?Java 反射機(jī)制可以生成任意類型的動(dòng)態(tài)代理類。
java.lang.reflect 包中的Proxy類和InvocationHandler接口提供了生成動(dòng)態(tài)代理類的能力。
一、定義業(yè)務(wù)接口以及實(shí)現(xiàn)

public interface IUserService {
    void login(String userName,String userPwd);
    void register(String userName);
    void download(String bookName);
}
public class UserService implements IUserService {
    @Override
    public void login(String userName,String userPwd) {
        System.out.println("正在執(zhí)行登錄操作~");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("登錄完成,賬號(hào):"+userName+",密碼:"+userPwd);
    }
    @Override
    public void register(String userName) {
        System.out.println("正在執(zhí)行注冊(cè)操作~");
        try {
            Thread.sleep(1500);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("用戶名"+userName+"注冊(cè)完成");
    }
    @Override
    public void download(String bookName) {
        System.out.println("正在執(zhí)行下載操作~");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("《"+bookName+"》" +"下載完成。");
    }
}

二、自定義創(chuàng)建代理的類,在該類里調(diào)用Proxy的靜態(tài)方法創(chuàng)建一個(gè)代理類

public class DefineProxy {
    private Object target;
    public DefineProxy(Object target){
        this.target = target;
    }

    public Object createProxy(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("開始計(jì)算方法 "+method.getName()+" 的執(zhí)行時(shí)間~");
                long start = System.currentTimeMillis();
                Object invoke = method.invoke(target, args);
                long end = System.currentTimeMillis();
                System.out.println("執(zhí)行時(shí)間為:"+(end-start)+"毫秒。");
                return invoke;
            }
        });
    }
}

① DefineProxy構(gòu)造方法傳入的target對(duì)象是即將被代理的類的對(duì)象,最終還是需要使用這個(gè)對(duì)象來執(zhí)行業(yè)務(wù)操作。
② Proxy的靜態(tài)方法newProxyInstance參數(shù)

//public static Object newProxyInstance(ClassLoader loader,
//                                          Class<?>[] interfaces,
//                                          InvocationHandler h)
  • loader:類加載器,使用本類的類加載器即可,或者target對(duì)象的類加載器也行。
  • interfaces:接口數(shù)組,表示被代理類實(shí)現(xiàn)的接口,因可能會(huì)實(shí)現(xiàn)多個(gè)接口,所以這里是數(shù)組的形式。
  • InvocationHandler h:核心,在這里創(chuàng)建一個(gè)內(nèi)部實(shí)現(xiàn)類實(shí)現(xiàn)InvocationHandler接口,重寫invoke方法
    invoke里的參數(shù)proxy是代理類的實(shí)例,method是代理類的實(shí)例執(zhí)行的方法,args則是執(zhí)行的方法里面的參數(shù),
    我們可以在這里對(duì)執(zhí)行核心業(yè)務(wù)邏輯前后增加代碼。method.invoke(target, args)這里是用了反射的原理讓target對(duì)象去執(zhí)行method方法。
    三、通過代理調(diào)用方法
public class Test {
    public static void main(String[] args) {
//        IUserService proxy = (IUserService)ProxyFactory.jdk_getProxyInstance(new UserService());
        IUserService proxy = (IUserService)new DefineProxy(new UserService()).createProxy();
        proxy.login("anbanyu","czw520kdd");
        proxy.register("anbanyu");
        proxy.download("小而美:持續(xù)盈利的經(jīng)營(yíng)法則");
    }
}
cglib 動(dòng)態(tài)代理

JDK 中提供的生成動(dòng)態(tài)代理類的機(jī)制有個(gè)鮮明的特點(diǎn)是:某個(gè)類必須有實(shí)現(xiàn)的接口,如果某個(gè)類沒有實(shí)現(xiàn)接口,那么這個(gè)類就不能通過 JDK 產(chǎn)生動(dòng)態(tài)代理了!不過幸好我們有 CGLib。CGLIB(Code Generation Library)是一個(gè)強(qiáng)大的、高性能、高質(zhì)量的Code生成類庫,它可以在運(yùn)行期擴(kuò)展Java類與實(shí)現(xiàn)Java接口。
CGLIB 通過動(dòng)態(tài)生成一個(gè)需要被代理類的子類(即被代理類作為父類),該子類重寫被代理類的所有不是 final 修飾的方法,并在子類中采用方法攔截的技術(shù)攔截父類所有的方法調(diào)用,進(jìn)而織入橫切邏輯。此外,因?yàn)?CGLIB 采用整型變量建立了方法索引,這比使用 JDK 動(dòng)態(tài)代理更快(使用 Java 反射技術(shù)創(chuàng)建代理類的實(shí)例)。

CGLib 創(chuàng)建某個(gè)類 A 的動(dòng)態(tài)代理類的模式是:

  • 查找 A 上的所有非 final 的 public 類型的方法定義;
  • 將這些方法的定義轉(zhuǎn)換成字節(jié)碼;
  • 將組成的字節(jié)碼轉(zhuǎn)換成相應(yīng)的代理的 class 對(duì)象;
  • 實(shí)現(xiàn) MethodInterceptor 接口,用來處理 對(duì)代理類上所有方法的請(qǐng)求(這個(gè)接口和JDK動(dòng)態(tài)代理InvocationHandler的功能和角色是一樣的)

一、首先定義一個(gè)委托類,注意就是一個(gè)普通的類

public class Dog {
    public void eat(){
        System.out.println("狗吃屎");
    }
}

二、實(shí)現(xiàn)MethodInterceptor方法

public class CGLibProxy implements MethodInterceptor {

    private Object target;

    public CGLibProxy(Object target){
        this.target = target;
    }

    public Object createProxy(){
        //cglib中的增強(qiáng)器,用來創(chuàng)建動(dòng)態(tài)代理
        Enhancer enhancer = new Enhancer();
        //設(shè)置要?jiǎng)?chuàng)建動(dòng)態(tài)代理的類
        enhancer.setSuperclass(target.getClass());
        //設(shè)置回調(diào),這里相當(dāng)于是對(duì)于代理類上所有方法的調(diào)用,都會(huì)調(diào)用callback,而callback則需要實(shí)現(xiàn)intercept()方法進(jìn)行攔截。
        enhancer.setCallback(this);
        //創(chuàng)建代理類
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib proxy start...");
        methodProxy.invokeSuper(o,args);
        System.out.println("cglib proxy end...");
        return null;
    }
}

三、測(cè)試代理類

public class Test {
    public static void main(String[] args) {
//        Dog dog = (Dog)ProxyFactory.cglib_getProxyInstance(new Dog());
        Dog dog = (Dog)new CGLibProxy(new Dog()).createProxy();
        dog.eat();
    }
}
  • 注意由于 CGLib 動(dòng)態(tài)代理采用的是繼承委托類的方式,因此不能代理 final 修飾的類。如果將上例中的類Dog添加 final 修飾符,再次運(yùn)行則會(huì)看到如下錯(cuò)誤信息:

Exception in thread "main" java.lang.IllegalArgumentException: Cannot subclass final class chapter14.Train at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:565) at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25) at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:329)

兩種動(dòng)態(tài)代理區(qū)別總結(jié)

Java 動(dòng)態(tài)代理是利用反射機(jī)制生成一個(gè)實(shí)現(xiàn)代理接口的匿名類,在調(diào)用具體方法前調(diào)用 InvokeHandler 來處理。而 CGLIB 動(dòng)態(tài)代理是利用 ASM 開源包,對(duì)代理對(duì)象類的 class 文件加載進(jìn)來,通過修改其字節(jié)碼生成子類來處理。

使用場(chǎng)景

實(shí)現(xiàn) AOP 功能

Spring 的 AOP 功能就是利用動(dòng)態(tài)代理的原理實(shí)現(xiàn)的。其會(huì)根據(jù)被代理對(duì)象是否實(shí)現(xiàn)了接口選擇不同的生成代理對(duì)象的方式,如果被代理對(duì)象實(shí)現(xiàn)了需要被代理的接口,則使用 JDK 的動(dòng)態(tài)代理,否則便使用 CGLIB 代理。

  • 如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,默認(rèn)情況下會(huì)采用 JDK 的動(dòng)態(tài)代理實(shí)現(xiàn) AOP,對(duì)應(yīng)的包裝類為 JdkDynamicAopProxy。
  • 如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,可以強(qiáng)制使用 CGLIB 實(shí)現(xiàn) AOP
  • 如果目標(biāo)對(duì)象沒有實(shí)現(xiàn)了接口,必須采用 CGLIB 庫,spring 會(huì)自動(dòng)在 JDK 動(dòng)態(tài)代理和 CGLIB 之間轉(zhuǎn)換

Spring Aop 和 cglib的關(guān)系

image.png
  • 最底層是字節(jié)碼。
  • ASM是操作字節(jié)碼的工具。
  • cglib基于ASM字節(jié)碼工具操作字節(jié)碼(即動(dòng)態(tài)生成代理,對(duì)方法進(jìn)行增強(qiáng))。
  • Spring aop基于cglib進(jìn)行封裝,實(shí)現(xiàn)cglib方式的動(dòng)態(tài)代理。
cglib依賴包
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容