深入探究动态代理:JDK 与 CGLIB 的实现奥秘与差异剖析

开发 前端
当目标对象有接口且注重开发便捷性、遵循接口编程规范时,JDK 动态代理是首选。例如在标准的企业级 Java 应用开发中,服务层接口常使用 JDK 代理实现日志记录、权限验证等 AOP 功能,与 Spring 等框架无缝集成。

在 Java 编程领域,动态代理是一种强大的设计模式,它允许开发者在运行时创建代理对象,对目标对象的行为进行增强、监控或修改,而无需在编译期就确定代理的具体逻辑。这一特性在诸多框架中广泛应用,如 Spring AOP(面向切面编程),为实现横切面关注点的模块化提供了关键支撑。

一、JDK 动态代理实现原理

JDK 动态代理依托于 Java 反射机制构建,核心涉及  java.lang.reflect  包下的几个重要类。

首先,需要定义一个接口,该接口代表目标对象与代理对象共同遵循的行为规范。假设我们有一个简单的图形绘制接口  Shape :

public interface Shape {
    void draw();
}
接着是目标类实现此接口,例如  Circle  类:
public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a circle");
    }
}

创建代理对象的关键在于实现  java.lang.reflect.InvocationHandler  接口,它定义了一个  invoke  方法,用于处理代理对象上所有方法的调用逻辑:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ShapeInvocationHandler implements InvocationHandler {
    private final Object target;
    public ShapeInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在目标方法调用前添加额外逻辑,如日志记录
        System.out.println("Before method invocation");
        Object result = method.invoke(target, args);
        // 在目标方法调用后添加额外逻辑,如性能统计
        System.out.println("After method invocation");
        return result;
    }
}

最后,通过  java.lang.reflect.Proxy  类生成代理对象:

import java.lang.reflect.Proxy;
public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle();
        ShapeInvocationHandler handler = new ShapeInvocationHandler(circle);
        Shape proxyShape = (Shape) Proxy.newProxyInstance(
                Shape.class.getClassLoader(),
                new Class[]{Shape.class},
                handler);
        proxyShape.draw();
    }
}

在上述代码中,当调用代理对象  proxyShape  的  draw  方法时,实际上控制权转移到  ShapeInvocationHandler  的  invoke  方法,在此可以灵活插入前置、后置逻辑,实现对目标对象方法的动态增强。

二、CGLIB 动态代理实现原理

CGLIB(Code Generation Library)动态代理采取了截然不同的字节码生成策略。它不依赖接口,而是直接对目标类进行字节码扩展。

引入 CGLIB 相关依赖后,以同样的  Circle  类为例(此时无需实现接口):

public class Circle {
    public void draw() {
        System.out.println("Drawing a circle");
    }
}

创建一个实现  net.sf.cglib.proxy.MethodInterceptor  接口的拦截器类,它类似于 JDK 代理中的  InvocationHandler :

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibShapeInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("CGLIB: Before method call");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("CGLIB: After method call");
        return result;
    }
}

利用  net.sf.cglib.proxy.Enhancer  类生成代理对象:

import net.sf.cglib.proxy.Enhancer;
public class Main {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Circle.class);
        enhancer.setCallback(new CglibShapeInterceptor());
        Circle proxyCircle = (Circle) enhancer.create();
        proxyCircle.draw();
    }
}

这里, Enhancer  通过修改目标类的字节码,在原始方法执行前后织入自定义逻辑,生成的代理对象继承自目标类,能直接调用目标类非  private  的方法,无需像 JDK 代理那样依赖接口。

三、JDK 动态代理与 CGLIB 动态代理的区别

(一)实现基础

- JDK 动态代理:基于接口实现,要求目标对象必须实现至少一个接口。它利用 Java 反射在运行时动态生成代理类,该代理类同样实现目标对象的接口,通过接口回调机制将方法调用转发至  InvocationHandler  处理。

- CGLIB 动态代理:基于继承机制,直接操作目标类的字节码,生成目标类的子类作为代理对象。它通过重写子类方法,在其中插入拦截逻辑,调用父类(即目标类)原始方法实现功能,无需目标对象有接口实现。

(二)性能表现

- 在创建代理对象阶段,JDK 动态代理相对较快,因为它只需基于接口信息利用反射生成简单代理类结构;而 CGLIB 需要通过复杂的字节码生成技术创建子类,耗时较长。

- 但在方法调用时,JDK 动态代理由于每次都要经过反射查找方法等操作,性能开销较大;CGLIB 代理对象调用方法类似普通对象调用,因为方法已在字节码层面重写优化,若频繁调用代理方法,CGLIB 在性能上更具优势。

(三)适用场景

- 当目标对象有接口且注重开发便捷性、遵循接口编程规范时,JDK 动态代理是首选。例如在标准的企业级 Java 应用开发中,服务层接口常使用 JDK 代理实现日志记录、权限验证等 AOP 功能,与 Spring 等框架无缝集成。

- 若目标对象没有接口,或者需要对 final 修饰的类进行代理(JDK 代理无法做到,因 final 类不可继承),CGLIB 动态代理便能大显身手。像一些遗留代码改造、底层工具类增强场景,CGLIB 可突破接口限制,灵活实现代理需求。

JDK 动态代理与 CGLIB 动态代理各具特色,它们从不同角度为 Java 开发者提供了动态代理的实现路径,深入理解二者原理与区别,有助于在面对复杂多变的编程需求时,精准选择合适的代理方式,打造高效、健壮的 Java 应用。

责任编辑:武晓燕 来源: 程序员conan
相关推荐

2024-01-04 07:42:44

JavaCGLIBJDK

2023-12-06 08:23:44

代理模式设计模式

2021-04-22 09:58:15

JDK代理动态

2022-09-01 10:40:29

SpringAOPJDK

2021-07-14 11:07:56

AOPJDKCglib

2024-03-07 08:22:51

Java机制元数据

2023-09-28 09:03:56

开源搜索分析引擎

2024-09-05 09:35:58

CGLIBSpring动态代理

2024-01-26 08:33:14

JDK17JDK11版本

2012-09-28 10:20:14

IBMdw

2010-08-16 10:25:23

DIVSPAN

2012-09-27 09:47:43

SpringJava面向对象

2010-09-01 09:29:51

CSS层叠CSS继承

2020-10-25 17:11:29

JDK代理监控

2024-01-31 23:47:17

i++++i编码

2023-06-07 07:43:57

数据库JOIN类型

2013-05-07 11:43:47

2022-11-15 09:57:51

Java接口

2010-09-30 09:16:04

cookieJ2ME

2011-11-24 21:03:10

ibmdw
点赞
收藏

51CTO技术栈公众号