Java中的代理原理

28

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方法来直接调用目标对象方法。

涉及到的各个类之间的关系如下图所示:

其他代理

  1. 利用AspectJ编译器插件。该插件的工作原理是:在编译阶段直接改写class文件,在切点直接调用定义的切面方法。
  2. 利用Agent类加载代理的方式。在启动java程序时,配置启动参数java -javaagent:/location/aspectjweaver.jar。该插件的工作原理是:在类加载阶段改写类信息,也就是在切点添加调用切面方法的代码。生成的字节码只存在于内存中,可以通过阿里的Arthas工具来查看。