1. 程式人生 > 其它 >面試官讓手寫動態代理實現?——JDK代理和CGLIB代理解析

面試官讓手寫動態代理實現?——JDK代理和CGLIB代理解析

技術標籤:springbootjavaspringproxy反射aop

什麼是動態代理

動態代理是相對於靜態代理產生的概念。
可以簡單理解為,靜態代理是在程式碼中編寫好代理類,動態代理的代理類是在執行時動態生成的。
使用代理一般是為了在原類中增加一些增強功能,如Spring的AOP就是通過動態代理實現的。

Spring中使用動態代理

有JDK代理和CGLIB代理兩種實現。

JDK代理和CGLIB代理區別

JDK

利用攔截器(攔截器必須實現InvocationHanlder)加上反射機制生成一個實現代理介面的匿名類,在呼叫具體方法前呼叫InvokeHandler來處理。

CJLIB

利用ASM開源包,對代理物件類的class檔案載入進來,通過修改其位元組碼生成子類來處理。

何時選用

  • 當Bean實現介面時,Spring就會用JDK的動態代理。
  • 當Bean沒有實現介面時,Spring使用CGlib是實現。
  • 可以強制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)。

具體實現

JDK動態代理實現

定義一個介面:

interface  Demo{
  int add(int, int);
}

實現類:

class Real implements Demo {
  @Override
  public int add(int x, int y) {
    return x + y;
  }

這裡Real就是我們需要代理的類。

動態代理程式碼:

class Handler implements InvocationHandler {
  private final Real real;

  public Handler(Real real) {
    this.real = real;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args)
      throws IllegalAccessException, IllegalArgumentException,
             InvocationTargetException {
    System.out.println("=== BEFORE ===");
    Object re = method.invoke(real, args);
    System.out.println("=== AFTER ===");
    return re;
  }
}

構造方法把要代理的物件傳入Handler中。
這裡invoke方法很重要,可以看到方法呼叫是通過我們傳遞一個Method型別引數,然後呼叫method.invoke來實現,即通過反射來實現。

生成代理類的樣子:

public ProxyClass implements Demo {
  private static Method mAdd;

  private InvocationHandler handler;

  static {
    Class clazz = Class.forName("Demo");
    mAdd = clazz.getMethod("add", int.class, int.class);
  }
  
  @Override
  public int add(int x, int y) {
    return (Integer)handler.invoke(this, mAdd, new Object[] {x, y});
  }
}

這裡add方法是呼叫了handler的invoke方法,傳遞三個引數,第一個是代理類本身,第二個是add方法的反射類,最後一個是引數列表。我們是通過InvocationHandler來完成攔截與代理。

JDK Proxy具體使用:

Handler handler = new Handler(new Real());
ifc p = (ifc)Proxy.newProxyInstance(ifc.class.getClassLoader(),
                                    new Class[] {Demo},
                                    handler);
p.add(1, 2);

CGLIB動態代理實現

沒有介面,直接是實現類:

class Real {
  public int add(int x, int y) {
    return x + y;
  }
}

使用了一個與JDK Proxy中Handler類似的類:

public class Interceptor implements MethodInterceptor {
  @Override
  public Object intercept(Object obj,
                          Method method,
                          Object[] args,
                          MethodProxy proxy) throws Throwable {
      System.out.println("=== BEFORE ===");
      Object re = proxy.invokeSuper(obj, args);
      System.out.println("=== AFTER ===");
      return re;
  }
}

代理類的大致樣子:

public ProxyClass extends Real {
  private static Method mAdd;
  private static MethodProxy mAddProxy;

  private MethodInterceptor interceptor;

  static {
    Class clazz = Class.forName("ifc");
    mAdd = clazz.getMethod("add", int.class, int.class);
    // Some logic to generate mAddProxy.
    // ...
  }
  
  @Override
  public int add(int x, int y) {
    return (Integer)interceptor.invoke(
        this, mAdd, new Object[] {x, y}, mAddProxy);
  }
}

與jdk代理大致相同,只是多了一個MethodProxy。
去上面Interceptor可以看到,呼叫invoke方法是

Object re = proxy.invokeSuper(obj, args);

而不是

Object re = method.invoke(obj, args);

原因是代理類繼承了原始類,obj指向的就是代理類物件的例項,如果第二種寫法就會遞迴呼叫代理類的add方法。
因此cglib封裝了一個MethodProxy類,其中invokeSuper方法可以呼叫原始基類的真正方法。

使用:

public static void main(String[] args) {
  Enhancer eh = new Enhancer();
  eh.setSuperclass(Real.class);
  eh.setCallback(new Interceptor());

  Real r = (Real)eh.create();
  int result = r.add(1, 2);
}

總結

JDK代理和CGLIB代理實現本質上是很相似的。
都包含一下兩點內容:

  • 有一個介面或者基類,定義了一個代理類。
  • 一個方法攔截器,完成方法的攔截和代理,是所有呼叫鏈的橋樑。