Java动态代理应用基础
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();//这里打印?
}