【JavaEE30-后端部分】Spring AOP 原理——代理模式,原来AOP是这样“偷偷”增强你的代码的【AI辅助理解】
开篇你有没有想过这个问题老铁们前面我们学了怎么用 AOP 给方法加耗时统计、加日志、加各种增强功能。用起来是真爽——写一个切面类加几个注解所有方法都自动被增强了。但是你有没有想过一个问题Spring 到底是怎么做到“不修改源码就能增强方法”的这就像你买了一台冰箱它本来只能制冷突然有一天它自己学会了制冰、联网、播放音乐……你没拆开它它怎么就多了这些功能答案就是代理模式。今天我们就来揭开这个黑盒子看看 Spring AOP 背后的“魔术”到底是怎么变的。一、什么是代理模式先讲个故事1.1 没有代理的日子假设你是一个房东手里有套房子要出租。你自己带人看房、自己谈价格、自己签合同、自己收租金……累不累更麻烦的是如果租客半夜马桶坏了你也得自己去修。这就是没有代理的情况客户端租客直接访问目标对象房东。租客 → 房东看房、签合同、收租、维修1.2 有了代理之后后来你找了个中介。租客要找房子先找中介中介带看房、谈价格、签合同、收租金出了事租客也先找中介。你只需要安心收钱就行了。这就是代理模式客户端不直接访问目标对象而是通过一个代理类来间接访问。租客 → 中介代理 → 房东目标中介帮你干了所有杂活但你房东的核心业务出租房子一点都没变。这就是代理模式的核心价值在不修改目标对象的前提下对目标对象的功能进行增强或控制。二、代理模式在AOP中的角色把上面的例子对应到 AOP生活中的角色AOP 中的角色说明房东目标对象Target真正干活的业务方法租客客户端Client调用业务方法的代码中介代理对象Proxy在目标方法前后干杂活的切面租房合同接口Interface规定房东和中介都要做的事画个图就清楚了三、代理模式的两种实现方式代理模式分为两种静态代理和动态代理。它们的区别就像“提前找好中介”和“临时找个中介”。3.1 静态代理——提前写好中介合同定义在程序运行前代理类的.class文件就已经存在了。也就是说你是手动写的代理类。生活例子你提前找好了一个中介签了合同以后所有租房业务都通过他。这个中介是固定存在的不是临时找的。代码示例以租房为例// 1. 定义接口房东和中介都要遵守的规矩publicinterfaceHouseSubject{voidrentHouse();}// 2. 目标对象房东publicclassRealHouseSubjectimplementsHouseSubject{OverridepublicvoidrentHouse(){System.out.println(我是房东我出租房子);}}// 3. 代理对象中介publicclassHouseProxyimplementsHouseSubject{privateHouseSubjecttarget;// 被代理的房东publicHouseProxy(HouseSubjecttarget){this.targettarget;}OverridepublicvoidrentHouse(){System.out.println(中介开始代理带人看房);target.rentHouse();// 房东出租房子System.out.println(中介代理结束收中介费);}}// 4. 使用publicclassMain{publicstaticvoidmain(String[]args){HouseSubjectlandlordnewRealHouseSubject();HouseProxyproxynewHouseProxy(landlord);proxy.rentHouse();// 通过中介租房}}运行结果中介开始代理带人看房 我是房东我出租房子 中介代理结束收中介费静态代理的缺点如果房东新增了“卖房子”的业务接口要改房东要改中介也要改。如果来了个新房东新的被代理对象又要写一个新的代理类。代码写死不灵活维护成本高。所以静态代理在日常开发中几乎不用。3.2 动态代理——运行时临时找个中介定义在程序运行时动态创建代理对象。你不需要手动写代理类JDK 或 CGLIB 帮你自动生成。生活例子你不是提前找好中介而是每次有租客来看房时临时打电话叫一个中介过来。这个中介是“现找现用”的。动态代理有两种实现方式JDK 动态代理和CGLIB 动态代理。四、JDK 动态代理——只能代理有接口的类4.1 工作原理JDK 动态代理要求目标对象必须实现接口。它会在运行时根据你给的接口动态生成一个代理类。三步走定义一个接口目标对象要实现它。写一个InvocationHandler在里面写增强逻辑。用Proxy.newProxyInstance()创建代理对象。4.2 代码示例importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;importjava.lang.reflect.Proxy;// 1. 接口publicinterfaceHouseSubject{voidrentHouse();}// 2. 目标对象房东publicclassRealHouseSubjectimplementsHouseSubject{OverridepublicvoidrentHouse(){System.out.println(我是房东我出租房子);}}// 3. 自定义 InvocationHandler增强逻辑写在这里publicclassJDKInvocationHandlerimplementsInvocationHandler{privateObjecttarget;// 被代理的对象publicJDKInvocationHandler(Objecttarget){this.targettarget;}OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{System.out.println(JDK代理开始代理);Objectresultmethod.invoke(target,args);// 调用原方法System.out.println(JDK代理代理结束);returnresult;}}// 4. 使用publicclassMain{publicstaticvoidmain(String[]args){HouseSubjectlandlordnewRealHouseSubject();// 动态创建代理对象HouseSubjectproxy(HouseSubject)Proxy.newProxyInstance(landlord.getClass().getClassLoader(),// 类加载器landlord.getClass().getInterfaces(),// 要实现的接口newJDKInvocationHandler(landlord)// 增强逻辑);proxy.rentHouse();}}运行结果JDK代理开始代理 我是房东我出租房子 JDK代理代理结束4.3 JDK 动态代理的优缺点优点缺点不需要手动写代理类自动生成只能代理实现了接口的类代码灵活增强逻辑统一如果目标类没有接口无法使用五、CGLIB 动态代理——没有接口也能代理5.1 为什么需要 CGLIBJDK 动态代理有个致命问题目标类必须实现接口。但很多时候我们的业务类可能没有接口比如直接写的Service、Controller。怎么办CGLIBCode Generation Library解决了这个问题。它通过继承目标类来生成代理子类所以不需要接口。生活例子你找个中介但房东没有签任何合同没有接口。CGLIB 的做法是直接“认”这个房东当干爹然后以“干儿子”的身份去帮他出租房子。5.2 工作原理CGLIB 会在运行时动态生成目标类的子类在子类中重写父类的方法加入增强逻辑。三步走写一个MethodInterceptor在里面写增强逻辑。用Enhancer.create()创建代理对象。5.3 代码示例importorg.springframework.cglib.proxy.Enhancer;importorg.springframework.cglib.proxy.MethodInterceptor;importorg.springframework.cglib.proxy.MethodProxy;importjava.lang.reflect.Method;// 1. 目标对象没有接口publicclassRealHouseSubject{publicvoidrentHouse(){System.out.println(我是房东我出租房子);}}// 2. 自定义 MethodInterceptorpublicclassCGLIBInterceptorimplementsMethodInterceptor{privateObjecttarget;publicCGLIBInterceptor(Objecttarget){this.targettarget;}OverridepublicObjectintercept(Objectobj,Methodmethod,Object[]args,MethodProxyproxy)throwsThrowable{System.out.println(CGLIB代理开始代理);Objectresultproxy.invoke(target,args);// 调用原方法System.out.println(CGLIB代理代理结束);returnresult;}}// 3. 使用publicclassMain{publicstaticvoidmain(String[]args){RealHouseSubjectlandlordnewRealHouseSubject();// 动态创建代理对象通过继承RealHouseSubjectproxy(RealHouseSubject)Enhancer.create(landlord.getClass(),// 目标类的类型newCGLIBInterceptor(landlord)// 增强逻辑);proxy.rentHouse();}}运行结果CGLIB代理开始代理 我是房东我出租房子 CGLIB代理代理结束5.4 CGLIB 的优缺点优点缺点不需要接口可以代理普通类不能代理 final 类因为 final 不能被继承性能较好需要额外引入 CGLIB 依赖Spring Boot 已内置六、JDK vs CGLIB一张图看懂区别七、Spring AOP 到底用哪种代理Spring AOP 的代理选择逻辑很简单目标类实现了接口→ 默认使用JDK 动态代理目标类没有实现接口→ 使用CGLIB 动态代理Spring Boot 2.x 开始为了简化配置默认强制使用 CGLIB 代理不管你有没有接口。这样更统一也更方便。如果你想改回去可以在application.properties中配置spring.aop.proxy-target-classfalse但一般不建议改因为 CGLIB 更通用。八、结语AOP 的学习到此结束老铁们到这儿我们的 Spring AOP 系列就全部结束了。我们一路走来入门篇用 AOP 统计方法耗时感受它的威力。核心概念篇把切点、连接点、通知、切面这些抽象概念变得具体化。通知类型篇搞清楚了五种通知的执行时机和顺序。切点表达式篇学会了用execution和annotation精准定位要增强的方法。原理篇揭开了 AOP 的神秘面纱明白了它背后是动态代理在支撑。AOP 的核心思想就一句话把重复的、横跨多个模块的共性功能抽取出来在不修改源码的情况下统一增强。掌握了 AOP你就掌握了一种强大的代码复用和功能增强的能力。以后遇到“给所有方法加日志”“统计所有方法耗时”“统一权限校验”这类需求你就能轻松搞定。Spring AOP 就到这里感谢你的陪伴