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关键字修饰的类不可变只能被引用不能被修改


相关文章
|
7天前
|
安全 Java 开发者
AOP中的JDK动态代理与CGLIB动态代理:深度解析与实战模拟
【11月更文挑战第21天】面向切面编程(AOP,Aspect-Oriented Programming)是一种编程范式,它通过将横切关注点(cross-cutting concerns)与业务逻辑分离,以提高代码的可维护性和可重用性。在Java开发中,AOP的实现离不开动态代理技术,其中JDK动态代理和CGLIB动态代理是两种常用的方式。本文将从背景、历史、功能点、业务场景、底层逻辑等多个维度,深度解析这两种代理方式的区别,并通过Java示例进行模拟和比较。
25 4
|
28天前
|
设计模式 Java API
[Java]静态代理与动态代理(基于JDK1.8)
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
24 0
[Java]静态代理与动态代理(基于JDK1.8)
|
1月前
|
Java
【编程进阶知识】静态代理、JDK动态代理及Cglib动态代理各自存在的缺点及代码示例
本文介绍了三种Java代理模式:静态代理、JDK动态代理和Cglib动态代理。静态代理针对特定接口或对象,需手动编码实现;JDK动态代理通过反射机制实现,适用于所有接口;Cglib动态代理则基于字节码技术,无需接口支持,但需引入外部库。每种方法各有优缺点,选择时应根据具体需求考虑。
21 1
|
1月前
|
Java
Java基础之 JDK8 HashMap 源码分析(中间写出与JDK7的区别)
这篇文章详细分析了Java中HashMap的源码,包括JDK8与JDK7的区别、构造函数、put和get方法的实现,以及位运算法的应用,并讨论了JDK8中的优化,如链表转红黑树的阈值和扩容机制。
27 1
|
1月前
|
Dubbo Java 应用服务中间件
剖析Tomcat线程池与JDK线程池的区别和联系!
剖析Tomcat线程池与JDK线程池的区别和联系!
111 0
剖析Tomcat线程池与JDK线程池的区别和联系!
|
3月前
|
开发者 C# 容器
【独家揭秘】当WPF邂逅DirectX:看这两个技术如何联手打造令人惊艳的高性能图形渲染体验,从环境搭建到代码实践,一步步教你成为图形编程高手
【8月更文挑战第31天】本文通过代码示例详细介绍了如何在WPF应用中集成DirectX以实现高性能图形渲染。首先创建WPF项目并使用SharpDX作为桥梁,然后在XAML中定义承载DirectX内容的容器。接着,通过C#代码初始化DirectX环境,设置渲染逻辑,并在WPF窗口中绘制图形。此方法适用于从简单2D到复杂3D场景的各种图形处理需求,为WPF开发者提供了高性能图形渲染的技术支持和实践指导。
230 0
|
2月前
|
Java
安装JDK18没有JRE环境的解决办法
安装JDK18没有JRE环境的解决办法
337 3
|
3月前
|
Java 关系型数据库 MySQL
"解锁Java Web传奇之旅:从JDK1.8到Tomcat,再到MariaDB,一场跨越数据库的冒险安装盛宴,挑战你的技术极限!"
【8月更文挑战第19天】在Linux上搭建Java Web应用环境,需安装JDK 1.8、Tomcat及MariaDB。本指南详述了使用apt-get安装OpenJDK 1.8的方法,并验证其版本。接着下载与解压Tomcat至`/usr/local/`目录,并启动服务。最后,通过apt-get安装MariaDB,设置基本安全配置。完成这些步骤后,即可验证各组件的状态,为部署Java Web应用打下基础。
59 1
|
3月前
|
Oracle Java 关系型数据库
Mac安装JDK1.8
Mac安装JDK1.8
710 4
|
1月前
|
Oracle Java 关系型数据库
jdk17安装全方位手把手安装教程 / 已有jdk8了,安装JDK17后如何配置环境变量 / 多个不同版本的JDK,如何配置环境变量?
本文提供了详细的JDK 17安装教程,包括下载、安装、配置环境变量的步骤,并解释了在已有其他版本JDK的情况下如何管理多个JDK环境。
824 0