Spring AOP裡的靜態代理和動態代理,你真的瞭解嘛?

陳彥斌發表於2020-07-15

什麼是代理?

  為某一個物件建立一個代理物件,程式不直接用原本的物件,而是由建立的代理物件來控制原物件,通過代理類這中間一層,能有效控制對委託類物件的直接訪問,也可以很好地隱藏和保護委託類物件,同時也為實施不同控制策略預留了空間

什麼是靜態代理?

  由程式建立或特定工具自動生成原始碼,在程式執行前,代理類的.class檔案就已經存在

  通過將目標類與代理類實現同一個介面,讓代理類持有真實類物件,然後在代理類方法中呼叫真實類方法,在呼叫真實類方法的前後新增我們所需要的功能擴充套件程式碼來達到增強的目的。

優點

  代理使客戶端不需要知道實現類是什麼,怎麼做,而客戶端只需知道代理即可

  方便增加功能,擴充套件業務邏輯

缺點

  代理類中常出現大量冗餘的程式碼,非常不利於擴充套件和維護

  如果介面增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了程式碼維護的複雜度

案例演示

PayService.java(介面

package net.cybclass.sp.proxy;

public interface PayService {
    /**
     * 支付回撥
     * @param outTradeNo 訂單號
     * @return
     */
    String callback(String outTradeNo);

    /**
     * 下單
     * @param userId 使用者id
     * @param productId 產品id
     * @return
     */
    int save(int userId,int productId);
}

PayServiceImpl.java(介面實現類)

package net.cybclass.sp.proxy;

public class PayServiceImpl implements PayService{
    public String callback(String outTradeNo) {
        System.out.println("目標類 PayServiceImpl 回撥 方法 callback");
        return outTradeNo;
    }

    public int save(int userId, int productId) {
        System.out.println("目標類 PayServiceImpl 回撥 方法 save");
        return productId;
    }
}

StaticProxyPayServiceImpl.java(介面實現類,靜態代理)

package net.cybclass.sp.proxy;

public class StaticProxyPayServiceImpl implements PayService{
    private PayService payService;
    public StaticProxyPayServiceImpl(PayService payService)
    {
        this.payService=payService;
    }
    public String callback(String outTradeNo) {
        System.out.println("StaticProxyPayServiceImpl callback begin");
        String result=payService.callback(outTradeNo);
        System.out.println("StaticProxyPayServiceImpl callback end");
        return result;
    }

    public int save(int userId, int productId) {
        System.out.println("StaticProxyPayServiceImpl save begin");
        int id = payService.save(userId, productId);
        System.out.println("StaticProxyPayServiceImpl save end");
        return id;
    }
}

演示

什麼是動態代理?

  在程式執行時,運用反射機制動態建立而成,無需手動編寫程式碼

    • JDK動態代理
    • CGLIB動態代理(原理:是對指定的業務類生成一個子類,並覆蓋其中的業務方法來實現代理)

jdk動態代理演示

定義一個類,去實現InvocationHandler這個介面,並車從寫invoke方法

//Object proxy:被代理的物件
//Method method:要呼叫的方法
//Object[] args:方法呼叫時所需要引數
public Object invoke(Object proxy, Method method, Object[] args){}

PayService.java(介面)

package net.cybclass.sp.proxy;

public interface PayService {
    /**
     * 支付回撥
     * @param outTradeNo 訂單號
     * @return
     */
    String callback(String outTradeNo);

    /**
     * 下單
     * @param userId 使用者id
     * @param productId 產品id
     * @return
     */
    int save(int userId,int productId);
}

PayServiceImpl.java(介面實現類)

package net.cybclass.sp.proxy;

public class PayServiceImpl implements PayService{
    public String callback(String outTradeNo) {
        System.out.println("目標類 PayServiceImpl 回撥 方法 callback");
        return outTradeNo;
    }

    public int save(int userId, int productId) {
        System.out.println("目標類 PayServiceImpl 回撥 方法 save");
        return productId;
    }
}

JDKProxy.java(jdk動態代理類)

package net.cybclass.sp.proxy;

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

public class JDKProxy implements InvocationHandler {
    //目標類
    private Object targetObject;

    /**
     * 獲取代理物件
     * @param targetObject 目標類
     * @return
     */
    public Object newProxyInstance(Object targetObject) {
        this.targetObject = targetObject;
        //繫結關係,也就是和具體的那個實現類關聯
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
                targetObject.getClass().getInterfaces(), this);
    }

    /**
     * JDK動態代理
     *
     * @param proxy  靜態代理物件
     * @param method 要呼叫的方法
     * @param args   方法呼叫時所需要引數
     * @return
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        try {
            System.out.println("通過JDK動態代理呼叫"+method.getName()+",列印日誌 begin");
            result = method.invoke(targetObject, args);
            System.out.println("通過JDK動態代理呼叫"+method.getName()+",列印日誌 end");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return result;
    }
}

演示

CGLIB動態代理演示

PayService.java(介面)

package net.cybclass.sp.proxy;

public interface PayService {
    /**
     * 支付回撥
     * @param outTradeNo 訂單號
     * @return
     */
    String callback(String outTradeNo);

    /**
     * 下單
     * @param userId 使用者id
     * @param productId 產品id
     * @return
     */
    int save(int userId,int productId);
}

PayServiceImpl.java(介面實現類)

package net.cybclass.sp.proxy;

public class PayServiceImpl implements PayService{
    public String callback(String outTradeNo) {
        System.out.println("目標類 PayServiceImpl 回撥 方法 callback");
        return outTradeNo;
    }

    public int save(int userId, int productId) {
        System.out.println("目標類 PayServiceImpl 回撥 方法 save");
        return productId;
    }
}

CGLIBProxy.java(CGLIB動態代理類)

package net.cybclass.sp.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 CGLIBProxy implements MethodInterceptor {
    //目標類
    private Object targetObject;
    //繫結關係
    public Object newProxyInstance(Object targetObject){
        this.targetObject=targetObject;
        Enhancer enhancer=new Enhancer();
        //設定代理類的父類(目標類)
        enhancer.setSuperclass(this.targetObject.getClass());
        //設定回撥函式
        enhancer.setCallback(this);
        //建立子類(代理物件)
        return enhancer.create();
    }
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object result=null;
        try
        {
            System.out.println("通過CGLIB動態代理呼叫"+method.getName()+",列印日誌 begin");
            result=methodProxy.invokeSuper(o,args);
            System.out.println("通過CGLIB動態代理呼叫"+method.getName()+",列印日誌 end");
        }
        catch (Exception ex){
            ex.printStackTrace();
        }
        return result;
    }
}

演示

總結

  動態代理與靜態代理相比較,最大的好處是介面中宣告的所有方法都被轉移到呼叫處理器一個集中的方法中處理,解耦和易維護。

兩種動態代理的區別

  • JDK動態代理:要求目標物件實現一個介面,但是有時候目標物件只是一個單獨的物件,並沒有實現任何的介面,這個時候就可以用CGLIB動態代理
  • JDK動態代理是自帶的,CGLIB需要引入第三方包
  • CGLIB動態代理,它是記憶體中構建一個子類物件從而實現對目標物件功能的擴充套件
  • CGLIB動態代理基於繼承來實現代理,所以無法對final類,private方法和static方法實現代理

Spring AOP中的代理使用的預設策略

  • 如果目標物件實現類介面,則預設採用JDK動態代理
  • 如果目標物件沒有實現介面,則採用CGLIB進行動態代理

 

相關文章