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

513 阅读3分钟

本文已参与掘金创作者训练营第三期「话题写作」赛道,详情查看:掘力计划|创作者训练营第三期正在进行,「写」出个人影响力

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  !!!

源码分析

按照时序图我们先看 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  !!!!

源码分析

如图首先设置被代理类,然后设置自己写的方法拦截器,然后创建创建代理类的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关键字修饰的类不可变只能被引用不能被修改