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


相关文章
|
4小时前
|
Java 编译器 API
【面试问题】JDK 和 JRE 的区别?
【1月更文挑战第27天】【面试问题】JDK 和 JRE 的区别?
|
4小时前
|
Java 开发框架 XML
JDK、JRE、Java SE、Java EE和Java ME有什么区别?
JDK、JRE、Java SE、Java EE和Java ME有什么区别?
|
4小时前
|
存储 监控 Oracle
简单介绍JDK、JRE、JVM三者区别
1995年Sun公司在SunWorld大会上发布了Java1.0版本,并提出可那个响彻云霄的“一次运行,随处编译”的口号,这句话充分的展示出了Java的跨平台特性,提到跨平台,便衍生出了我们今天需要讨论的话题,JDK、JRE、JVM三者的区别
21 2
|
4小时前
|
Java 程序员 API
浅谈JDK动态代理
浅谈JDK动态代理
33 1
|
4小时前
|
存储 安全 Java
JDK、JRE 和 JVM 的区别和联系
JDK、JRE 和 JVM 的区别和联系
97 0
|
4小时前
|
Java
关于JDK动态代理
关于JDK动态代理的一些理解
15 0
|
5小时前
|
设计模式 Java API
[Java]静态代理、动态代理(基于JDK1.8)
本篇文章主要是对静态代理和动态代理实现思路的简述,以示例为主,少涉及理论。 如果文中阐述不全或不对的,多多交流。
56 1
[Java]静态代理、动态代理(基于JDK1.8)
|
4小时前
|
设计模式 安全 Java
深入理解Spring Boot AOP:CGLIB代理与JDK动态代理的完全指南
深入理解Spring Boot AOP:CGLIB代理与JDK动态代理的完全指南
398 1
|
4小时前
|
Java 数据安全/隐私保护
【面试问题】JDK 动态代理与 CGLIB 区别?
【1月更文挑战第27天】【面试问题】JDK 动态代理与 CGLIB 区别?
|
4小时前
|
IDE Java Shell
02|手把手教你安装JDK与配置主流IDE
02|手把手教你安装JDK与配置主流IDE
21 0