Bootstrap

Java动态代理原理图解(附2种实现方式详细对比)

Java动态代理原理

按照代理的创建时期,代理类可以分为两种:

静态代理:由程序员创建或特定工具自动生成源代码,再对其编译,在程序运行前,代理类的.class文件就已经存在了。

动态代理:在程序运行时,可以运用反射机制动态创建代理类的.class文件。

动态代理类与静态代理类最主要不同的是,代理类的字节码不是在程序运行前生成的,而是在程序运行时再虚拟机中程序自动创建的。

实现动态代理的方式很多,比如JDK 自身提供的动态代理,就是主要利用了上面提到的反射机制。

动态代理不止有反射一种实现方式,例如,动态代理可以通过 CGLib 来实现,而 CGLib 是基于 ASM(一个 Java 字节码操作框架)而非反射实现的。

简单来说,动态代理是一种行为方式,而反射或 ASM 只是它的一种实现手段而已。

下面我就分别再详解Java动态代理的2种主流现方式:JDK原生动态代理与CGLib@mikechen

JDK原生动态代理

JDK Proxy 动态代理的实现无需引用第三方类,只需要实现 InvocationHandler 接口,重写 invoke() 方法即可,整个实现代码如下所示:

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

 

/**

 * JDK Proxy 相关示例

 */

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.");

        }

    }

 

    /**

     * JDK Proxy

     */

    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   方法的参数

         * @return

         * @throws InvocationTargetException

         * @throws IllegalAccessException

         */

        @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.

可以看出 JDK Proxy 实现动态代理的核心是实现 Invocation 接口,我们查看 Invocation 的源码,会发现里面其实只有一个 invoke() 方法,源码如下:

public interface InvocationHandler {  public Object invoke(Object proxy, Method method, Object[] args)          throws Throwable;}

这是因为在动态代理中有一个重要的角色也就是代理器,它用于统一管理被代理的对象,显然 InvocationHandler 就是这个代理器,而 invoke() 方法则是触发代理的执行方法,我们通过实现 Invocation 接口来拥有动态代理的能力。

CGLib动态代理实现

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成,CGLIB通过继承方式实现代理。

在使用 CGLib 之前,我们要先在项目中引入 CGLib 框架,在 pom.xml 中添加如下配置:

<!-- https://mvnrepository.com/artifact/cglib/cglib -->

<dependency>

    <groupId>cglib</groupId>

    <artifactId>cglib</artifactId>

    <version>3.3.0</version>

</dependency>

CGLib 实现代码如下:

package com.mikechen.proxydemo;

 

import net.sf.cglib.proxy.Enhancer;

import net.sf.cglib.proxy.MethodInterceptor;

import net.sf.cglib.proxy.MethodProxy;

 

import java.lang.reflect.Method;

 

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();

以上程序的执行结果是:

方法调用前业务处理.

The car is running.

可以看出 CGLib 和 JDK Proxy 的实现代码比较类似,都是通过实现代理器的接口,再调用某一个方法完成动态代理的,唯一不同的是,CGLib 在初始化被代理类时,是通过 Enhancer 对象把代理对象设置为被代理类的子类来实现动态代理的。因此被代理类不能被关键字 final 修饰,如果被 final 修饰,再使用 Enhancer 设置父类时会报错,动态代理的构建会失败。

JDK动态代理与CGLib的区别

1.JDK动态代理具体实现原理

  • 通过实现InvocationHandler接口创建自己的调用处理器;
  • 通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理;
  • 通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;
  • 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;

2、CGLib动态代理

CGLib是一个强大、高性能的Code生产类库,可以实现运行期动态扩展java类,Spring在运行期间通过 CGlib继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程。

3、两者对比

  • JDK动态代理是面向接口的。
  • CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,会失败)。

4、性能对比

1)CGLib所创建的动态代理对象在实际运行时候的性能要比JDK动态代理高不少,有研究表明,大概要高10倍;

2)但是CGLib在创建对象的时候所花费的时间却比JDK动态代理要多很多,有研究表明,大概有8倍的差距;

3)因此,对于singleton的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用CGLib动态代理,反正,则比较适用JDK动态代理。

;