JDK 动态代理与 CGLIB 有哪些区别?

简介: Jdk 动态代理利用拦截器(必须实现InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用。InvokeHandler来处理。

JDK 动态代理和 CGLIB


Jdk 动态代理


利用拦截器(必须实现InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用。InvokeHandler来处理。


举个例子:


// 接口类
public interface OrderService {
    OrderDto createOrder(OrderDto orderDto);
}
// 实现类
public class OrderServiceImpl implements OrderService {
    @Override
    public OrderDto createOrder(OrderDto orderDto) {
        System.out.println("OrderServiceImpl#createOrder !!!");
        return null;
    }
}
// mian 方法
public static void main(String[] args) {
    OrderServiceImpl orderServiceImpl = new OrderServiceImpl();
    OrderService orderService = (OrderService)Proxy
        .newProxyInstance(OrderServiceHandler.class.getClassLoader(), orderServiceImpl.getClass().getInterfaces(),
                          (o, method, args1) -> {
                              System.out.println("before !!!");
                              Object invoke = method.invoke(orderServiceImpl, args1);
                              System.out.println("after  !!!");
                              return invoke;
                          });
    orderService.createOrder(new OrderDto());
}


输出结果如下:


before !!!
OrderServiceImpl#createOrder !!!
after  !!!


源码分析


image.png


按照时序图我们先看 newProxyInstance 方法:


public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
    {
        // ....
        // 查找或者生成代理类
        Class<?> cl = getProxyClass0(loader, intfs);
        // 使用代理类构造函数实例化对象
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }


再看关键的 getProxyClass0


/**
 * Generate a proxy class.  Must call the checkProxyAccess method
 * to perform permission checks before calling this.
 */
private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    // If the proxy class defined by the given loader implementing
    // the given interfaces exists, this will simply return the cached copy;
    // otherwise, it will create the proxy class via the ProxyClassFactory
    return proxyClassCache.get(loader, interfaces);
}


再看看 ProxyClassFactory 类的 apply 方法


public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class<?> intf : interfaces) {
            // ... 
            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;
      // 通过接口生成代理类
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
        }


Cglib 动态代理


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


举个例子:


// 没有接口的实现类
public class OrderService {
    public OrderDto createOrder(OrderDto orderDto) {
        System.out.println("OrderService#createOrder !!!");
        return null;
    }
}
// main 方法
public static void main(String[] args) {
    OrderService orderService = new OrderService();
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(orderService.getClass());
    enhancer.setCallback((MethodInterceptor)(obj, method, args1, proxy) -> {
        System.out.println("before !!!!");
        Object o = proxy.invokeSuper(obj, args1);
        System.out.println("after  !!!!");
        return o;
    });
    OrderService orderServiceProxy = (OrderService) enhancer.create();
    orderServiceProxy.createOrder(new OrderDto());
}


输出结果如下:


before !!!!
OrderService#createOrder !!!
after  !!!!


源码分析


image.png


如图首先设置被代理类,然后设置自己写的方法拦截器,然后创建创建代理类的Class对象,并调用代理类的CGLIB$SET_THREAD_CALLBACKS方法设置回调。


常见问题


什么时候用 cglib 什么时候用 jdk 动态代理?


1、目标对象生成了接口 默认用JDK动态代理;


2、如果目标对象使用了接口,可以强制使用cglib;


3、如果目标对象没有实现接口,必须采用cglib库,Spring会自动在JDK动态代理和cglib之间转换;


JDK 动态代理和 cglib 字节码生成的区别?


1、JDK动态代理只能对实现了接口的类生成代理,而不能针对类


2、Cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法的增强,但是因为采用的是继承,所以该类或方法最好不要生成final,对于final类或方法,是无法继承的。


Cglib 比 JDK 快?


1、cglib底层是ASM字节码生成框架,但是字节码技术生成代理类,在JDK1.6之前比使用java反射的效率要高


2、在jdk6之后逐步对JDK动态代理进行了优化,在调用次数比较少时效率高于cglib代理效率


3、只有在大量调用的时候cglib的效率高,但是在1.8的时候JDK的效率已高于cglib


4、Cglib不能对声明final的方法进行代理,因为cglib是动态生成代理对象,final关键字修饰的类不可变只能被引用不能被修改


