SpringBoot中5种动态代理实现

动态代理允许我们在不修改源代码的情况下,为对象增加额外的行为。在SpringBoot应用中,动态代理被广泛用于实现事务管理、缓存、安全控制、日志记录等横切关注点。

1. JDK动态代理:Java原生的代理方案

实现原理

JDK动态代理是Java标准库提供的代理机制,基于java.lang.reflect.Proxy类和InvocationHandler接口实现。它通过反射在运行时动态创建接口的代理实例。

核心代码示例

java 复制代码
public class JdkDynamicProxyDemo {
    interface UserService {
        void save(User user);
        User find(Long id);
    }
    
    // 实现类
    static class UserServiceImpl implements UserService {
        @Override
        public void save(User user) {
            System.out.println("保存用户: " + user.getName());
        }
        
        @Override
        public User find(Long id) {
            System.out.println("查找用户ID: " + id);
            return new User(id, "用户" + id);
        }
    }
    
    // 调用处理器
    static class LoggingInvocationHandler implements InvocationHandler {
        private final Object target;
        
        public LoggingInvocationHandler(Object target) {
            this.target = target;
        }
        
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("Before: " + method.getName());
            long startTime = System.currentTimeMillis();
            
            Object result = method.invoke(target, args);
            
            long endTime = System.currentTimeMillis();
            System.out.println("After: " + method.getName() + ", 耗时: " + (endTime - startTime) + "ms");
            
            return result;
        }
    }
    
    public static void main(String[] args) {
        // 创建目标对象
        UserService userService = new UserServiceImpl();
        
        // 创建代理对象
        UserService proxy = (UserService) Proxy.newProxyInstance(
            userService.getClass().getClassLoader(),
            userService.getClass().getInterfaces(),
            new LoggingInvocationHandler(userService)
        );
        
        // 调用代理方法
        proxy.save(new User(1L, "张三"));
        User user = proxy.find(2L);
    }
}

优点

  1. JDK标准库自带:无需引入额外依赖,减少了项目体积
  2. 生成代码简单:代理逻辑集中在InvocationHandler中,易于理解和维护
  3. 性能相对稳定:在JDK 8后的版本中,性能有明显提升

局限性

  1. 只能代理接口:被代理类必须实现接口,无法代理类
  2. 反射调用开销:每次方法调用都需通过反射机制,有一定性能损耗
  3. 无法拦截final方法:无法代理被final修饰的方法

Spring中的应用

在Spring中,当Bean实现了接口时,默认使用JDK动态代理。这可以通过配置修改:

ini 复制代码
@EnableAspectJAutoProxy(proxyTargetClass = false) // 默认值为false,表示优先使用JDK动态代理

2. CGLIB代理:基于字节码的强大代理

实现原理

CGLIB(Code Generation Library)是一个强大的高性能字节码生成库,它通过继承被代理类生成子类的方式实现代理。Spring从3.2版本开始将CGLIB直接集成到框架中。

核心代码示例

java 复制代码
public class CglibProxyDemo {
    // 不需要实现接口的类
    static class UserService {
        public void save(User user) {
            System.out.println("保存用户: " + user.getName());
        }
        
        public User find(Long id) {
            System.out.println("查找用户ID: " + id);
            return new User(id, "用户" + id);
        }
    }
    
    // CGLIB方法拦截器
    static class LoggingMethodInterceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("Before: " + method.getName());
            long startTime = System.currentTimeMillis();
            
            // 调用原始方法
            Object result = proxy.invokeSuper(obj, args);
            
            long endTime = System.currentTimeMillis();
            System.out.println("After: " + method.getName() + ", 耗时: " + (endTime - startTime) + "ms");
            
            return result;
        }
    }
    
    public static void main(String[] args) {
        // 创建CGLIB代理
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class);
        enhancer.setCallback(new LoggingMethodInterceptor());
        
        // 创建代理对象
        UserService proxy = (UserService) enhancer.create();
        
        // 调用代理方法
        proxy.save(new User(1L, "张三"));
        User user = proxy.find(2L);
    }
}

优点

  1. 可以代理类:不要求目标类实现接口,应用场景更广泛
  2. 性能较高:通过生成字节码而非反射调用,方法调用性能优于JDK代理
  3. 功能丰富:支持多种回调类型,如LazyLoader、Dispatcher等
  4. 集成到Spring:Spring框架已内置CGLIB,无需额外依赖

局限性

  1. 无法代理final类和方法:由于使用继承机制,无法代理final修饰的类或方法
  2. 构造函数调用:在生成代理对象时会调用目标类的构造函数,可能导致意外行为
  3. 复杂性增加:生成的字节码复杂度高,调试困难
  4. 对Java版本敏感:在不同Java版本间可能存在兼容性问题

Spring中的应用

在Spring中,当Bean没有实现接口或配置了proxyTargetClass=true时,使用CGLIB代理:

ini 复制代码
@EnableAspectJAutoProxy(proxyTargetClass = true) // 强制使用CGLIB代理

