動態代理jdk的Proxy與spring的CGlib

三隻蛋黃派發表於2022-01-22

1. 為什麼要使用動態代理?

動態代理:在不改變原有程式碼的情況下上進行物件功能增強 使用代理物件代替原來的物件完成功能 進而達到擴充功能的目的

2.JDK Proxy 動態代理面向介面的動態代理

特點:

  1. 一定要有介面和實現類的存在 代理物件增強的是實現類 在實現介面的方法重寫的方法
  2. 生成的代理物件只能轉換成 介面的不能轉換成 被代理類
  3. 代理物件只能增強介面中定義的方法 實現類中其他和介面無關的方法是無法增強的
  4. 代理物件只能讀取到介面中方法上的註解 不能讀取到實現類方法上的註解
    使用方法:
public class Test1 {
    public static void main(String[] args) {
        Dinner dinner=new Person("張三");
        // 通過Porxy動態代理獲得一個代理物件,在代理物件中,對某個方法進行增強
//        ClassLoader loader,被代理的物件的類載入器
        ClassLoader classLoader = dinner.getClass().getClassLoader();
//        Class<?>[] interfaces,被代理物件所實現的所有介面
        Class[] interaces= dinner.getClass().getInterfaces();
//        InvocationHandler h,執行處理器物件,專門用於定義增強的規則
        InvocationHandler handler = new InvocationHandler(){
            // invoke 當我們讓代理物件呼叫任何方法時,都會觸發invoke方法的執行
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//                Object proxy, 代理物件
//                Method method,被代理的方法
//                Object[] args,被代理方法執行時的實參
                Object res=null;
               if(method.getName().equals("eat")){
                   System.out.println("飯前洗手");
                   // 讓原有的eat的方法去執行
                   res =method.invoke(dinner, args);
                   System.out.println("飯後刷碗");
               }else{
                   // 如果是其他方法,那麼正常執行就可以了
                   res =method.invoke(dinner, args);
               }
                return res;
            }
        };
        Dinner dinnerProxy =(Dinner) Proxy.newProxyInstance(classLoader,interaces,handler);
        //dinnerProxy.eat("包子");
        dinnerProxy.drink();
    }
}
interface Dinner{
    void eat(String foodName);
    void drink();
}
class Person implements Dinner{
    private String name;
    public Person(String name) {
        this.name = name;
    }
    @Override
    public void eat(String foodName) {
        System.out.println(name+"正在吃"+foodName);
    }
    @Override
    public void drink( ) {
        System.out.println(name+"正在喝茶");
    }
}
class Student implements Dinner{
    private String name;
    public Student(String name) {
        this.name = name;
    }
    @Override
    public void eat(String foodName) {
        System.out.println(name+"正在食堂吃"+foodName);
    }
    @Override
    public void drink( ) {
        System.out.println(name+"正在喝可樂");
    }
}

3.CGlib動態代理

cglib動態代理模式是面向父類

特點:

  1. 面向父類的和介面沒有直接關係
    2.不僅可以增強介面中定義的方法還可以增強其他方法
    3.可以讀取父類中方法上的所有註解

  2. 使用例項

public class Test1 {
    @Test
    public void testCglib(){
        Person person =new Person();
        // 獲取一個Person的代理物件
        // 1 獲得一個Enhancer物件
        Enhancer enhancer=new Enhancer();
        // 2 設定父類位元組碼
        enhancer.setSuperclass(person.getClass());
        // 3 獲取MethodIntercepter物件 用於定義增強規則
        MethodInterceptor methodInterceptor=new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                /*Object o,  生成之後的代理物件 personProxy
                Method method,  父類中原本要執行的方法  Person>>> eat()
                Object[] objects, 方法在呼叫時傳入的實引數組
                MethodProxy methodProxy  子類中重寫父類的方法 personProxy >>> eat()
                */
                Object res =null;
                if(method.getName().equals("eat")){
                    // 如果是eat方法 則增強並執行
                    System.out.println("飯前洗手");
                    res=methodProxy.invokeSuper(o,objects);
                    System.out.println("飯後刷碗");
                }else{
                    // 如果是其他方法 不增強執行
                    res=methodProxy.invokeSuper(o,objects); // 子類物件方法在執行,預設會呼叫父類對應被重寫的方法
                }
                return res;
            }
        };
        // 4 設定methodInterceptor
        enhancer.setCallback(methodInterceptor);
        // 5 獲得代理物件
        Person personProxy = (Person)enhancer.create();
        // 6 使用代理物件完成功能
        personProxy.eat("包子");
    }
}
class Person  {
    public Person( ) {
    }
    public void eat(String foodName) {
        System.out.println("張三正在吃"+foodName);
    }
}

兩個動態代理的區別

  1. JDK動態代理是面向介面的,只能增強實現類中介面中存在的方法。CGlib是面向父類的,可以增強父類的所有方法
  2. JDK得到的物件是JDK代理物件例項,而CGlib得到的物件是被代理物件的子類

相關文章