0、代理的本质

如果把对象间方法的调用理解为client到server之间的通信,那么Proxy的本质是实现一个具有特定功能的中间人。在Java中存在多种方式实现对对象方法调用的代理。

有了中间人之后,就可以对调用进行hook,达到如下目的:

1)控制调用流程,如权限控制、隐藏、监控调用记录等;

2)篡改调用方提供的Request(即Input),如对分布式操作进行Map;

3)篡改被调用方返回的Response(即Output),如对分布式操作结果进行Reduce。

 

1、设计模式-代理模式

示例代码:

接口ITest.java

package me.xjump.test.proxy;

public interface ITest {
  public void m();
}

对象Test1.java

package me.xjump.test.proxy;

public class Test1 implements ITest {

  @Override
  public void m() {
    System.out.println("Test1.m() called.");
  }
  
  public void m1() {
    System.out.println("Test1.m1() called.");
  }
}

对象Test2.java

package me.xjump.test.proxy;

public class Test2 implements ITest {

  @Override
  public void m() {
    System.out.println("Test2.m() called.");
  }
  
  public void m1() {
    System.out.println("Test2.m1() called.");
  }
}

代理类StaticProxyForITest.java

package me.xjump.test.proxy;

public class StaticProxyForITest implements ITest {

  private ITest test1;  
  
  public StaticProxyForITest(ITest test12) {  
    this.test1 = test12;  
  }  
  @Override
  public void m() {
    System.out.println("before m() call.");
    test1.m();
    System.out.println("after m() call.");
  }
}

应用:

private static void testStaticProxy() {
  System.out.println("static proxy test, Proxy Pattern.");
  ITest test1 = new Test1();  
  ITest test2 = new Test2();  
  StaticProxyForITest test1Proxy = new StaticProxyForITest(test1);  
  test1Proxy.m(); 
  //test1Proxy.m1(); 静态代理时,无法调用非接口方法m1
  StaticProxyForITest test2Proxy = new StaticProxyForITest(test2);  
  test2Proxy.m(); 
  //test2Proxy.m1(); 静态代理时,无法调用非接口方法m1
}

从上述代码来看,静态代理需要为每个Interface编写ProxyWrapper类来实现对Interface中方法的代理。

从实现上看,无法编写一次代码,就完美做到对需关注所有方法hook。这是静态代理的局限性,但静态代理作为经典的设计模式,并非毫无用处。例如,静态代理的性能就比动态代理要好很多。

 

2、JDK动态代理

DynamicProxyJDK.java

package me.xjump.test.proxy;

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

public class DynamicProxyJDK implements InvocationHandler {

  private Object target;

  public Object bindToHandler(Object target) {
    this.target = target;
    return Proxy.newProxyInstance(target.getClass().getClassLoader(),
        target.getClass().getInterfaces(), this); 
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable {
    Object result = null;
    System.out.println("before invoke.");
    result = method.invoke(target, args);
    System.out.println("after invoke.");
    return result;
  }
}

应用:

private static void testDynamicProxyJDK() {
  System.out.println("\nJDK dynamic proxy test1.");
  DynamicProxyJDK testDynamicProxyJDK = new DynamicProxyJDK();
  ITest test1jdk = (ITest)testDynamicProxyJDK.bindToHandler(new Test1());
  test1jdk.m();
  //test1jdk.m1(); jdk动态代理时,无法调用m1
  System.out.println("\nJDK dynamic proxy test2.");
  ITest test2jdk = (ITest)testDynamicProxyJDK.bindToHandler(new Test2());
  test2jdk.m();
  //test2jdk.m1(); jdk动态代理时,无法调用m1
  test1jdk.m();//这里打印?
}

JDK动态代理的关键在实现java.lang.reflect.InvocationHandler这个Interface的invoke方法。

需要注意的是:

1)上述代码中的DynamicProxyJDK这个Class实际上是一个InvocationHandler的binder。因为DynamicProxyJDK实例化后,target成员变量实际上是dynamicProxyJDK对象的状态。也就是说,如果dynamicProxyJDK绑定到多个对象上,其状态为最后绑定的对象。这和下面的CGLib动态代理是有区别的。

2)Proxy.newProxyInstance()构造的Proxy必须cast到ITest上,而不是Test1或Test2对应的对象上。那么在使用proxy时就无法做到对非ITest接口方法的调用及hook。

 

3、CGLib,ASM,bcel、Javassist等

DynamicProxyCGLib.java

package me.xjump.test.proxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class DynamicProxyCGLib implements MethodInterceptor {

  public Object createProxyInstance(Object target) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(target.getClass());
    enhancer.setCallback(this);
    return enhancer.create();
  }

  @Override
  public Object intercept(Object obj, Method method, Object[] args,
      MethodProxy proxy) throws Throwable {
    Object result = null;
    System.out.println("Test1DynamicProxyCGLib: befor invoke.");
    result = proxy.invokeSuper(obj, args);
    System.out.println("Test1DynamicProxyCGLib: after invoke.");
    return result;
  }
}

应用:

private static void testDynamicProxyCGLib() {
  System.out.println("\nCGLib dynamic proxy test1.");
  DynamicProxyCGLib testDynamicProxyCGLib = new DynamicProxyCGLib();
  Test1 test1cglib = (Test1)testDynamicProxyCGLib.createProxyInstance(new Test1());
  test1cglib.m();
  System.out.println("\nCGLib dynamic proxy test2.");
  Test2 test2cglib = (Test2)testDynamicProxyCGLib.createProxyInstance(new Test2());
  test2cglib.m();
  test1cglib.m();//这里打印?
}