1. 程式人生 > 實用技巧 >jdk動態代理與cglib動態代理

jdk動態代理與cglib動態代理

動態代理

動態代理23種設計模式之一

動態代理的基於反射的

代理模式:

作用:
	1)功能增強:在原有的功能上,增加了額外的功能,做了功能增強
	2)控制訪問:代理不讓使用者訪問目標,比如4S店不讓我直接找廠家
開發中,如果A類本來是呼叫C類的方法,完成某個功能
但是C不讓A呼叫
A不能訪問C,然後在A和C之間建立一個B代理類
C讓B訪問
B是代理類,既能訪問C,同時又能增加新的業務功能,增強對C的訪問
A通過訪問B,B訪問C,到了A訪問C的結果,雖然是間接訪問

有點類似使用者和廠家的關係
比如說小李想買輛邁凱倫,但是小李不能直接聯絡到邁凱倫的廠家,廠家並不提供個人業務,那怎麼辦呢?只能通過4S店了,
我們去4S店買,4S店幫我們去和廠家訂車,提車,並且還能給我們提供優質的服務。所以邁凱倫4S店就好比代理類,不僅幫我們去間接找到了廠家,
還對業務進行增強,為小李提供服務

實現代理的模式:

  • 靜態代理:

    ​ 1)代理類是自己實現的,自己建立一個java類,表示代理類

    ​ 2)並且要代理的目標類也是確定的

  • 動態代理

    ​ 1)程式執行過程中,使用JDK的反射機制,建立代理類物件,並動態指定要代理的目標類

動態代理的實現分類:

  • JDK動態代理:使用jdk反射包中的類和介面實現動態代理的功能
  • cglib動態代理:第三方的工具庫,cglib的原理是繼承,通過繼承目標類,建立他的子類,在子類總重寫父類中同名的方法,實現功能的修改
因為cglib是繼承,重寫方法,所以要求目標類不能是final的,方法也不能是final的
cglib的要求目標類比較寬鬆,只要能繼承就可以了
cglib在很多的框架中使用,比如mybatis,spring框架中都有使用

區別:

JDK動態代理要求目標類必須有介面


複習method方法

public interface Hello {  void sayHello(String name);  }
public class HelloImpl implements Hello {

    @Override
    public void sayHello(String name) {
        System.out.println("你好!"+name);
    }
}
public class Test01 {

    @Test
    public void test01(){
        Hello hello = new HelloImpl();
        hello.sayHello("小李");
        /*
        你好!小李
        */
}
public class Test01 {
    @Test
    public void test01(){
        //通過反射機制來執行sayHello方法      
        Hello hello = new HelloImpl();
        
        try {
            Method method = Hello.class.getDeclaredMethod("sayHello", String.class);
            method.invoke(hello,"啟亮");
			
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/*
你好!啟亮
*/

Object obj : 物件,要執行這個物件的方法

Object... args : 方法執行的引數值

Method是一個獨立的方法物件,代表所有sayHello方法的物件,通過invoke就可以執行任意物件的sayHello方法,然後通過invoke中引數指定具體執行那個物件中的sayHello方法,和執行方法鎖需要的引數

jdk動態代理的實現

反射包 java.lang.reflect;裡面有三個類
InvocationHandler
Method
Proxy

jdk動態代理的使用

1)Proxy類:

​ 核心的物件,建立代理物件。之前建立物件都是new類的構造方法

​ 現在是使用Proxy類的方法,代替new的使用

方法:newProxyInstance

原型:

public static Object newProxyInstance(
    ClassLoader loader,
    Class<?>[] interfaces,
    InvocationHandler h    
){   ...   }

引數:

ClassLoader loader,  
類載入器(目標物件的類載入器),負責向記憶體中載入物件,使用反射獲取物件的ClassLoader 
Class<?>[] interfaces, 
介面,目標物件實現的介面,也是反射獲取的
InvocationHandler h   
我們自己寫的,代理類要完成的功能
返回值:就是代理的物件

2)newProxyInstance

InvocationHandler介面
原始碼:
public interface InvocationHandler {
	public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;  }

InvocationHandler: 譯為:呼叫處理程式

invoke:表示代理物件要執行的功能程式碼,代理類要完成的功能就寫在這個方法中
		完成的功能:1.呼叫目標方法,執行目標方法的功能
				  2.功能增強,在目標方法呼叫時,增加功能
				  
引數說明:
proxy:jdk建立的代理物件,無需賦值
method:目標類中的方法  ,由jdk提供method物件的
args:目標類中方法的引數,由jdk提供

InvocationHandler介面:表示你的代理要幹什麼

怎麼使用:

​ 1.既然是介面,先實現介面InvocationHandler

​ 2.重寫invoke方法,原來靜態代理中代理類完成的功能寫在這裡

3)Method類:

​ 表示方法的,具體來說就是目標類中的方法

​ 作用:通過Method可以執行某個目標類的方法,Method.invoke();

Method.invoke() ; 中的invoke只是Method類中的方法
public Object invoke(...,...)是InvocationHandler介面中的方法

Method.invoke(目標物件,方法的引數);

通過這個目標物件就可以執行物件的方法,而不需要知道這個方法的名稱,因為method方法的值是jdk幫你確定的,這個物件本身就是jdk提供的

動態代理實現步驟

1.建立介面,定義目標類要完成的功能

2.建立目標類實現介面

3.建立InvocationHandler介面的實現類,在invoke方法中完成代理類的功能

​ ● 呼叫目標方法

​ ● 增強功能

4.使用Proxy類的靜態方法,建立代理物件,並把返回值轉換為介面型別

程式碼實現:

User.java

public interface User {
    void add();
    void delete();
    void update();
    void find();
}

UserImpl.java

public class UserImpl implements User {
    @Override
    public void add() {
        System.out.println("增");
    }

