动态代理

动态代理

静态代理不适用的场景

当项目中有大量的类和方法需要同类型的代理时,静态代理就不太适用了,因为你不可能为每个类编写一个对应的代理类。
有时候无法提前获知要代理的类有哪些,例如第三方框架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生成的代理类是继承自目标类的,在创建子类对象之前一定要先创建父类对象,所以在代理对象中一定会有目标对象。



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