java 動態代理的方法及其缺陷

本文部分原創,大部分爲黑馬程序員課程的提煉

1 動態代理簡介

特點:字節碼隨用隨創建,隨用隨加載
作用:不修改源碼的基礎上對方法增強,在此過程中會建立一個新的類對象。
分類:基於接口的動態代理、基於子類的動態代理

2 基於接口的動態代理:

  涉及的類:Proxy
  提供者:JDK官方

創建代理對象的要求:
被代理類最少實現一個接口,如果沒有則不能使用,新建的對象必須爲接口。
如何創建代理對象:
Object Proxy.newProxyInstance(ClassLoader,Class[],InvocationHandler)
方法的參數:
ClassLoader:類加載器
它是用於加載代理對象字節碼的。和被代理對象使用相同的類加載器。固定寫法。
Class[]:字節碼數組
它是用於讓代理對象和被代理對象有相同方法。固定寫法。
InvocationHandler:用於提供增強的代碼
它是讓我們寫如何代理。我們一般都是些一個該接口的實現類,通常情況下都是匿名內部類,但不是必須的。此接口的實現類都是誰用誰寫。

例如 Producer 爲被代理對象,IProducer 爲其接口
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 作用:執行被代理對象的任何接口方法都會經過該方法
* 方法參數的含義
* @param proxy 代理對象的引用,注意,不要使用method.invoke(proxy),這樣程序會陷入死循環,invoke()方法傳入的對象應該由外部給出
* @param method 當前執行的方法
* @param args 當前執行方法所需的參數
* @return 和被代理對象方法有相同的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增強的代碼
Object returnValue = null;

            //1.獲取方法執行的參數
            Float money = (Float)args[0];
            //2.判斷當前方法是不是銷售
            if("saleProduct".equals(method.getName())) {
                returnValue = method.invoke(producer, money0.8f);
            }
            return returnValue;
        }
    });

proxyProducer.saleProduct(10000f);

3 基於子類的動態代理

由第三方包提供

cglib cglib 2.1_3 涉及的類:Enhancer 提供者:第三方cglib庫 創建代理對象的要求: 被代理類不能是最終類 如何創建代理對象: Object Enhancer.create(Class , Callback) 方法的參數: Class:字節碼 它是用於指定被代理對象的字節碼。
  Callback:用於提供增強的代碼。它是讓我們寫如何代理。我們一般都是些一個該接口的實現類,通常情況下都是匿名內部類,但不是必須的。此接口的實現類都是誰用誰寫。
      我們一般寫的都是該接口的子接口實現類:MethodInterceptor

Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 作用:執行被代理對象的任何接口方法都會經過該方法
* @param proxy 代理對象的引用,注意,不要使用method.invoke(proxy),這樣程序會陷入死循環,invoke()方法傳入的對象應該由外部給出
* @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);

4 缺陷

動態代理有一個很大的缺陷。這在基於子類和基於接口的動態代理方法中都存在。以下以基於接口的動態代理方法舉例。
1)建立一個接口

public interface Fruit {
    public void eat();
    public void run();
}

2)建立一個將要被代理的類

public class Apple implements Fruit {
    @Override
    public void eat() {
        System.out.println("我吃了一個蘋果");
    }
    public void run(){
        System.out.println("蘋果跑起來了!!!");
        eat();
    }
}

3)對類中的方法進行增強
我想在吃蘋果前洗個手。
我想在跑步前熱個身。

public class Demo1 {
    @Test
    public void demo1(){
        final Apple apple = new Apple();
        final Fruit apple1 = (Fruit)Proxy.newProxyInstance(apple.getClass().getClassLoader(), apple.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if("run".equals(method.getName()))
                    System.out.println("蘋果熱身一下");
                if("eat".equals(method.getName()))
                    System.out.println("洗一下手");
                method.invoke(apple);
                return null;
            }
        });
        System.out.println("apple1的方法:");
        apple1.run();
    }
}

運行結果

apple1的方法:
蘋果熱身一下
蘋果跑起來了!!!
我吃了一個蘋果

發現:我並沒有在吃蘋果前洗手!!!!
這點很關鍵。
因爲有時候我寫的類中,許多內部的方法會相互之間調用,但如果我想對它們進行同時增強的話,用動態代理是做不到的。因爲動態代理只會增強最先調用的方法,後續內部之間的相互調用的方法,是不會被增強的。

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