相关文章
|
6月前
|
存储 算法 Java
JvM JDK JRE 三者区别与联系详解
本文深入解析了Java编程中的三个核心概念:JVM(Java虚拟机)、JDK(Java开发工具包)和JRE(Java运行环境)。JVM是执行Java字节码的虚拟计算机,实现“一次编译,到处运行”;JDK包含JRE及开发工具,用于编写和调试Java程序;JRE负责运行已编译的Java程序。文章详细阐述了它们的功能、组成及应用场景,并通过实例说明其在实际开发中的作用,帮助开发者理解三者联系与区别,提升开发效率与问题解决能力。适合Java初学者及进阶开发者学习参考。
1021 3
|
9月前
|
Java Spring
JDK动态代理和CGLIB动态代理的区别
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理: ● JDK动态代理只提供接口的代理,不支持类的代理Proxy.newProxyInstance(类加载器, 代理对象实现的所有接口, 代理执行器) ● CGLIB是通过继承的方式做的动态代理 , 如果某个类被标记为final,那么它是无法使用 CGLIB做动态代理的。Enhancer.create(父类的字节码对象, 代理执行器)
|
9月前
|
监控 Java API
JDK动态代理和CGLIB动态代理
Java动态代理允许在运行时创建代理对象,增强或拦截目标类方法的执行。主要通过两种方式实现:JDK动态代理和CGLIB动态代理。JDK动态代理基于接口,利用`java.lang.reflect.Proxy`类和`InvocationHandler`接口;CGLIB则通过字节码技术生成目标类的子类作为代理,适用于未实现接口的类。两者均用于在方法执行前后添加额外逻辑,如日志记录、权限控制等,广泛应用于AOP框架中。
357 2
|
10月前
|
监控 Java API
JDK动态代理和CGLIB动态代理
Java动态代理允许在运行时创建代理对象,增强或拦截目标类的方法调用,无需修改原代码。它有两种主要实现方式:JDK动态代理和CGLIB动态代理。 - **JDK动态代理**:通过`java.lang.reflect.Proxy`类和`InvocationHandler`接口实现,适用于实现了接口的类。它在方法调用前后插入额外逻辑,如日志记录、权限控制等。 - **CGLIB动态代理**:基于字节码技术,为未实现接口的类生成子类作为代理,重写父类方法以添加增强逻辑。适用于没有接口的类,但要求目标类不能是`final`类或方法。
149 1
|
10月前
|
Java API 数据安全/隐私保护
探索Java动态代理的奥秘:JDK vs CGLIB
动态代理是一种在 运行时动态生成代理类的技术,无需手动编写代理类代码。它通过拦截目标方法的调用,实现对核心逻辑的 无侵入式增强(如日志、事务、权限控制等)。
350 0
探索Java动态代理的奥秘:JDK vs CGLIB
安装JDK18没有JRE环境的解决办法
安装JDK18没有JRE环境的解决办法
1312 141
|
5月前
|
存储 Ubuntu 安全
在Ubuntu 16.04上安装openjdk-6/7/8-jdk的步骤
在整个安装过程中,你可能需要管理员权限,因此你可能要使用 `sudo` 来获取必要的权限。记得做完每一个步骤后,都要检查输出,以确保没有发生错误,并且每项操作都成功完成。如果在安装过程中遇到问题,查看 `/var/log/` 下的日志文件对于问题的解决可能是有帮助的。
386 21
|
5月前
|
IDE Ubuntu Java
在Ubuntu18.04安装兼容JDK 8的Eclipse集成开发环境的指南。
完成以上步骤后,您将在Ubuntu 18.04系统上成功安装并配置了Eclipse IDE,它将与JDK 8兼容,可以开始进行Java开发工作。如果遇到任何问题,请确保每一步骤都正确执行,并检查是否所有路径都与您的具体情况相匹配。
260 11
|
4月前
|
Ubuntu Java Android开发
在Ubuntu 18.04上安装与JDK 8兼容的Eclipse版本的步骤。
安装过程结束后,您就可以开始使用Eclipse来开发您的Java项目了,并且确保它与JDK 8兼容无误。这个过程涉及的是一个基本的安装流程,针对使用Java 8的用户,Eclipse的其他配置和插件安装根据个人开发环境和需求来定制。
347 0
|
7月前
|
Java 关系型数据库 MySQL
在Linux平台上进行JDK、Tomcat、MySQL的安装并部署后端项目
现在,你可以通过访问http://Your_IP:Tomcat_Port/Your_Project访问你的项目了。如果一切顺利,你将看到那绚烂的胜利之光照耀在你的项目之上!
421 41