实用指南:手写Spring第21弹:CGLIB动态代理与Spring AOP:深入理解面向切面编程的核心机制
CGLIB动态代理与Spring AOP:深入理解面向切面编程的核心机制
"CGLIB动态代理 vs JDK代理:Spring AOP底层原理与实战全解析"
引言
在上一篇文章中,我们深入探讨了JDK动态代理的实现原理和应用场景。然而,JDK动态代理有一个重要的限制:它只能代理实现了接口的类。在实际开发中,我们经常会遇到需要代理没有接口的普通类的情况。这时,CGLIB动态代理就闪亮登场了。本文将深入剖析CGLIB动态代理的技术原理,对比JDK动态代理的差异,并正式引入Spring AOP的核心概念,帮助读者全面掌握面向切面编程的精髓。
目录
CGLIB动态代理与Spring AOP:深入理解面向切面编程的核心机制
(❁´◡`❁)您的点赞➕评论➕收藏⭐是作者创作的最大动力
支持我:点赞+收藏⭐️+留言欢迎留言讨论
(源码 + 调试运行 + 问题答疑)
有兴趣可以联系我。文末有免费源码
免费获取源码。
更多内容敬请期待。如有需要可以联系作者免费送
更多源码定制,项目修改,项目二开可以联系作者
点击可以进行搜索(每人免费送一套代码):千套源码目录(点我)2025源码免费送(点我)
我们常常在当下感到时间慢,觉得未来遥远,但一旦回头看,时间已经悄然流逝。对于未来,尽管如此,也应该保持一种从容的态度,相信未来仍有许多可能性等待着我们。
CGLIB动态代理深度解析
什么是CGLIB?
CGLIB(Code Generation Library)是一个强大的、高性能的代码生成库,它通过在运行时动态生成子类来扩展Java类的功能。与JDK动态代理基于接口的实现方式不同,CGLIB采用继承的方式,即使目标类没有实现任何接口,也能创建代理对象。
CGLIB的核心工作原理
CGLIB的动态代理机制基于以下核心技术:
1. 字节码生成技术
CGLIB使用ASM(一个Java字节码操作框架)直接生成代理类的字节码。ASM提供了比Java反射更底层的字节码操作能力,能够直接生成、转换和分析Java字节码。
2. 方法拦截机制
CGLIB通过MethodInterceptor接口实现方法拦截,其核心方法如下:
public interface MethodInterceptor extends Callback {
Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable;
}
3. FastClass机制
CGLIB引入了FastClass概念,通过为每个类生成一个FastClass,实现方法的快速调用,避免了反射调用的性能开销。
CGLIB动态代理实现详解
让我们通过一个完整的示例来理解CGLIB的工作原理:
// 目标类(没有实现任何接口)
public class UserService {
public User findUserById(Long id) {
System.out.println("执行查询用户操作,ID: " + id);
return new User(id, "用户" + id);
}
public void updateUser(User user) {
System.out.println("执行更新用户操作: " + user.getName());
}
public final void deleteUser(Long id) {
System.out.println("执行删除用户操作,ID: " + id);
}
}
// CGLIB方法拦截器
public class LoggingInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println("开始执行方法: " + method.getName());
try {
// 调用父类(目标类)的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("方法执行成功,耗时: " +
(System.currentTimeMillis() - startTime) + "ms");
return result;
} catch (Exception e) {
System.err.println("方法执行异常: " + e.getMessage());
throw e;
}
}
}
// CGLIB代理工厂
public class CglibProxyFactory {
@SuppressWarnings("unchecked")
public static T createProxy(Class targetClass) {
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(targetClass);
// 设置回调接口
enhancer.setCallback(new LoggingInterceptor());
// 创建代理对象
return (T) enhancer.create();
}
}
// 使用示例
public class CglibDemo {
public static void main(String[] args) {
// 创建CGLIB代理
UserService userService = CglibProxyFactory.createProxy(UserService.class);
// 调用方法
User user = userService.findUserById(1L);
userService.updateUser(user);
}
}
CGLIB代理的字节码生成过程
CGLIB创建代理类的过程可以概括为以下步骤:
创建Enhancer实例:作为CGLIB的核心类,Enhancer负责代理类的生成。
设置父类:通过
setSuperclass()方法指定要代理的目标类。设置回调:通过
setCallback()方法设置方法拦截器。生成字节码:使用ASM生成代理类的字节码。
加载类:通过自定义的ClassLoader加载生成的代理类。
创建实例:通过反射创建代理对象。
生成的代理类大致相当于以下代码:
public class UserService$$EnhancerByCGLIB$$abcd1234 extends UserService {
private MethodInterceptor interceptor;
public UserService$$EnhancerByCGLIB$$abcd1234() {
this.interceptor = ...;
}
public User findUserById(Long id) {
Method method = ...; // 获取Method对象
return (User) interceptor.intercept(this, method, new Object[]{id},
MethodProxy.create(UserService.class, this, "findUserById",
"()Lcom/example/User;", "findUserById"));
}
// 其他被代理的方法...
}
JDK动态代理 vs CGLIB动态代理
技术实现对比
| 特性 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 实现基础 | 基于接口 | 基于继承 |
| 代理方式 | 实现相同的接口 | 继承目标类 |
| 性能 | 较慢(反射调用) | 较快(FastClass机制) |
| 限制 | 只能代理接口方法 | 不能代理final类/方法 |
| 依赖 | JDK原生支持 | 需要引入CGLIB库 |
性能深度分析
JDK动态代理的性能特点
方法调用通过反射实现,有一定的性能开销
在JDK 8及以上版本中,性能有所优化
适合代理接口方法较少的情况
CGLIB的性能优势
通过FastClass实现方法索引,避免反射调用
首次加载较慢,但后续调用性能接近直接调用
适合高性能要求的场景
选择策略
在实际项目中,选择哪种代理方式需要考虑以下因素:
目标对象特性:如果目标对象实现了接口,两种方式都可以;如果没有接口,只能选择CGLIB。
性能要求:对性能要求极高的场景,CGLIB更有优势。
方法限制:如果需要代理final方法,只能选择JDK动态代理。
依赖考虑:如果不希望引入额外依赖,JDK动态代理是更好的选择。
Spring AOP核心概念详解
Spring AOP(Aspect-Oriented Programming)是Spring框架的重要组成部分,它基于代理模式实现了面向切面编程的能力。理解Spring AOP的核心概念对于掌握其工作原理至关重要。
AOP基本术语
1. 切面(Aspect)
切面是跨多个类的关注点的模块化。一个切面包含通知(Advice)和切入点(Pointcut)。在Spring中,切面通常用@Aspect注解标注的类来表示。
示例:
@Aspect
@Component
public class LoggingAspect {
// 通知和切入点定义...
}
2. 连接点(Joinpoint)
连接点是程序执行过程中的一个点,如方法执行或异常处理。在Spring AOP中,连接点总是代表方法的执行。
理解要点:
Spring AOP只支持方法级别的连接点
每个被代理的方法都是一个潜在的连接点
连接点是AOP框架可以插入额外逻辑的点
3. 通知(Advice)
通知是切面在特定连接点采取的行动。Spring AOP提供了以下几种类型的通知:
前置通知(Before Advice)
在连接点之前执行,但不能阻止执行流程继续到连接点(除非它抛出异常)。
后置通知(After Returning Advice)
在连接点正常完成后执行(方法正常返回,没有抛出异常)。
异常通知(After Throwing Advice)
在方法抛出异常退出时执行。
最终通知(After (Finally) Advice)
无论连接点以何种方式退出(正常返回或异常抛出),都会执行。
环绕通知(Around Advice)
包围连接点的通知,是最强大的通知类型。环绕通知可以在方法调用前后执行自定义行为,并决定是否继续执行连接点或返回自己的返回值或抛出异常。
4. 切入点(Pointcut)
切入点是一个表达式,用于匹配连接点。通知与切入点表达式关联,并在与该切入点匹配的连接点处运行。
切入点表达式示例:
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
@Pointcut("within(@org.springframework.stereotype.Service *)")
public void serviceBeans() {}
5. 目标对象(Target Object)
被一个或多个切面通知的对象。由于Spring AOP是使用运行时代理实现的,因此该对象始终是代理对象。
Spring AOP的代理机制
Spring AOP根据目标对象的特性智能选择代理方式:
如果目标对象实现了接口:默认使用JDK动态代理
如果目标对象没有实现接口:使用CGLIB代理
可以强制使用CGLIB:通过配置
proxyTargetClass=true
Spring AOP配置方式
1. 基于XML的配置
2. 基于注解的配置(推荐)
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
// 配置类
}
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
// 前置通知逻辑
}
}
实际应用场景与最佳实践
1. 日志记录
通过AOP统一处理方法的入参、出参和异常日志。
2. 性能监控
监控方法执行时间,识别性能瓶颈。
3. 事务管理
Spring的事务管理就是基于AOP实现的。
4. 安全控制
统一进行权限校验和访问控制。
5. 缓存处理
在方法执行前后进行缓存操作。
总结
CGLIB动态代理和JDK动态代理各有优劣,它们共同构成了Spring AOP的底层技术基础。理解它们的实现原理和适用场景,有助于我们在实际开发中做出更合理的技术选择。
Spring AOP通过抽象出切面、连接点、通知、切入点等概念,为开发者提供了一套完善的面向切面编程解决方案。掌握这些核心概念,不仅能够更好地使用Spring框架,还能够设计出更加模块化、可维护的软件系统。
在实际项目中,我们应该根据具体需求选择合适的代理方式,并合理运用AOP来解决横切关注点问题,从而提高代码的质量和可维护性。
CGLIB代理生成流程

Spring AOP代理选择机制

AOP通知执行顺序

(❁´◡`❁)您的点赞➕评论➕收藏⭐是作者创作的最大动力
支持我:点赞+收藏⭐️+留言欢迎留言讨论
(源码 + 调试运行 + 问题答疑)
有兴趣可以联系我。文末有免费源码
学习知识需费心,
整理归纳更费神。
源码免费人人喜,
码农福利等你领!常来我家多看看,
网址:扣棣编程,
感谢支持常陪伴,
点赞关注别忘记!山高路远坑又深,
大军纵横任驰奔,
谁敢横刀立马行?
唯有点赞+关注成!
往期文章推荐:
基于Springboot + vue实现的学生宿舍信息管理系统
免费获取宠物商城源码--SpringBoot+Vue宠物商城网站系统
【2025小年源码免费送】
浙公网安备 33010602011771号