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();//这里打印?
}