1. 程式人生 > >java基礎-代理(靜態代理、動態代理、cglib代理)

java基礎-代理(靜態代理、動態代理、cglib代理)

代理(Proxy)是一種設計模式,提供了對目標物件另外的訪問方式;即通過代理物件訪問目標物件.這樣做的好處是:可以在目標物件實現的基礎上,增強額外的功能操作,即擴充套件目標物件的功能.

這裡使用到程式設計中的一個思想:不要隨意去修改別人已經寫好的程式碼或者方法,如果需改修改,可以通過代理的方式來擴充套件該方法.

1.  靜態代理

靜態代理在使用時,需要定義介面或者父類,被代理物件與代理物件一起實現相同的介面或者是繼承相同父類.

代理物件與目標物件要實現相同的介面,然後通過呼叫相同的方法來呼叫目標物件的方法.

介面:

package com.h3c.demo.proxy;

public interface Person {

    String sing(String name);

    String dance(String name);
}

目標物件:

package com.h3c.demo.proxy;

public class WangHong implements  Person {
    @Override
    public String sing(String name) {
        System.out.println("網紅唱"+name+"歌!!");
        return "唱歌之後!";
    }

    @Override
    public String dance(String name) {
        System.out.println("網紅跳"+name+"舞!!");
        return "跳舞之後!";
    }
}

代理物件:

package com.h3c.demo.proxy;

public class WanHongStaticProxy implements  Person{

    private WangHong wangHong;

    public WanHongStaticProxy(WangHong wangHong) {
        this.wangHong = wangHong;
    }

    @Override
    public String sing(String name) {
        System.out.println("唱歌之前");
        return wangHong.sing(name);
    }

    @Override
    public String dance(String name) {
        System.out.println("跳舞之前");
        return wangHong.dance(name);
    }
}

測試類:

        //測試靜態代理
        WangHong wangHong = new WangHong();
        WanHongStaticProxy wanHongStaticProxy = new WanHongStaticProxy(wangHong);
        wanHongStaticProxy.sing("盜將行");
        wanHongStaticProxy.dance("爵士");

控制檯結果:

唱歌之前
網紅唱盜將行歌!!
跳舞之前
網紅跳爵士舞!!

靜態代理總結:

1.可以做到在不修改目標物件的功能前提下,對目標功能擴充套件.

2.缺點:

因為代理物件需要與目標物件實現一樣的介面,所以會有很多代理類,類太多.同時,一旦介面增加方法,目標物件與代理物件都要維護.

2.  動態代理

動態代理有以下特點:

1.代理物件,不需要實現介面

2.代理物件的生成,是利用JDK的API,動態的在記憶體中構建代理物件(需要我們指定建立代理物件/目標物件實現的介面的型別)

3.動態代理也叫做:JDK代理,介面代理

JDK中生成代理物件的API

代理類所在包:java.lang.reflect.Proxy

JDK實現代理只需要使用newProxyInstance方法,但是該方法需要接收三個引數,完整的寫法是:

static Object newProxyInstance(ClassLoader loader,

Class[] interfaces,InvocationHandler h )

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

ClassLoader loader,:指定當前目標物件使用類載入器,獲取載入器的方法是固定的

Class[] interfaces,:目標物件實現的介面的型別,使用泛型方式確認型別

InvocationHandler h:事件處理,執行目標物件的方法時,會觸發事件處理器的方法,會把當前執行目標物件的方法作為引數傳入

代理物件:

package com.h3c.demo.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class WangHongProxy {

    private Person wangHong = new WangHong();

    public Person getProxy() {
        //使用Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)返回某個物件的代理物件
        return (Person) Proxy.newProxyInstance(WangHongProxy.class.getClassLoader(), wangHong.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //如果呼叫的是代理物件的sing方法
                        if (method.getName().equals("sing")) {
                            System.out.println("唱歌之前請求");
                            //代理物件呼叫真實目標物件的sing方法去處理使用者請求
                            return method.invoke(wangHong, args);
                            }
                        //如果呼叫的是代理物件的dance方法
                        if (method.getName().equals("dance")) {
                            System.out.println("跳舞之前請求");
                            //代理物件呼叫真實目標物件的dance方法去處理使用者請求
                            return method.invoke(wangHong, args);
                            }
                        return null;
                    }
                });
    }
}

測試類:

        WangHongProxy proxy = new WangHongProxy();
        //獲得代理物件
        Person p = proxy.getProxy();
        //呼叫代理物件的sing方法
        String retValue = p.sing("不捨");
        System.out.println(retValue);
        //呼叫代理物件的dance方法
        String value = p.dance("孔雀舞");
        System.out.println(value);

控制檯結果:

網紅唱不捨歌!
唱歌之後
跳舞之前請求
網紅跳孔雀舞舞!
跳舞之後

總結:

代理物件不需要實現介面,但是目標物件一定要實現介面,否則不能用動態代理

3.  cglib代理

上面的靜態代理和動態代理模式都是要求目標物件是實現一個介面的目標物件,但是有時候目標物件只是一個單獨的物件,並沒有實現任何的介面,這個時候就可以使用以目標物件子類的方式類實現代理,這種方法就叫做:Cglib代理.

Cglib代理,也叫作子類代理,它是在記憶體中構建一個子類物件從而實現對目標物件功能的擴充套件.

JDK的動態代理有一個限制,就是使用動態代理的物件必須實現一個或多個介面,如果想代理沒有實現介面的類,就可以使用Cglib實現.

Cglib是一個強大的高效能的程式碼生成包,它可以在執行期擴充套件java類與實現java介面.它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop,為他們提供方法的interception(攔截)

Cglib包的底層是通過使用一個小而塊的位元組碼處理框架ASM來轉換位元組碼並生成新的類.不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class檔案的格式和指令集都很熟悉.

Cglib子類代理實現方法:

1.需要引入cglib的jar檔案,但是Spring的核心包中已經包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.

2.引入功能包後,就可以在記憶體中動態構建子類

3.代理的類不能為final,否則報錯

4.目標物件的方法如果為final/static,那麼就不會被攔截,即不會執行目標物件額外的業務方法.

目標物件類:

package com.h3c.demo.proxy;

public class WangHongCglib {

    public String sing(String name) {
        System.out.println("網紅唱"+name+"歌!");
        return "唱歌之後";
    }

    public String dance(String name) {
        System.out.println("網紅跳"+name+"舞!");
        return "跳舞之後";
    }
}

cglib工廠類:

package com.h3c.demo.proxy;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class ProxyFactoryCglib implements MethodInterceptor {

    //維護目標物件
    private Object target;

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

    //給目標物件建立一個代理物件
    public Object getProxyInstance(){
        //1.工具類
        Enhancer en = new Enhancer();
        //2.設定父類
        en.setSuperclass(target.getClass());
        //3.設定回撥函式
        en.setCallback(this);
        //4.建立子類(代理物件)
        return en.create();

    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("開始處理");

        //執行目標物件的方法
        Object returnValue = method.invoke(target, objects);

        System.out.println("處理結束");

        return returnValue;
    }
}

測試類:

//目標物件
WangHongCglib target = new WangHongCglib();
//代理物件
WangHongCglib proxyInstance = (WangHongCglib) new ProxyFactoryCglib(target).getProxyInstance();
//執行代理物件的方法
proxyInstance.sing("借我");
proxyInstance.dance("街舞");

控制檯結果:

開始處理
網紅唱借我歌!
處理結束
開始處理
網紅跳街舞舞!
處理結束

在Spring的AOP程式設計中:

如果加入容器的目標物件有實現介面,用JDK代理.

如果目標物件沒有實現介面,用Cglib代理.