欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

SpringBoot中5种动态代理的实现方案,

来源: javaer 分享于  点击 49315 次 点评:33

SpringBoot中5种动态代理的实现方案,


目录
  • 1. JDK动态代理:Java原生的代理方案
    • 实现原理
    • 核心代码示例
    • 优点
    • 局限性
    • Spring中的应用
  • 2. CGLIB代理:基于字节码的强大代理
    • 实现原理
    • 核心代码示例
    • 优点
    • 局限性
    • Spring中的应用
  • 3. ByteBuddy:现代化的字节码操作库
    • 实现原理
    • 核心代码示例
    • 优点
    • 局限性
    • Spring中的应用
  • 4. Javassist:更易用的字节码编辑库
    • 实现原理
    • 核心代码示例
    • 优点
    • 局限性
    • Spring中的应用
  • 5. AspectJ:完整的AOP解决方案
    • 实现原理
    • 核心代码示例
    • 优点
    • 局限性
    • Spring中的应用
  • 使用场景对比与选择建议

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

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

    实现原理

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

    核心代码示例

    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);
        }
    }
    

    优点

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

    局限性

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

    Spring中的应用

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

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

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

    实现原理

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

    核心代码示例

    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);
        }
    }
    

    优点

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

    局限性

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

    Spring中的应用

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

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

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

    实现原理

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

    核心代码示例

    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);
        }
    }
    

    优点

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

    局限性

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

    Spring中的应用

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

    @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规范。

    核心代码示例

    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);
        }
    }
    

    优点

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

    局限性

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

    Spring中的应用

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

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

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

    实现原理

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

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

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

    核心代码示例

    AspectJ切面定义:

    @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:

    @Configuration
    @EnableAspectJAutoProxy
    public class AspectJConfig {
        @Bean
        public LoggingAspect loggingAspect() {
            return new LoggingAspect();
        }
    }
    

    编译时织入配置(maven):

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

    优点

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

    局限性

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

    Spring中的应用

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

    proxy-target-class模式:使用Spring AOP但CGLIB生成的代理

    @EnableAspectJAutoProxy(proxyTargetClass = true)
    

    LTW(Load-Time Weaving)模式:在类加载时织入

    @EnableLoadTimeWeaving
    

    编译时织入:需要配置AspectJ编译器

    使用场景对比与选择建议

    代理实现最适用场景不适用场景
    JDK动态代理基于接口的简单代理,轻量级应用没有实现接口的类,性能敏感场景
    CGLIB没有实现接口的类,需要兼顾性能和便捷性final类/方法,高安全性环境
    ByteBuddy现代化项目,关注性能优化,复杂代理逻辑追求最小依赖的简单项目
    Javassist需要动态生成/修改类的复杂场景API设计敏感项目,初学者
    AspectJ企业级应用,性能关键型场景,复杂AOP需求简单项目,快速原型,学习成本敏感

    到此这篇关于SpringBoot中5种动态代理的实现方案的文章就介绍到这了,更多相关SpringBoot动态代理内容请搜索3672js教程以前的文章或继续浏览下面的相关文章希望大家以后多多支持3672js教程!

    您可能感兴趣的文章:
    • SpringBoot项目中JDK动态代理和CGLIB动态代理的使用详解
    • SpringBoot利用Junit动态代理实现Mock方法
    • SpringBoot中拦截器和动态代理的区别详解
    • SpringBoot/Spring AOP默认动态代理方式实例详解
    相关栏目:

    用户点评