Java中的代理原理
JDK提供的代理
通过jdk提供的代理来实现代理功能时,需要代理对象实现接口。jdk代理的原理:通过Proxy.newProxyIntance()方法来创建一个代理对象,需要传入InvocationHandler的实现类,该接口提供三个参数,其中一个是Method对象,这里通过Method对象来反射调用目标对象的方法来实现代理功能。
示例代码:
public class JDKProxy {
interface Foo {
void foo();
}
static class Target implements Foo {
@Override
public void foo() {
System.out.println("target foo method.");
}
}
public static void main(String[] args) {
// 要代理的目标对象
Target target = new Target();
Foo o = (Foo) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), new Class[]{Foo.class}, (proxy, method, args1) -> {
System.out.println("method invoke before.");
Object obj = method.invoke(target, args1);
System.out.println("method invoke after.");
return obj;
});
o.foo();
}
}
缺点
- 只能针对接口进行代理
原理
通过ASM技术在内存中动态生成代理类的字节码,然后通过自定义类加载器来加载该类。代理类实现了要代理的接口,在代理类中,通过调用InvocationHandler
接口来增强方法。
Cglib实现代理
与JDK提供的代理不同的是cglib代理的目标类不需要实现接口,cglib代理类是目标类的子类。cglib有一些局限性:类不能是最终类,因为被final修饰的类不能被继承,方法也不能被final修饰。
cglib提供了多种方式来调用代理对象,1.与jdk提供的代理相同,使用反射的方式调用目标对象。2.使用methodProxy.invoke方法(MethodInterceptor的最后一个参数)来调用目标对象方式,该方式不是用反射实现的。3.使用methodProxy.invokeSuper方法来调用,该方法不再需要目标对象,只需传入代理对象即可。
public class CglibProxy {
static class Target {
public void foo() {
System.out.println("target foo method.");
}
}
public static void main(String[] args) {
Target target = new Target();
Target oo = (Target) Enhancer.create(Target.class, (MethodInterceptor) (o, method, objects, methodProxy) -> {
System.out.println("method invoke before.");
Object res = method.invoke(target, objects);// 底层使用反射
//Object res = methodProxy.invoke(target, objects);// 底层没有使用反射
//Object res = methodProxy.invokeSuper(o, objects);// 底层没有使用反射,不再需要目标对象
System.out.println("method invoke after.");
return res;
});
oo.foo();
}
}
原理
在cglib中,有三种方式来调用被增强的方法,这三种的实现原理各有不同。
method.invoke
通过这个方法调用被增强的方法,其底层原理与jdk代理类似,底层通过反射来调用被增强的方法。但是cglib对其进行了优化,当第17次调用被增强的方法后,cglib会替换代理方式,由反射方式(method.invoke)变为直接方法调用。
methodProxy.invoke 与 methodProxy.invokeSuper
这两种方式都不是基于反射实现的,而是通过直接调用的方式。其内部原理是动态生成了一个FastClass
类的子类,该类中维护了目标对象方法签名与调用方法的映射,根据传入的方法签名调用目标对象的指定方法。
方法签名
方法签名包含两部分:方法名和出入参数。
示例:void foo(int i)
方法名:foo
出入参数:(I)V
在动态生成目标类的代理对象时,会为每个方法创建MethodProxy
对象,MethodProxy
对象中保存了方法签名和一个FastClassInfo
对象,FastClassInfo
对象中包含了两个动态生成的FastClass
的子类,分别对应了目标对象和代理对象,MethodProxy
对象通过调用该FastClass
对象的invoke
方法来直接调用目标对象方法。
涉及到的各个类之间的关系如下图所示:
其他代理
- 利用AspectJ编译器插件。该插件的工作原理是:在编译阶段直接改写class文件,在切点直接调用定义的切面方法。
- 利用Agent类加载代理的方式。在启动java程序时,配置启动参数
java -javaagent:/location/aspectjweaver.jar
。该插件的工作原理是:在类加载阶段改写类信息,也就是在切点添加调用切面方法的代码。生成的字节码只存在于内存中,可以通过阿里的Arthas工具来查看。