【Java】動態代理實例講解

實例1

/**
 * 對生產廠家要求的接口
 */
public interface IProducer {
    /**
     * 銷售
     * @param money
     */
    public void saleProduct(float money);
    /**
     * 售後
     * @param money
     */
    public void afterService(float money);
}
/**
 * 一個生產者
 */
public class Producer implements IProducer{
    /**
     * 銷售
     * @param money
     */
    public void saleProduct(float money){
        System.out.println("生產者銷售產品,並拿到錢:"+money);
    }
    /**
     * 售後
     * @param money
     */
    public void afterService(float money){
        System.out.println("生產者提供售後服務,並拿到錢:"+money);
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 模擬一個消費者
 */

/**
 * 動態代理:
 * 特點:字節碼隨用隨創建,隨用隨加載
 * 作用:不修改源碼的基礎上對方法增強
 * 分類:
 * 基於接口的動態代理
 * 基於子類的動態代理
 * 基於接口的動態代理:
 * 涉及的類:Proxy
 * 提供者:JDK官方
 * 如何創建代理對象:
 * 使用Proxy類中的newProxyInstance方法
 * 創建代理對象的要求:
 * 被代理類最少實現一個接口,如果沒有則不能使用
 * newProxyInstance方法的參數:
 * ClassLoader:類加載器
 * 它是用於加載代理對象字節碼的。和被代理對象使用相同的類加載器。固定寫法。
 * Class[]:字節碼數組
 * 它是用於讓代理對象和被代理對象有相同方法。固定寫法。
 * InvocationHandler:用於提供增強的代碼
 * 它是讓我們寫如何代理。我們一般都是些一個該接口的實現類,通常情況下都是匿名內部類,但不是必須的。
 * 此接口的實現類都是誰用誰寫。
 */


public class Client {


    public static void main(String[] args) {
        IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(Producer.class.getClassLoader(),
                Producer.class.getInterfaces(), new MyInvocationHandler());
        proxyProducer.saleProduct(10000f);
    }
}

class MyInvocationHandler implements InvocationHandler {
    /**
     * 作用:執行被代理對象的任何接口方法都會經過該方法
     * 方法參數的含義
     *
     * @param proxy  代理對象的引用
     * @param method 當前執行的方法
     * @param args   當前執行方法所需的參數
     * @return 和被代理對象方法有相同的返回值
     * @throws Throwable
     */
    private Producer producer = new Producer();

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //提供增強的代碼
        Object returnValue = null;

        //1.判斷當前方法是不是銷售
        if ("saleProduct".equals(method.getName())) {
            //2.獲取方法執行的參數
            Float money = (Float) args[0];
            returnValue = method.invoke(producer, money * 0.8f);
            System.out.println("代理獲得" + money * 0.2f);
        }
        return returnValue;
    }
}

簡單說,上述代碼就是對producer這個對象進行代理,更進一步我發現,代理對象的類只能被聲明成接口類型的類,Producer proxyProducer = (Producer) Proxy.newProxyInstance()這樣就會報錯,不能這麼寫,代理類對象是newProxyInstance第二個參數裏聲明的類,第二個參數裏還都得是接口。

並且通過調試可以看出,proxyProducer對象裏持有了一個我寫的MyInvocationHandler對象
在這裏插入圖片描述

實例2

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

interface IAccount {
    public IAccount deposit(double value);
    public double getBalance();
}



class ExampleInvocationHandler implements InvocationHandler {
    private double balance;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//            proxy指的就是代理對象,也就是下面的那個account,因此在這裏面如果打印account
//            print(proxy),那麼會調用proxy.toString()方法,這會導致無終止的遞歸調用invoke,導致棧內存溢出
        // simplified method checks, would need to check the parameter count and types too
        if ("deposit".equals(method.getName())) {
            Double value = (Double) args[0];
            System.out.println("deposit: " + value);
            balance += value;
//                這裏返回proxy是可以保證可以鏈式編程,就像下面可以多個deposit連着寫
            return proxy; // here we use the proxy to return 'this'
        }
        if ("getBalance".equals(method.getName())) {
            return balance;
        }
        return null;
    }
}

public class Main {
    public static void main(String[] args) {
        IAccount account = (IAccount) Proxy.newProxyInstance(IAccount.class.getClassLoader(),
                new Class[]{IAccount.class},
                new ExampleInvocationHandler());
// method chaining for the win!
        account.deposit(5000).deposit(4000).deposit(-2500);
        System.out.println("Balance: " + account.getBalance());
    }
}

強行說的話,上面的代碼誰也沒代理,因爲invoke方法裏並沒有調用哪個實例對象的方法,但是生成了一個代理對象,這個代理對象是一個聲明的是一個接口類,當執行方法的時候,直接走的就是invoke方法。

爲什麼叫反射呢,正常情況下都是對象調用方法,而反射機制就是用方法調用對象。

實例3

package com.itheima.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

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

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

        /**
         * 動態代理:
         *  特點:字節碼隨用隨創建,隨用隨加載
         *  作用:不修改源碼的基礎上對方法增強
         *  分類:
         *      基於接口的動態代理
         *      基於子類的動態代理
         *  基於子類的動態代理:
         *      涉及的類:Enhancer
         *      提供者:第三方cglib庫
         *  如何創建代理對象:
         *      使用Enhancer類中的create方法
         *  創建代理對象的要求:
         *      被代理類不能是最終類
         *  create方法的參數:
         *      Class:字節碼
         *          它是用於指定被代理對象的字節碼。
         *
         *      Callback:用於提供增強的代碼
         *          它是讓我們寫如何代理。我們一般都是些一個該接口的實現類,通常情況下都是匿名內部類,但不是必須的。
         *          此接口的實現類都是誰用誰寫。
         *          我們一般寫的都是該接口的子接口實現類:MethodInterceptor
         */
        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(12000f);
    }
}

實例1和2使用的是jdk本身的動態代理,要求被代理類實現接口;而實例3使用的是cglib實現動態代理,不需要被代理類實現接口

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章