    @Override
    public void delete() {
        System.out.println("刪");
    }

    @Override
    public void update() {
        System.out.println("改");
    }

    @Override
    public void find() {
        System.out.println("查");
    }
}

UserHandler.java

//必須實現InvocationHandler介面,完成代理類要做的功能
//1.呼叫目標方法  2.功能增強
public class UserHandler implements InvocationHandler {

    private Object target = null;//目標物件

    //動態代理:目標物件是動態的,不是固定的
    //傳入的哪個物件,就給哪個物件建立代理
    public UserHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        /*
        target:裡面這個物件是動態的,需要傳入
        result:是方法執行的返回值,沒有可以不加
         */

        System.out.println("前增強");//增強方法

        Object result = method.invoke(target,args);//傳入的物件執行的每個方法,都會呼叫這個

        System.out.println("後增強");//增強方法

        //返回方法執行的返回值
        return result;
    }
}

MyProxy.java

public class MyProxy {

    @Test
    public void test() {
        //1.建立目標物件
        User user = new UserImpl();

        //2.建立InvocationHandler物件
        InvocationHandler handler = new UserHandler(user);

        //3.建立代理物件
        User userProxy = (User) Proxy.newProxyInstance(
             user.getClass().getClassLoader(),//獲得這個物件的類載入器才能操作這個物件
             user.getClass().getInterfaces(),
             handler
        );
        //4.把型別轉換成代理物件的介面型別,因為目標物件實現了這個介面

        //5.通過代理物件執行方法
        userProxy.add();
        System.out.println("=========");
        userProxy.delete();
        System.out.println("=========");
        userProxy.update();
        System.out.println("=========");
        userProxy.find();

    }
}

列印結果:

前增強
增
後增強
=========
前增強
刪
後增強
=========
前增強
改
後增強
=========
前增強
查
後增強

匿名內部類實現:

User.java

public interface User {
    void add();
    void delete();
    void update();
    void select();
}

UserImpl.java

public class UserImpl implements User {
    @Override
    public void add() {
        System.out.println("增");
    }

    @Override
    public void delete() {
        System.out.println("刪");
    }

    @Override
    public void update() {
        System.out.println("改");
    }

    @Override
    public void select() {
        System.out.println("查");
    }
}

MyProxyTest.java

public class MyProxyTest {
        private User user = new UserImpl();
    @Test
    public void test01(){
        user.add();
        user.delete();
        user.update();
        user.select();
        System.out.println("================");

        User proxy = (User) Proxy.newProxyInstance(
                user.getClass().getClassLoader(),//被代理物件的類載入器
                user.getClass().getInterfaces(),//獲取被代理物件所實現的介面,(有共同的特徵)
                new InvocationHandler() {      //就是具體實現代理的邏輯
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("A");
                        //所有代理物件呼叫的方法都會執行
                        Object invoke = method.invoke(user, args);
                        System.out.println("B");
                        return invoke;//返回給代理物件,有返回值就是此返回值
                    }
                });
        proxy.add();
        System.out.println("*******");
        proxy.delete();
        System.out.println("*******");
        proxy.update();
        System.out.println("*******");
        proxy.select();
    }
}

*

cglib動態代理的實現

動態代理:
 特點:位元組碼隨用隨建立,隨用隨載入
 作用:不修改原始碼的基礎上對方法增強
 分類:
     基於介面的動態代理  jdk
     基於子類的動態代理  cglib
 基於子類的動態代理:
     涉及的類:Enhancer
     提供者:第三方cglib庫
 如何建立代理物件:
     使用Enhancer類中的create方法
 建立代理物件的要求:
     被代理類不能是最終類
 create方法的引數:
     Class:位元組碼
         它是用於指定被代理物件的位元組碼。

     Callback:用於提供增強的程式碼
         它是讓我們寫如何代理。我們一般都是些一個該介面的實現類,通常情況下都是匿名內部類,但不是必須的。
         此介面的實現類都是誰用誰寫。
         我們一般寫的都是該介面的子介面實現類:MethodInterceptor

依賴:

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.1</version>
</dependency>

案例:

Producer.java

/**
 * 一個生產者
 */
public class Producer {

    /**
     * 銷售
     * @param money
     */
    public void saleProduct(float money){
        System.out.println("銷售產品,並拿到錢:"+money);
    }

    /**
     * 售後
     * @param money
     */
    public void afterService(float money){
        System.out.println("提供售後服務,並拿到錢:"+money);
    }
}

測試類:

Client.java

/**
 * 模擬一個消費者
 */
public class Client {

    public static void main(String[] args) {
        
        final Producer producer = new Producer();

        Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 執行被代理物件的任何方法都會經過該方法
             * @param proxy
             * @param method
             * @param args
             *    以上三個引數和基於介面的動態代理中invoke方法的引數是一樣的
             * @param methodProxy :當前執行方法的代理物件
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                //提供增強的程式碼
                Object returnValue = null;

                //1.獲取方法執行的引數
                Float money = (Float)args[0];
                //2.判斷當前方法是不是銷售
                if("saleProduct".equals(method.getName())) {
                    returnValue = method.invoke(producer, money*0.8f);
                }
                return returnValue;
            }
        });
        cglibProducer.saleProduct(10000f);
    }
}
//銷售產品,並拿到錢:8000.0