上周五在电面试阿里时,被问到Spring AOP中JDK和CGLib动态代理的区别?于是搜集网上相关知识点,在此整理一下,供大家参考。

JDK和CGLib动态代理实现
动态代理在Java中有着广泛的应用,如Spring AOP,Hibernate数据查询、测试框架的后端mock、RPC,Java注解对象获取等。动态代理的代理关系是在运行时期确定的。在讲解两种动态代理区别之前,首先通过实例代码分别实现两种动态代理,直观感受一下动态代理是个什么东西,是如何实现的,然后通过分析其实现方式和原理,阐述两种动态代理的区别。

Jdk原生动态代理

IHelloService接口

public interface IHelloService {
    void sayHello();
}

HelloServiceImpl实现类

public class HelloServiceImpl implements IHelloService {
    @Override
    public void sayHello() {
        System.out.println("Jdk say Hello");
    }
}

JdkDynamicProxy实现InvocationHandler接口类

public class JdkDynamicProxy implements InvocationHandler {

    private Object targetObject;

    public Object newProxyInstance(Object targetObject) {
        //将目标对象传入进行代理
        this.targetObject = targetObject;
        //返回代理对象
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxyObject, Method method, Object[] args) throws Throwable {
        System.out.println("JdkDynamicProxy," + method.getName() + "方法,执行之前");
        return method.invoke(this.targetObject, args);
    }
}

在该类除了覆写invoke()方法,还封装了一个生成代理类的方法,该方法可以不放置在此的。上述代码的关键是Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) 方法,该方法会根据指定的参数动态创建代理对象。三个参数的意义如下:

loader,指定代理对象的类加载器;
interfaces,代理对象需要实现的接口,可以同时指定多个接口;
handler,方法调用的实际处理者,代理对象的方法调用都会转发到这里(*注意1)。
newProxyInstance()会返回一个实现了指定接口的代理对象,对该对象的所有方法调用都会转发给InvocationHandler.invoke()方法。动态代理神奇的地方就是:

代理对象是在程序运行时产生的,而不是编译期;
对代理对象的所有接口方法调用都会转发到InvocationHandler.invoke()方法,在invoke()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等。
注意1:对于从Object中继承的方法,JDK Proxy会把hashCode()、equals()、toString()这三个非接口方法转发给InvocationHandler,其余的Object方法则不会转发。详见JDK Proxy官方文档。

测试代码:

@Test
public void jdkDynamicProxyPatternTest() {
    IHelloService helloService = new HelloServiceImpl();
    IHelloService proxySubject = (IHelloService) new JdkDynamicProxy2().newProxyInstance(helloService);
    proxySubject.sayHello();
}

运行结果:

JDK动态代理整个实现过程其实很简单,包括三个步骤:

定义一个接口IHelloService,大家都知道Jdk的动态代理是基于接口,这个就是那个接口。
编写一个实现该接口的类HelloServiceImpl,这个就是目标对象,也就是被代理的对象类。
最后编写一个实现InvocationHandler接口的类JdkDynamicProxy,代理类的方法调用都会被转发到该类的invoke()方法。

CGLib动态代理

HelloService没有实现任何接口的类

public class HelloService {
    public void sayHello() {
        System.out.println("Cglib Say Hello");
    }
}

CglibDynamicProxy实现MethodInterceptor接口类

public class CglibDynamicProxy implements MethodInterceptor {

    // 维护目标对象
    private Object target;

    public CglibDynamicProxy(Object target) {
        this.target = target;
    }

    //给目标对象创建一个代理对象
    public Object newProxyInstance(){
        //1.工具类
        Enhancer en = new Enhancer();
        //2.设置父类
        en.setSuperclass(target.getClass());
        //3.设置回调函数
        en.setCallback(this);
        //4.创建子类(代理对象)
        return en.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("CglibDynamicProxy,"+ method.getName()+"方法,执行之前");
        Object returnValue = method.invoke(target, args);
        System.out.println("CglibDynamicProxy,"+ method.getName()+"方法,执行之后");
        return returnValue;
    }
}

上述代码中,通过CGLIB的Enhancer来指定要代理的目标对象(target.getClass())、实际处理代理逻辑的对象(this),最终通过调用create()方法得到代理对象,对这个对象所有非final方法的调用都会转发给MethodInterceptor.intercept()方法,在intercept()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等。CGLIG中MethodInterceptor的作用跟JDK代理中的InvocationHandler很类似,都是方法调用的中转站。

注意:对于从Object中继承的方法,CGLIB代理也会进行代理,如hashCode()、equals()、toString()等,但是getClass()、wait()等方法不会,因为它是final方法,CGLIB无法代理。

测试代码:

@Test
public void cglibDynamicProxyPatternTest() {
    HelloService cglibProxySubject = (HelloService) new CglibDynamicProxy(new HelloService()).newProxyInstance();
    cglibProxySubject.sayHello();
}

运行结果:

JDK和CGLib动态代理分析
自Java 1.3以后,Java提供了动态代理技术,允许开发者在运行期创建接口的代理实例,后来这项技术被用到了Spring的很多地方。JDK动态代理主要涉及java.lang.reflect包下边的两个类:Proxy和InvocationHandler。其中,InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑(如:我们在方法执行前后打印的日志,本文只是为了演示,实际的应用一般不会只是简单的打印日志的),并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起。

JDK动态代理的话,他有一个限制,就是它只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,如何创建动态代理实例哪?答案就是CGLib。

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。

JDK和CGLib动态代理区别

1、JDK动态代理具体实现原理:

通过实现InvocationHandler接口创建自己的调用处理器;
通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理;
通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;
通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;
JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,Spring通过Java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。

2、CGLib动态代理:

利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

3、两者对比:

JDK动态代理是面向接口的。
CGLib动态代理是通过字节码底层继承要代理类来实现,因此如果被代理类被final关键字所修饰,会失败。
4、使用注意:

如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制);
如果要被代理的对象不是个实现类那么,Spring会强制使用CGLib来实现动态代理。

JDK和CGLib动态代理性能对比

关于两者之间的性能的话,网上有人对于不通版本的jdk进行测试,经过多次试验,测试结果大致是这样的,在1.6和1.7的时候,JDK动态代理的速度要比CGLib动态代理的速度要慢,但是并没有教科书上的10倍差距,在JDK1.8的时候,JDK动态代理的速度已经比CGLib动态代理的速度快很多了,但是JDK动态代理和CGLIB动态代理的适用场景还是不一样的哈!

链接:https://blog.csdn.net/shallynever/article/details/103351299

作者:买菜做饭