铁锤的IT奇妙探险 Java Back-End Coder

聊聊到底什么是动态代理?


动态代理在程序中的应用几乎无处不在,如何实现并使用动态代理是程序员必备技能之一,我们来聊聊动态代理的原理及其两种实现方式。

动态代理

  • 代理模式是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但是B是“真正”实现类,A则比较“虚”,他借用了B的方法去实现接口的方法。A虽然是“伪军”,但它可以增强B,在调用B的方法前后都做些其他的事情。
  • 类A写死持有B,就是B的静态代理。如果A代理的对象是不确定的,就是动态代理。
  • 动态代理的常用实现方式是反射,典型的是JDK Proxy(反射) 和 CGLib(ASM)。
  • 这里推荐一篇讲解撒杏仁的知乎回答,将动态代理和静态代理讲的非常形象。
  • 动态代理与静态代理的区别是静态代理只能针对特定一种产品(蛋糕、面包、饼干、酸奶)做某种代理动作(撒杏仁),而动态代理则可以对所有类型产品(蛋糕、面包、饼干、酸奶等)做某种代理动作(撒杏仁)。

JDK Proxy 和 CGLib

区别

  • JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现;
  • JDK Proxy 是通过拦截器加反射的方式实现的;
  • JDK Proxy 只能代理继承接口的类;
  • CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;
  • CGLib 无需通过接口来实现,它是通过实现子类的方式来完成调用的;
  • JDK Proxy是“对象的代理”,CGLib是“类的代理”。

原理分析

  1. JDK Proxy 动态代理的实现无需引用第三方类,只需要实现 InvocationHandler 接口,重写 invoke() 方法即可
public class ProxyExample {
    static interface Car {
        void running();
    }

    static class Bus implements Car {
        @Override
        public void running() {
            System.out.println("The bus is running.");
        }
    }

    static class Taxi implements Car {
        @Override
        public void running() {
            System.out.println("The taxi is running.");
        }
    }

    static class JDKProxy implements InvocationHandler {
        private Object target; // 代理对象

        // 获取到代理对象
        public Object getInstance(Object target) {
            this.target = target;
            // 取得代理对象
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(), this);
        }

        /**
         * 触发代理的执行方法
         * @param proxy  代理对象
         * @param method 代理方法
         * @param args   方法的参数
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws InvocationTargetException, IllegalAccessException {
            System.out.println("动态代理之前的业务处理.");
            Object result = method.invoke(target, args); // 执行调用方法(此方法执行前后,可以进行相关业务处理)
            return result;
        }
    }

    public static void main(String[] args) {
        // 执行 JDK Proxy
        JDKProxy jdkProxy = new JDKProxy();
        Car carInstance = (Car) jdkProxy.getInstance(new Taxi());
        carInstance.running();
	}
动态代理之前的业务处理.(动态代理加强的部分)
The taxi is running.

//动态代理的使用

//new 一个目标对象
Taxi taxi = new Taxi(); 
//new一个InvocationHandler代理器(自己实现)
JDKProxy jdkProxy = new JDKProxy();
//将目标对象作为参数传入创建代理对象的方法
Object obj = jdkProxy.getInstance(taxi);
//强转为目标对象的接口类型
Taxi newTaxi = (Taxi)obj;

推荐一篇博客解释invoke()如何自动调用的?点击访问

  1. CGLib的实现
public class CGLibExample {

    static class Car {
        public void running() {
            System.out.println("The car is running.");
        }
    }

    /**
     * CGLib 代理类
     */
    static class CGLibProxy implements MethodInterceptor {
        private Object target; // 代理对象

        public Object getInstance(Object target) {
            this.target = target;
            Enhancer enhancer = new Enhancer();
            // 设置父类为实例类
            enhancer.setSuperclass(this.target.getClass());
            // 回调方法
            enhancer.setCallback(this);
            // 创建代理对象
            return enhancer.create();
        }

        @Override
        public Object intercept(Object o, Method method,
                                Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("方法调用前业务处理.");
            Object result = methodProxy.invokeSuper(o, objects); // 执行方法调用
            return result;
        }
    }

    // 执行 CGLib 的方法调用
    public static void main(String[] args) {
        // 创建 CGLib 代理类
        CGLibProxy proxy = new CGLibProxy();
        // 初始化代理对象
        Car car = (Car) proxy.getInstance(new Car());
        // 执行方法
        car.running();
  • 两者唯一不同的是,CGLib 在初始化被代理类时,是通过 Enhancer 对象把代理对象设置为被代理类的子类来实现动态代理的,因此被代理类不能被关键字 final 修饰。
  • 原理:生成一个继承B的类型C(代理类),这个代理类持有一个MethodInterceptor,我们setCallback时传入的。 C重写所有B中的方法(方法名一致),然后在C中,构建名叫“CGLIB”+“$父类方法名$”的方法(下面叫cglib方法,所有非private的方法都会被构建),方法体里只有一句话super.方法名(),可以简单的认为保持了对父类方法的一个引用,方便调用,重写方法则是外界调用的入口。

Similar Posts

上一篇 锁的了解

Comments