3. ByteBuddy:现代化的字节码操作库

实现原理

ByteBuddy是一个相对较新的字节码生成和操作库,设计更加现代化,API更加友好。它可以创建和修改Java类,而无需理解底层的JVM指令集。

核心代码示例

java 复制代码
public class ByteBuddyProxyDemo {
    interface UserService {
        void save(User user);
        User find(Long id);
    }
    
    static class UserServiceImpl implements UserService {
        @Override
        public void save(User user) {
            System.out.println("保存用户: " + user.getName());
        }
        
        @Override
        public User find(Long id) {
            System.out.println("查找用户ID: " + id);
            return new User(id, "用户" + id);
        }
    }
    
    static class LoggingInterceptor {
        @RuntimeType
        public static Object intercept(@Origin Method method, @SuperCall Callable<?> callable,
                                       @AllArguments Object[] args) throws Exception {
            System.out.println("Before: " + method.getName());
            long startTime = System.currentTimeMillis();
            
            Object result = callable.call();
            
            long endTime = System.currentTimeMillis();
            System.out.println("After: " + method.getName() + ", 耗时: " + (endTime - startTime) + "ms");
            
            return result;
        }
    }
    
    public static void main(String[] args) throws Exception {
        UserService userService = new UserServiceImpl();
        
        // 创建ByteBuddy代理
        UserService proxy = new ByteBuddy()
            .subclass(UserServiceImpl.class)
            .method(ElementMatchers.any())
            .intercept(MethodDelegation.to(new LoggingInterceptor()))
            .make()
            .load(UserServiceImpl.class.getClassLoader())
            .getLoaded()
            .getDeclaredConstructor()
            .newInstance();
        
        // 调用代理方法
        proxy.save(new User(1L, "张三"));
        User user = proxy.find(2L);
    }
}

优点

  1. 流畅的API:提供链式编程风格,代码更加可读
  2. 性能卓越:在多项基准测试中,性能优于CGLIB和JDK代理
  3. 类型安全:API设计更注重类型安全,减少运行时错误
  4. 支持Java新特性:对Java 9+模块系统等新特性有更好的支持
  5. 功能丰富:支持方法重定向、字段访问、构造函数拦截等多种场景

局限性

  1. 额外依赖:需要引入额外的依赖库
  2. 学习曲线:API虽然流畅,但概念较多,有一定学习成本
  3. 在Spring中集成度不高:需要自定义配置才能在Spring中替代默认代理机制

Spring中的应用

ByteBuddy虽然不是Spring默认的代理实现,但可以通过自定义ProxyFactory来集成:

typescript 复制代码
@Configuration
public class ByteBuddyProxyConfig {
    @Bean
    public AopConfigurer byteBuddyAopConfigurer() {
        return new AopConfigurer() {
            @Override
            public void configureProxyCreator(Object bean, String beanName) {
                // 配置ByteBuddy作为代理创建器
                // 实现细节略
            }
        };
    }
}

4. Javassist:更易用的字节码编辑库

实现原理

Javassist是一个开源的Java字节码操作库,提供了两个层次的API:源代码级别和字节码级别。它允许开发者用简单的Java语法直接编辑字节码,无需深入了解JVM规范。

核心代码示例

java 复制代码
public class JavassistProxyDemo {
    interface UserService {
        void save(User user);
        User find(Long id);
    }
    
    static class UserServiceImpl implements UserService {
        @Override
        public void save(User user) {
            System.out.println("保存用户: " + user.getName());
        }
        
        @Override
        public User find(Long id) {
            System.out.println("查找用户ID: " + id);
            return new User(id, "用户" + id);
        }
    }
    
    public static void main(String[] args) throws Exception {
        ProxyFactory factory = new ProxyFactory();
        
        // 设置代理接口
        factory.setInterfaces(new Class[] { UserService.class });
        
        // 创建方法过滤器
        MethodHandler handler = new MethodHandler() {
            private UserService target = new UserServiceImpl();
            
            @Override
            public Object invoke(Object self, Method method, Method proceed, Object[] args) throws Throwable {
                System.out.println("Before: " + method.getName());
                long startTime = System.currentTimeMillis();
                
                Object result = method.invoke(target, args);
                
                long endTime = System.currentTimeMillis();
                System.out.println("After: " + method.getName() + ", 耗时: " + (endTime - startTime) + "ms");
                
                return result;
            }
        };
        
        // 创建代理对象
        UserService proxy = (UserService) factory.create(new Class<?>[0], new Object[0], handler);
        
        // 调用代理方法
        proxy.save(new User(1L, "张三"));
        User user = proxy.find(2L);
    }
}

优点

  1. 源代码级API:可以不懂字节码指令集,以Java代码形式操作字节码
  2. 轻量级库:相比其他字节码库体积较小
  3. 功能全面:支持创建类、修改类、动态编译Java源代码等
  4. 性能良好:生成的代理代码性能接近CGLIB
  5. 长期维护:库历史悠久,稳定可靠

