动态代理

starlin 1,115 2018-07-09

简介

动态代理是程序在运行期间动态构建代理对象和动态调用代理方法的一种机制,众说周知,实现动态代理的方式有JDK Proxy 和 CGLib ,那么他们区别是什么了?

动态代理的常用实现方式是反射。反射机制是指程序在运行期间可以访问、检测和修改其本身状态或行为的一种能力,使用反射我们可以调用任意一个类对象,以及类对象中包含的属性及方法。

但动态代理不止有反射一种实现方式,例如,动态代理可以通过 CGLib 来实现,而 CGLib 是基于 ASM(一个 Java 字节码操作框架)而非反射实现的。简单来说,动态代理是一种行为方式,而反射或 ASM 只是它的一种实现手段而已。

JDK Proxy 和 CGLib 的区别主要体现在以下几个方面:

  • JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现;
  • Java 对 JDK Proxy 提供了稳定的支持,并且会持续的升级和更新 JDK Proxy,例如 Java 8 版本中的 JDK Proxy 性能相比于之前版本提升了很多;
  • JDK Proxy 是通过拦截器加反射的方式实现的;
  • JDK Proxy 只能代理继承接口的类;
  • JDK Proxy 实现和调用起来比较简单;
  • CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;
  • CGLib 无需通过接口来实现,它是通过实现子类的方式来完成调用的。

Java 中实现动态代理主要有两种模式:一种是使用 JDK,另外一种是使用 CGLib。

  • JDK 方式是面向接口的,主 要的相关类是 InvocationHandler 和 Proxy;
  • CGLib 可以代理普通类,主要的相关类是 MethodInterceptor 和 Enhancer。

在新版本的 Java 中差别并不是很大,Spring 选用了 CGLib,主要是因为它能够代理普通类的缘故。

JDKProxy动态代理

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

    public class JDKProxyTest {
        static interface Car{
            void running();
        }
    
        static class Bus implements Car{
            @Override
            public void running() {
                System.out.println("Bus is running");
            }
        }
    
        static class Taxi implements Car {
            @Override
            public void running() {
                System.out.println("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);
            }
    
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = method.invoke(target, args);
                return result;
            }
        }
    
        public static void main(String[] args) {
            JDKProxy jdkProxy = new JDKProxy();
            Car carInstance = (Car)jdkProxy.getInstance(new Taxi());
            carInstance.running();
        }
    }

运行结果:

Taxi is running

CGLib动态代理

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

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>

演示代码如下:

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

    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 {
            Object result = methodProxy.invokeSuper(o, objects);
            return result;
        }
    }

    public static void main(String[] args) {
        CGlibProxy cGlibProxy = new CGlibProxy();
        Car car = (Car) cGlibProxy.getInstance(new Car());
        car.running();
    }
}

运行结果:

Car is running

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

Lombok原理

Lombok的作用就不用过多介绍了,大家都懂的,来看看lombok的原理
其实Lombok 的实现和反射没有任何关系,Lombok 是基于 Java 1.6 实现的 JSR 269: Pluggable Annotation Processing API 来实现的,也就是通过编译期自定义注解处理器来实现的,在编译期阶段,当 Java 源码被抽象成语法树(AST)之后,Lombok 会根据自己的注解处理器动态修改 AST,增加新的代码(节点),在这一切执行之后就生成了最终的字节码(.class)文件,
这就是 Lombok 的执行原理。

Spring动态代理

Spring 框架中同时使用了两种动态代理 JDK Proxy 和 CGLib,当 Bean 实现了接口时,Spring 就会使用 JDK Proxy,在没有实现接口时就会使用 CGLib,
我们也可以在配置中指定强制使用 CGLib,只需要在 Spring 配置中添加 <aop:aspectj-autoproxy proxy-target-class="true"/> 即可。

其他

当面试官问动态代理的时候,经常会问到它和静态代理的区别?静态代理其实就是事先写好代理类,可以手工编写也可以使用工具生成,但它的缺点是每个业务类都要对应一个代理类,特别不灵活也不方便,于是就有了动态代理。

动态代理的常见使用场景有 RPC 框架的封装、AOP(面向切面编程)的实现、JDBC 的连接等。


# Java基础