Java 动态代理机制

 2023-02-01
原文作者:蒋先森 原文地址:https://jlj98.top/

如果有错,希望指出

动态代理在Java中有着广泛的应用,比如 Spring Aop、Hibernate 数据查询、RPC、Java 注解对象的获取。静态代理的代理关系在编译时确定,而动态代理的代理关系是在编译期确定的。主要了解:JDK Proxy 和 cglib 动态代理。

动态代理可以提供另一个对象的访问,同时可以隐藏实际对象的具体实例。

静态代理

再说动态代理之前,先了解下静态代理。

静态代理是代理模式实现方式之一,在程序运行前,由程序员创建或者特定工具自动生成源代码并对其编译生成 .class 文件。静态代理的实现需要三步:

  • 定义业务接口
  • 实现业务接口
  • 定义代理类并实现业务接口

最后就可以直接通过客户顿进行调用静态代理了。

202212301147568821.png

202212301147577902.png

202212301147587873.png

202212301147598384.png

202212301148007165.png

静态代理总结:

  • 优点:可以做到不对目标对象进行修改的前提下,对目标对象进行功能的扩展和拦截。
  • 缺点:因为代理对象,需要实现和目标对象一样的接口,会导致代理对象十分繁多,不易维护,同时一旦接口增加方法,则目标对象和代理类都需要维护。

JDK Proxy

先实现一个接口。

    public interface Hello {
        void sayHello();
        void sayJava();
    }
    public class HelloImpl implements Hello {
    
        @Override
        public void sayHello() {
            System.out.println("Hello Java!");
        }
        @Override
        public void sayJava() {
            System.out.println("Hello Java");
        }
    }

下面使用java.lang.reflect.Proxy 实现动态代理

    public class MyInvocationHandler implements InvocationHandler {
    
        private Object target;
        public MyInvocationHandler(Object target) {
            this.target = target;
        }
    
        /**
         * @param proxy  动态代理类的引用,通常情况下不需要它
         * @param method 方法对象的引用,代表被动态代理类调用的方法
         * @param args   args对象数组,代表被调用方法的参数
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("正在执行" + method.getName() + "方法");
            Object result = method.invoke(target, args);
            return result;
        }
    }
    
    public class MyDynamicProxy {
    
        public static void main(String[] args) {
    
            HelloImpl hello = new HelloImpl();
            MyInvocationHandler handler = new MyInvocationHandler(hello);
            //构造代码实例 Proxy.newProxyInstance(类加载器, 需要实现的接口数组,InvocationHandler接口)
            Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(),
                    HelloImpl.class.getInterfaces(),
                    handler);
            //调用代理
            proxyHello.sayHello();
            proxyHello.sayJava();
        }
    }

newProxyInstance()会返回一个实现了指定接口的代理对象,对该对象的所有方法调用都会转发给InvocationHandler.invoke()方法。理解上述代码需要对Java反射机制有一定了解。动态代理神奇的地方就是:

  • 代理对象是在程序运行时产生的,而不是编译期;
  • 对代理对象的所有接口方法调用都会转发到InvocationHandler.invoke()方法,在invoke()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;之后我们通过某种方式执行真正的方法体,示例中通过反射调用了Hello对象的相应方法,还可以通过RPC调用远程方法。

cglib

GCLIB(Code Generation Library)是一个基于 ASM 的字节码生成库,它允许在运行时对字节码进行修改和动态生成。

    //实现MethodInterceptor方法
    public class MyMethodInterceptor implements MethodInterceptor {
    
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("执行GCLIB动态代理");
            return methodProxy.invokeSuper(o, objects);
        }
    
    }
    
    public class CGlibTest {
        public static void main(String[] args) {
            //通过 CGlib 动态获取代理对象
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(HelloImpl.class);
            enhancer.setCallback(new MyMethodInterceptor());
            Hello helloProxy = (Hello) enhancer.create();
            helloProxy.sayHello();
            helloProxy.sayJava();
        }
    }

总结

JDK Proxy

  • JDK动态代理只能代理 有接口的类 ,并且是只能代理接口方法,不能代理一般的类中的方法
  • 提供了一个使用 InvocationHandler 作为参数的构造方法,在代理类中做一层包装,业务逻辑在 invoke 方法中实现;
  • 重写了 Object 类的 equals、hashCode、toString,它们都只是简单的调用了InvocationHandler 的 invoke 方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法;
  • 在 invoke 方法中我们甚至可以不用 Method.invoke 方法调用实现类就返回。这种方式常常用在 RPC 框架中,在 invoke 方法中发起通信调用远端的接口等。

CGlib

  • CGlib 可以传入接口也可以传入普通的类,接口使用实现的方式,普通类使用会使用继承的方式生成代理类;
  • 由于是继承方式,如果是 static方法、private方法、final方法等描述的方法 是不能被代理的;
  • 做了方法访问优化,使用建立方法索引的方式避免了传统 Method 的方法反射调用;
  • 提供 callback 和 filter 设计,可以灵活地给不同的方法绑定不同的 callback。编码更方便灵活。
  • CGLIB 会默认代理 Object 中 finalize、equals、toString、hashCode、clone等方法。比JDK代理多了finalize和clone。

Reference