局限性

  1. API不够直观:部分API设计较为古老,使用不如ByteBuddy流畅
  2. 文档不足:相比其他库,文档和示例较少
  3. 内存消耗:在操作大量类时可能消耗较多内存

Spring中的应用

Javassist不是Spring默认的代理实现,但一些基于Spring的框架使用它实现动态代理,如Hibernate:

typescript 复制代码
// 自定义Javassist代理工厂示例
public class JavassistProxyFactory implements ProxyFactory {
    @Override
    public Object createProxy(Object target, Interceptor interceptor) {
        // Javassist代理实现
        // ...
    }
}

5. AspectJ:完整的AOP解决方案

实现原理

AspectJ是一个完整的AOP框架,与前面的动态代理方案不同,它提供两种方式:

  1. 编译时织入:在编译源代码时直接修改字节码
  2. 加载时织入:在类加载到JVM时修改字节码

AspectJ拥有专门的切面语言,功能比Spring AOP更强大。

核心代码示例

AspectJ切面定义:

java 复制代码
@Aspect
public class LoggingAspect {
    @Before("execution(* com.example.service.UserService.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Before: " + joinPoint.getSignature().getName());
    }
    
    @Around("execution(* com.example.service.UserService.*(..))")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Before: " + joinPoint.getSignature().getName());
        long startTime = System.currentTimeMillis();
        
        Object result = joinPoint.proceed();
        
        long endTime = System.currentTimeMillis();
        System.out.println("After: " + joinPoint.getSignature().getName() + 
                          ", 耗时: " + (endTime - startTime) + "ms");
        
        return result;
    }
}

Spring配置AspectJ:

less 复制代码
@Configuration
@EnableAspectJAutoProxy
public class AspectJConfig {
    @Bean
    public LoggingAspect loggingAspect() {
        return new LoggingAspect();
    }
}

编译时织入配置(maven):

xml 复制代码
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.14.0</version>
    <configuration>
        <complianceLevel>11</complianceLevel>
        <source>11</source>
        <target>11</target>
        <aspectLibraries>
            <aspectLibrary>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
            </aspectLibrary>
        </aspectLibraries>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

优点

  1. 功能最全面:支持几乎所有AOP场景,包括构造函数、字段访问、异常等
  2. 性能最优:编译时织入无运行时开销,性能接近原生代码
  3. 完整语言支持:有专门的切面语言和语法,表达能力强
  4. 更灵活的切入点:可以对接口、实现类、构造函数、字段等定义切面

局限性

  1. 复杂度高:学习曲线陡峭,需要掌握AspectJ语法
  2. 构建过程改变:需要特殊的编译器或类加载器
  3. 调试难度增加:修改后的字节码可能难以调试

Spring中的应用

Spring可以通过以下方式使用AspectJ:

  1. proxy-target-class模式:使用Spring AOP但CGLIB生成的代理
ini 复制代码
@EnableAspectJAutoProxy(proxyTargetClass = true)
  1. LTW(Load-Time Weaving)模式:在类加载时织入
css 复制代码
@EnableLoadTimeWeaving
  1. 编译时织入:需要配置AspectJ编译器

使用场景对比与选择建议

代理实现 最适用场景 不适用场景
JDK动态代理 基于接口的简单代理,轻量级应用 没有实现接口的类,性能敏感场景
CGLIB 没有实现接口的类,需要兼顾性能和便捷性 final类/方法,高安全性环境
ByteBuddy 现代化项目,关注性能优化,复杂代理逻辑 追求最小依赖的简单项目
Javassist 需要动态生成/修改类的复杂场景 API设计敏感项目,初学者
AspectJ 企业级应用,性能关键型场景,复杂AOP需求 简单项目,快速原型,学习成本敏感
相关推荐
Albert Edison3 小时前
【最新版】IntelliJ IDEA 2025 创建 SpringBoot 项目
java·spring boot·intellij-idea
Piper蛋窝4 小时前
深入 Go 语言垃圾回收:从原理到内建类型 Slice、Map 的陷阱以及为何需要 strings.Builder
后端·go
六毛的毛7 小时前
Springboot开发常见注解一览
java·spring boot·后端
AntBlack7 小时前
拖了五个月 ,不当韭菜体验版算是正式发布了
前端·后端·python
31535669137 小时前
一个简单的脚本,让pdf开启夜间模式
前端·后端
uzong7 小时前
curl案例讲解
后端
开开心心就好8 小时前
免费PDF处理软件,支持多种操作
运维·服务器·前端·spring boot·智能手机·pdf·电脑
一只叫煤球的猫8 小时前
真实事故复盘:Redis分布式锁居然失效了?公司十年老程序员踩的坑
java·redis·后端
猴哥源码8 小时前
基于Java+SpringBoot的农事管理系统
java·spring boot
大鸡腿同学9 小时前
身弱武修法:玄之又玄,奇妙之门
后端