详解Spring AOP篇三

详解Spring AOP篇三

目录

代理模式

定义

代理模式的主要角色

静态代理

动态代理

JDK动态代理

接口介绍

CGLIB动态代理

Spring AOP源码解析

验证

没实现接口

实现了接口 

小结


Spring AOP 是基于动态代理来实现AOP的.

代理模式

代理模式, 也叫委托模式.

定义
为其他对象提供⼀种代理以控制对这个对象的访问. 它的作⽤就是通过提供⼀个代理类, 让我们在调⽤⽬标⽅法的时候, 不再是直接对⽬标⽅法进⾏调⽤, ⽽是通过代理类间接调⽤.

在某些情况下, ⼀个对象不适合或者不能直接引⽤另⼀个对象, ⽽代理对象可以在客⼾端和⽬标对象之间起到中介的作⽤.

使⽤代理前:

使用代理后:

代理模式的主要角色
1. Subject: 业务接⼝类. 可以是抽象类或者接⼝(不⼀定有)
2. RealSubject: 业务实现类. 具体的业务执⾏, 也就是被代理对象.
3. Proxy: 代理类. RealSubject的代理.

UML类图如下:

代理模式可以在不修改被代理对象的基础上, 通过扩展代理类, 进⾏⼀些功能的附加与增强.
根据代理的创建时期, 代理模式分为静态代理和动态代理.

• 静态代理: 由程序员创建代理类或特定⼯具⾃动⽣成源代码再对其编译, 在程序运⾏前代理类的.class ⽂件就已经存在了.
• 动态代理: 在程序运⾏时, 运⽤反射机制动态创建⽽成。
静态代理

静态代理: 在程序运⾏前, 代理类的 .class⽂件就已经存在了.

(以房租租赁为例,在出租房⼦之前, 中介已经做好了相关的⼯作, 就等租⼾来租房⼦了)

下面我们通过代码来加深理解:

1. 定义接⼝(定义房东要做的事情, 也是中介需要做的事情)

public interface HouseSubject { void rentHouse(); }

2. 实现接⼝(房东出租房⼦)

public class RealHouseSubject implements HouseSubject{ @Override public void rentHouse() { System.out.println("我是房东, 我出租房子"); } }

3. 代理(中介, 帮房东出租房⼦)

