動態代理

動態代理

靜態代理不適用的場景

當項目中有大量的類和方法需要同類型的代理時,靜態代理就不太適用了,因爲你不可能爲每個類編寫一個對應的代理類。
有時候無法提前獲知要代理的類有哪些,例如第三方框架Spring,要爲你類做代理,只能在運行時動態感知要代理的類。因此誕生了動態代理來解決這些問題,實現動態代理有JDK自帶和Cglib兩種方式,下面先分析JDK動態代理。

JDK動態代理

JDK動態代理主要用到Proxy類和InvocationHandler接口。Proxy用於生成代理類實例,方法如下:
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
ClassLoader爲生成代理類的加載器
interfaces爲此代理類要實現的接口
每一個代理類都要綁定一個InvocationHandler,調用代理類的方法,都會回調InvocationHandler.invoke。
這個方法的工作是:
  1. 在運行時動態生成一個新類ProxyClass作爲代理類,其實現了interfaces接口,有一個InvocationHandler成員變量,體現了“動態”;
  2. 創建代理類對象返回;
下面簡單介紹下生成的代理類的樣子,以TestInterface和TestInterfaceImpl爲例
public interface TestInterface{
    void method1();
}

public class TestInterfaceImpl implements TestInterface{
     void method1() {
          System.out.printfln("method1 invoke");
     }
}

調用Proxy.newProxyInstance(TestInterface.class.getClassLoader(),new Class[]{TestInterface.class},handler)生成的代理類大致如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;


//public final的,繼承Proxy,實現你傳入的接口
public final class TestProxy extends Proxy
  implements TestInterface
{
  //private static 的Method屬性,對應所有方法
  private static Method m1;
  private static Method m3;
  private static Method m0;
  private static Method m2;
  //唯一的構造方法,需要一個InvocationHandler接口傳入
  public TestProxy(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  
  
  //代理的方法,回調傳入的InvocationHandler的invoke方法
  public final void method1()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }


  static
  {
    try
    {//每一個屬性所代表的Method都是與上面加入代理方法列表時與固定類綁定的,這是class文件中的格式,方法要與固定的類綁定
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("TestInterface").getMethod("method1", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

從上面的代碼我們可以看到,代理類實現的method1方法回調了InvocationHandler的invoce,也印證了之前說的對代理類方法的調用都會委派給對應的InvocationHandler。

Each proxy instance has an associated invocation handler.
 * When a method is invoked on a proxy instance, the method
 * invocation is encoded and dispatched to the {@code invoke}
 * method of its invocation handler.
實現InvocationHandler接口,來自定義具體的業務邏輯。InvocationHandler就一個方法

invoke(Object proxy, Method method, Object[] args)
其中proxy是代理類實例,
method是調用方法,
args是調用參數。

現在假設我們的功能是在每個類方法調用之前打印時間戳日誌,那麼定義自己的TimestampLogInvocationHandler:
class TimestampLogInvocationHandler implements InvocationHandler {
	private Object target;//被代理對象
	TimestampLogInvocationHandler(Object target) {
		this.target = target;
	} 
	
	@Override
	Object invoke(Object proxy, Method method, Object[] args) {
		logger.info("當前時間:"+new Date());
		return method.invoke(target,args);
	}
}
//測試一下:
class MainTest {
	public static void main(String[] args) {
		TestInterface interface = new TestInterfaceImpl();//被代理對象
		TimestampInvocationHandler tih = new TimestampInvocationHandler(interface);//調用處理器
		//代理類實例
		TestInterface myProxy = (TestInterface)Proxy.newProxyInstance(TestInterface.class.getClassLoader(),new Class[]{TestInterface.class},tih);
		//調用代理類方法
		myProxy.method1();
	}
}
將會輸出:
當前時間:1020021212
method1
可以看到調用都被委派到了InvocationHandler上。


之後若有其他類也需要打印時間戳,可複用此InvocationHandler創建自己的代理類實例,無需再次開發。以上就是JDK的動態代理,下面看下Cglib動態代理。

Cgllib動態代理

JDK動態代理是基於接口的,不適用於類,同時效率也不高;因此誕生了CGLib框架,它底層採用asm技術實現動態代理,並且可以高效地爲類做代理。Cglib的原理是使用Enhancer設置被代理類型,設置回調方法MethodInterceptor(類似於InvocationHandler),以繼承的方式創建動態代理類。Enhancer的常用方法:
void setSuperclass(java.lang.Class superclass)//設置被代理的類型
void setCallback(Callback callback) //設置回調接口
Object create()//創建代理實例,該實例的類型爲superclass的子類
在setCallback中定義了回調接口,調用被代理的方法都會委派到回調接口。最通用的回調接口是MethodInterceptor,它與InvocationHandler類似只有一個方法:
/**
     * All generated proxied methods call this method instead of the original method.
     * The original method may either be invoked by normal reflection using the Method object,
     * or by using the MethodProxy (faster).
     * @param obj "this", the enhanced object
     * @param method intercepted Method
     * @param args argument array; primitive types are wrapped
     * @param proxy used to invoke super (non-intercepted method); may be called
     * as many times as needed
     * @throws Throwable any exception may be thrown; if so, super method will not be invoked
     * @return any value compatible with the signature of the proxied method. Method returning void will ignore this value.
     * @see MethodProxy
     */


public Object intercept(Object object, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable
object是代理實例,method是被攔截的方法,可以通過proxy.invokeSuper(obj, args)調用object的父類的相同方法。

下面看一個例子:
class Cglib{//目標類
    public void cglib(){
        System.out.println("CGLIB");
    }
}

class HelloProxy implements MethodInterceptor{//回調方法
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throwsThrowable {
        System.out.println("Hello");
        Object object = proxy.invokeSuper(obj, args);
        System.out.println("Powerful!");
        return object;
    }
}

public class TestMain {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Cglib.class);
        enhancer.setCallback(new HelloProxy());
        Cglib cglibProxy = (Cglib)enhancer.create();
        cglibProxy.cglib();
    }
}

輸出內容:

Hello

CGLIB

Powerful!

你可能會說,標準的代理模式中,代理類不是要持有被代理類的引用嗎,乍看起來CGLib動態代理沒有目標類的引用,其實仔細想一下,CGLib生成的代理類是繼承自目標類的,在創建子類對象之前一定要先創建父類對象,所以在代理對象中一定會有目標對象。



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