public class HouseProxy implements HouseSubject{ private HouseSubject target; public HouseProxy(HouseSubject target) { this.target = target; } @Override public void rentHouse() { //出租前 System.out.println("我是中介, 开始代理"); //出租房子 target.rentHouse(); //出租后 System.out.println("我是中介, 结束代理"); } }

4.使用

public class Main { public static void main(String[] args) { //静态代理 HouseProxy proxy = new HouseProxy(new RealHouseSubject()); proxy.rentHouse(); } }

运行结果:

上⾯这个代理实现⽅式就是静态代理.
从上述程序可以看出, 虽然静态代理也完成了对⽬标对象的代理, 但是由于代码都写死了, 对⽬标对象的每个⽅法的增强都是⼿动完成的,⾮常不灵活. 所以⽇常开发⼏乎看不到静态代理的场景.

接下来新增需求: 中介⼜新增了其他业务: 代理房屋出售,此时我们需要对上述代码进⾏修改:
1. 接⼝定义修改

public interface HouseSubject { void rentHouse(); void saleHouse(); }

2.接口实现修改

public class RealHouseSubject implements HouseSubject{ @Override public void rentHouse() { System.out.println("我是房东, 我出租房子"); } @Override public void saleHouse() { System.out.println("我是房东, 我出售房子"); } }

3.代理类修改

public class HouseProxy implements HouseSubject{ private HouseSubject target; public HouseProxy(HouseSubject target) { this.target = target; } @Override public void rentHouse() { //出租前 System.out.println("我是中介, 开始代理"); //出租房子 target.rentHouse(); //出租后 System.out.println("我是中介, 结束代理"); } @Override public void saleHouse() { //出租前 System.out.println("我是中介, 开始代理"); //出租房子 target.saleHouse(); //出租后 System.out.println("我是中介, 结束代理"); } }

从上述代码可以看出, 我们修改接⼝和业务实现类时, 还需要修改代理类.
同样的, 如果有新增接⼝和业务实现类, 也需要对每⼀个业务实现类新增代理类.
既然代理的流程是⼀样的, 有没有⼀种办法, 让他们通过⼀个代理类来实现呢?
这就需要⽤到动态代理技术了. 

动态代理

相⽐于静态代理来说,动态代理更加灵活.
我们不需要针对每个⽬标对象都单独创建⼀个代理对象, ⽽是把这个创建代理对象的⼯作推迟到程序运⾏时由JVM来实现. 也就是说动态代理在程序运⾏时, 根据需要动态创建⽣成.

Java也对动态代理进⾏了实现, 并给我们提供了⼀些API, 常⻅的实现⽅式有两种:

1. JDK动态代理
2. CGLIB动态代理
JDK动态代理

JDK 动态代理类实现步骤:

1. 定义⼀个接⼝及其实现类(静态代理中的 HouseSubject 和 RealHouseSubject )
2. ⾃定义 InvocationHandler 并重写 invoke ⽅法,在 invoke ⽅法中我们会调⽤⽬标⽅
法(被代理类的⽅法)并⾃定义⼀些处理逻辑
3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] 
interfaces,InvocationHandler h) ⽅法创建代理对象 

1.定义一个接口

public interface HouseSubject { void rentHouse(); void saleHouse(); }

2.定义一个实现类

public class RealHouseSubject implements HouseSubject{ @Override public void rentHouse() { System.out.println("我是房东, 我出租房子"); } @Override public void saleHouse() { System.out.println("我是房东, 我出售房子"); } }

3.实现 InvocationHandler 接口并重写 invoke 方法

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class JDKInvocationHandler implements InvocationHandler { private Object target;//目标对象 public JDKInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("我是代理, 开始代理"); //通过反射, 调用目标对象的方法 Object result = method.invoke(target, args); System.out.println("我是代理, 结束代理"); return result; } }

4.创建一个代理对象并使用

import java.lang.reflect.Proxy; public class Main { public static void main(String[] args) { //JDK动态代理 //目标对象 RealHouseSubject target = new RealHouseSubject(); //动态生成代理对象 代理接口, 运行成功 HouseSubject proxy= (HouseSubject) Proxy.newProxyInstance( HouseSubject.class.getClassLoader(), new Class[]{HouseSubject.class}, new JDKInvocationHandler(target)); proxy.rentHouse(); proxy.saleHouse(); } }

运行结果:

接口介绍

InvocationHandler

InvocationHandler 接⼝是Java动态代理的关键接⼝之⼀, 它定义了⼀个单⼀⽅法 invoke() , ⽤于
处理被代理对象的⽅法调⽤.

public interface InvocationHandler { /** * 参数说明 * proxy:代理对象 * method:代理对象需要实现的⽅法,即其中需要重写的⽅法 * args:method所对应⽅法的参数 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }

通过实现 InvocationHandler 接⼝, 可以对被代理对象的⽅法进⾏功能增强.

Proxy

Proxy 类中使⽤频率最⾼的⽅法是: newProxyInstance() , 这个⽅法主要⽤来⽣成⼀个代理
对象. 

这个⽅法⼀共有 3 个参数:

Loader: 类加载器, ⽤于加载代理对象.
interfaces : 被代理类实现的⼀些接⼝(这个参数的定义, 也决定了JDK动态代理只能代理实现了接⼝的⼀些类)
h : 实现了 InvocationHandler 接⼝的对象

如果让JDK代理类的话

import java.lang.reflect.Proxy; public class Main { public static void main(String[] args) { //JDK动态代理 //目标对象 RealHouseSubject target = new RealHouseSubject(); //JDK代理类 RealHouseSubject proxy= (RealHouseSubject)Proxy.newProxyInstance( RealHouseSubject.class.getClassLoader(), new Class[]{RealHouseSubject.class}, new JDKInvocationHandler(target)); proxy.rentHouse(); proxy.saleHouse(); } }

运行结果:

CGLIB动态代理

JDK 动态代理有⼀个最致命的问题是其只能代理实现了接⼝的类.

有些场景下, 我们的业务代码是直接实现的, 并没有接⼝定义. 为了解决这个问题, 我们可以⽤ CGLIB 动态代理机制来解决.

CGLIB(Code Generation Library)是⼀个基于ASM的字节码⽣成库,它允许我们在运⾏时对字节码进⾏修改和动态⽣成. CGLIB 通过继承⽅式实现代理, 很多知名的开源框架都使⽤到了CGLIB. 例如 Spring中的 AOP 模块中: 如果⽬标对象实现了接⼝,则默认采⽤ JDK 动态代理, 否则采⽤ CGLIB 动态代理.

CGLIB动态代理类实现步骤

1. 定义⼀个类(被代理类)
2. ⾃定义 MethodInterceptor 并重写 intercept ⽅法, intercept ⽤于增强⽬标⽅
法,和 JDK 动态代理中的 invoke ⽅法类似
3. 通过 Enhancer 类的 create()创建代理类

代码示例

HouseSubject

public interface HouseSubject { void rentHouse(); void saleHouse(); }

RealHouseSubject

public class RealHouseSubject implements HouseSubject{ @Override public void rentHouse() { System.out.println("我是房东, 我出租房子"); } @Override public void saleHouse() { System.out.println("我是房东, 我出售房子"); } }

 CGLibMethodInterceptor

import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CGLibMethodInterceptor implements MethodInterceptor { private Object target; public CGLibMethodInterceptor(Object target) { this.target = target; } /** * 调用代理对象的方法 */ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("我是中介, 开始代理"); Object result = method.invoke(target, args); System.out.println("我是中介, 结束代理"); return result; } }

 Main

import org.springframework.cglib.proxy.Enhancer; public class Main { public static void main(String[] args) { // 使用CGLib 完成代理 //目标对象 HouseSubject target = new RealHouseSubject(); //代理接口 HouseSubject houseSubject = (HouseSubject)Enhancer.create(target.getClass(), new CGLibMethodInterceptor(target)); houseSubject.rentHouse(); houseSubject.saleHouse(); } }

运行结果:

Main

import org.springframework.cglib.proxy.Enhancer; public class Main { public static void main(String[] args) { // 使用CGLib 完成代理 //目标对象 HouseSubject target = new RealHouseSubject(); //代理类 RealHouseSubject houseSubject = (RealHouseSubject) Enhancer.create(target.getClass(), new CGLibMethodInterceptor(target)); houseSubject.rentHouse(); houseSubject.saleHouse(); } }

运行结果:

接口介绍

MethodInterceptor

参数说明:

o: 被代理的对象

method: ⽬标⽅法(被拦截的⽅法, 也就是需要增强的⽅法)

objects: ⽅法⼊参

methodProxy: ⽤于调⽤原始⽅法

Enhancer.create() 

Enhancer.create() ⽤来⽣成⼀个代理对象。

参数说明:

argumentTypes:被代理类的类型(类或接口)

arguments:自定义方法拦截器 MethosInterceptor

Spring AOP源码解析

Spring AOP 主要基于两种⽅式实现的: JDK 及 CGLIB 的⽅式。

Spring对于AOP的实现,基本上都是靠 AnnotationAwareAspectJAutoProxyCreator 去完成
⽣成代理对象的逻辑在⽗类 AbstractAutoProxyCreator 中。

在上面的代码中有⼀个重要的属性: proxyTargetClass, 默认值为false. 也可以通过程序设置。

proxyTargetClass目标对象代理方式
false实现了接口JDK代理
false未实现接口(只有实现类)CGLIB代理
true实现了接口CGLIB代理
true未实现接口(只有实现类)CGLIB代理

注意

Spring Boot 2.X开始, 默认使⽤CGLIB代理
可以通过配置项 spring.aop.proxy-target-class=false 来进⾏修改,设置默认为jdk代理,

原来可以使用 @EnableAspectJAutoProxy(proxyTargetClass = true) 来设置,但是SpringBoot设置 @EnableAspectJAutoProxy ⽆效, 因为Spring Boot 默认使⽤AopAutoConfiguration进⾏装配 
验证
没实现接口

TestController

import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @Slf4j @RequestMapping("/test") @RestController public class TestController { @MyAspect @RequestMapping("/t1") public String t1(){ log.info("执行t1方法..."); return "t1"; } }

SpringAopApplication

import com.wmh.springaop.controller.TestController; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; @SpringBootApplication public class SpringAopApplication { public static void main(String[] args) { ApplicationContext context=SpringApplication.run(SpringAopApplication.class, args); TestController bean = context.getBean(TestController.class); System.out.println(bean); } }

设置配置项  spring.aop.proxy-target-class=false时

Debug模式下查看到的:

设置配置项  spring.aop.proxy-target-class=true时

Debug模式下查看到的:

实现了接口 

Iface

public interface Iface { void test(); }

MyAspect

import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAspect { }

TestController2

import com.wmh.springaop.config.MyAspect; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController2 implements Iface{ @MyAspect @RequestMapping("/test1") public void test() { System.out.println("测试"); } }

 SpringAopApplication

import com.wmh.springaop.controller.Iface; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; @SpringBootApplication public class SpringAopApplication { public static void main(String[] args) { ApplicationContext context=SpringApplication.run(SpringAopApplication.class, args); Iface iface=(Iface)context.getBean("testController2"); System.out.println(iface); } }

设置配置项  spring.aop.proxy-target-class=false时

Debug模式下查看到的:

设置配置项  spring.aop.proxy-target-class=true时

Debug模式下查看到的:

小结
Spring 默认proxyTargetClass 为false,实现了接口,使用JDK代理,未实现接口,使用CGLIB代理

SpringBoot 默认proxyTargetClass 为true,默认使用 CGLIB代理

但是可以通过设置配置项spring.aop.proxy-target-class指定proxyTargetClass。

Read more

除了 OpenClaw,今天 AI 热榜还有什么值得看?我把 5 个重点方向讲清楚了

除了 OpenClaw,今天 AI 热榜还有什么值得看?我把 5 个重点方向讲清楚了

🔥 个人主页:杨利杰YJlio❄️ 个人专栏:《Sysinternals实战教程》《Windows PowerShell 实战》《WINDOWS教程》《IOS教程》《微信助手》《锤子助手》《Python》《Kali Linux》《那些年未解决的Windows疑难杂症》🌟 让复杂的事情更简单,让重复的工作自动化 除了 OpenClaw,今天 AI 热榜还有什么值得看?我把 5 个重点方向讲清楚了 * 除了 OpenClaw,今天 AI 热榜还有什么值得看?我把 5 个重点方向讲清楚了 * 1. 我先说结论:今天这波 AI 热榜,最重要的不是“谁最火”,而是“风向变了” * 2. GoogleCloudPlatform / generative-ai:平台生态正在成为真正的护城河 * 3. MiroFish:群体智能和多智能体,开始从概念走向更具体的产品叙事

By Ne0inhk

QtCreator接入外部AI大模型

文章目录 * 一、概要 * 二、安装Qt5.14.2,配置高版本QtCreator * 三、下载AI插件 * 四、配置插件 * 4.1、AI大模型设置 * 4.2、自动补全配置 * 自动补全基础设置 * 模型参数设置 * 上下文设置 * 提示词设置 * Quick Refactor Settings(快速重构设置) * Ollama Settings(Ollama设置) * 4.3、聊天助手配置 * Chat Settings(聊天设置) * General Parameters(基础参数) * Advanced Parameters(高级参数) * Context Settings(上下文设置) * Ollama Settings(Ollama设置) * Chat Settings(聊天设置)

By Ne0inhk
别再手动写代码了!Claude Skills 实战,让 AI 帮你干 80% 的活!

别再手动写代码了!Claude Skills 实战,让 AI 帮你干 80% 的活!

📋 目录 1. 什么是 Claude Skills 2. 快速安装 Skills 3. 已安装的 Skills 清单 4. Skills 使用方式详解 5. 实战案例:使用 Frontend Design Skill 创建网站 6. Skill 管理最佳实践 7. 高级技巧 8. 常见问题排查 什么是 Claude Skills Claude Skills 是模块化的能力包,包含指令、元数据和可选资源(脚本、模板),让 Claude 在需要时自动加载和使用。 核心特点 * 自动触发 - 无需手动调用,Claude 会根据你的需求自动识别并使用合适的 Skill * 渐进式加载

By Ne0inhk
打造你的专属 AI 旅行管家:基于 OpenAgents 的多智能体旅游助手实战

打造你的专属 AI 旅行管家:基于 OpenAgents 的多智能体旅游助手实战

基于 OpenAgents 的多智能体旅游助手实战 在大模型与智能体(Agent)技术快速发展的今天,构建一个能理解需求、协调任务、调用工具的 AI 助手已不再是遥不可及的梦想。传统的聊天机器人往往只能被动回答问题,而现代智能体系统则具备“主动性”——它能拆解复杂目标、调用多个子模块、甚至与外部服务交互,从而完成端到端的任务。本文将带你从零开始,利用 OpenAgents 这一开源框架,搭建一个由多个专业智能体协同工作的本地旅游小助手。这个系统不仅能根据用户一句话生成完整的旅行计划、推荐个性化景点,还能逐步扩展至查询实时天气、预订酒店、规划交通等实用功能,真正实现“一句话开启智能旅行”。 一、本地启动智能体 要运行基于 OpenAgents 的智能体系统,首先需要启动其核心网络服务。该服务负责管理所有 Agent 的注册、通信和协作,是整个多智能体生态的“中枢神经系统”。 在终端中执行以下命令: openagents network start 首次运行时,OpenAgents 会自动打开浏览器,引导你完成初始化配置(

By Ne0inhk