当前位置: 首页 > news >正文

手撸 Spring 简易版 AOP

✅ 手撸 Spring 简易版 AOP

一、核心目标

在已有 IOC 容器基础上,新增 AOP 能力,包含:

  1. 自定义注解@MyAspect@MyBefore@MyAfter
  2. 切面类识别与注册;
  3. 使用 JDK 动态代理对目标 Bean 进行代理;
  4. 支持方法执行前/后通知(Before / After);
  5. 与 IOC 容器无缝集成(依赖注入 + AOP 代理)。

💡 注意:为简化,仅支持接口代理(JDK Proxy),不支持 CGLIB(无接口类)。

二、完整实现代码

步骤 1:定义 AOP 注解

import java.lang.annotation.*; // 标记切面类 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyAspect { } // 前置通知 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyBefore { String value(); // 切点表达式,如 "com.example.service.UserServiceImpl.getUser" } // 后置通知 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAfter { String value(); }

📌 切点表达式简化为全限定方法名(如org.example.service.UserServiceImpl.getUser),不使用 AspectJ 表达式。

步骤 2:扩展 MyApplicationContext,支持 AOP

在原有 IOC 容器中增加 AOP 处理逻辑。

import java.lang.reflect.*; import java.util.*; // 新增导入 import java.util.concurrent.ConcurrentHashMap; public class MyApplicationContext { private Map<String, MyBeanDefinition> beanDefinitionMap = new HashMap<>(); private Map<String, Object> singletonObjects = new ConcurrentHashMap<>(); // 改为线程安全 private Class<?> configClass; // 新增:存储切面信息 { 切点方法全名 -> 切面对象 } private Map<String, Object> aspectBeans = new HashMap<>(); // 存储 Before 方法 private Map<String, Method> beforeAdviceMethods = new HashMap<>(); // 存储 After 方法 private Map<String, Method> afterAdviceMethods = new HashMap<>(); public MyApplicationContext(Class<?> configClass) { this.configClass = configClass; scanAndRegisterBeanDefinitions(); registerAspects(); // 新增:注册切面 instantiateSingletons(); } // ====== 原有方法保持不变(scanAndRegisterBeanDefinitions, recursiveScan 等)====== // 新增:扫描并注册所有 @MyAspect 切面 private void registerAspects() { for (Map.Entry<String, MyBeanDefinition> entry : beanDefinitionMap.entrySet()) { Class<?> clazz = entry.getValue().getBeanClass(); if (clazz.isAnnotationPresent(MyAspect.class)) { String beanName = entry.getKey(); Object aspectBean = createBean(beanName, entry.getValue()); // 先实例化切面(无依赖注入) aspectBeans.put(beanName, aspectBean); // 解析切面中的 @MyBefore / @MyAfter for (Method method : clazz.getDeclaredMethods()) { if (method.isAnnotationPresent(MyBefore.class)) { MyBefore before = method.getAnnotation(MyBefore.class); String pointcut = before.value(); beforeAdviceMethods.put(pointcut, method); } if (method.isAnnotationPresent(MyAfter.class)) { MyAfter after = method.getAnnotation(MyAfter.class); String pointcut = after.value(); afterAdviceMethods.put(pointcut, method); } } } } } // 重写 createBean:如果目标 Bean 有切面,则返回代理对象 private Object createBean(String beanName, MyBeanDefinition beanDefinition) { Class<?> beanClass = beanDefinition.getBeanClass(); try { Object beanInstance = beanClass.getDeclaredConstructor().newInstance(); populateBean(beanInstance); // 依赖注入 // 检查是否需要 AOP 代理 if (needsProxy(beanClass)) { return createProxy(beanInstance, beanClass); } return beanInstance; } catch (Exception e) { throw new RuntimeException("创建 Bean 失败:" + beanName, e); } } // 判断是否需要代理:只要存在匹配的切点就代理 private boolean needsProxy(Class<?> targetClass) { for (String pointcut : beforeAdviceMethods.keySet()) { if (pointcut.startsWith(targetClass.getName())) { return true; } } for (String pointcut : afterAdviceMethods.keySet()) { if (pointcut.startsWith(targetClass.getName())) { return true; } } return false; } // 创建 JDK 动态代理 private Object createProxy(Object target, Class<?> targetClass) { return Proxy.newProxyInstance( targetClass.getClassLoader(), targetClass.getInterfaces(), // 必须有接口! new MyInvocationHandler(target, targetClass) ); } // 自定义 InvocationHandler private class MyInvocationHandler implements InvocationHandler { private Object target; private Class<?> targetClass; public MyInvocationHandler(Object target, Class<?> targetClass) { this.target = target; this.targetClass = targetClass; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String fullMethodName = targetClass.getName() + "." + method.getName(); // 执行 @MyBefore if (beforeAdviceMethods.containsKey(fullMethodName)) { Method beforeMethod = beforeAdviceMethods.get(fullMethodName); String aspectBeanName = findAspectBeanForMethod(beforeMethod); Object aspect = aspectBeans.get(aspectBeanName); beforeMethod.setAccessible(true); beforeMethod.invoke(aspect); } // 执行目标方法 Object result = method.invoke(target, args); // 执行 @MyAfter if (afterAdviceMethods.containsKey(fullMethodName)) { Method afterMethod = afterAdviceMethods.get(fullMethodName); String aspectBeanName = findAspectBeanForMethod(afterMethod); Object aspect = aspectBeans.get(aspectBeanName); afterMethod.setAccessible(true); afterMethod.invoke(aspect); } return result; } // 辅助:根据通知方法反推切面 Bean 名称 private String findAspectBeanForMethod(Method adviceMethod) { Class<?> aspectClass = adviceMethod.getDeclaringClass(); for (Map.Entry<String, Object> entry : aspectBeans.entrySet()) { if (entry.getValue().getClass() == aspectClass) { return entry.getKey(); } } throw new RuntimeException("未找到切面对应的 Bean:" + aspectClass.getName()); } } // ====== 原有方法:populateBean, getBean, 工具方法等保持不变 ====== // (此处省略,与你提供的代码一致) }

⚠️ 注意:目标类必须实现接口,否则Proxy.newProxyInstance会失败。

步骤 3:编写测试用例

1. 定义接口和实现类
public interface UserService { void getUser(); } @MyComponent("userService") public class UserServiceImpl implements UserService { @MyAutowired private UserDao userDao; @Override public void getUser() { userDao.queryUser(); System.out.println("业务逻辑:获取用户"); } }
2. 编写切面类
@MyAspect @MyComponent("logAspect") public class LogAspect { @MyBefore("com.example.spring6.aop.demo.UserServiceImpl.getUser") public void beforeGetUser() { System.out.println("【AOP 前置通知】准备调用 getUser 方法"); } @MyAfter("com.example.spring6.aop.demo.UserServiceImpl.getUser") public void afterGetUser() { System.out.println("【AOP 后置通知】getUser 方法执行完毕"); } }
3. 配置类(同 IOC)
@MyConfiguration(scanPackage = "org.example.spring6.aop") public class AppConfig { }
4. 测试主类
public class MyAopTest { public static void main(String[] args) { MyApplicationContext context = new MyApplicationContext(AppConfig.class); UserService userService = (UserService) context.getBean("userService"); userService.getUser(); } }
运行结果
【AOP 前置通知】准备调用 getUser 方法 Spring 6.x 简易 IOC:查询用户信息 业务逻辑:获取用户 【AOP 后置通知】getUser 方法执行完毕

✅ 成功实现 AOP 通知!

三、简易 AOP vs Spring 6.x 对比

简易 AOP 组件Spring 6.x 原生组件说明
@MyAspect/@MyBefore@Aspect/@Before切面与通知注解
MyInvocationHandlerJdkDynamicAopProxyJDK 动态代理处理器
aspectBeans+adviceMethodsAdvisorRegistry+PointcutAdvisor切面注册与匹配
手动解析切点PointcutExpression+AspectJExpressionPointcutSpring 使用 AspectJ 表达式

📌关注我,每天5分钟,带你从 Java 小白变身编程高手!
👉 点赞 + 关注+私信 ”AOP源码“获取手撸源码,让更多小伙伴一起进步!

http://www.jsqmd.com/news/73656/

相关文章:

  • 从文本到电影级画面:Wan2.2-T2V-A14B视频生成技术拆解
  • Venture Global宣布完成Venture Global Plaquemines LNG, LLC 30亿美元高级担保票据发行
  • 从零配置到高效开发,Cirq代码补全插件实战教程,量子程序员必备技能
  • 车联网时序数据库哪个好
  • **主题:** 医疗数据标准化漏异常值,后来补鲁棒缩放才稳住多中心模型预测
  • 基于PLC的室内空气净化器控制系统设计
  • 别再把数据管道当“体力活”了:从单体任务到事件驱动的升级之路
  • 百度ERNIE模型家族2025年度技术突破全景:从基础研究到产业落地的里程碑跨越
  • Skyhigh Security升级数据安全态势管理(DSPM)能力,助力企业满足《数字个人数据保护法》(DPDPA)合规要求,强化亚太地区数据保护
  • 【最详细】Kubernetes探针介绍、应用与最佳实践
  • **主题:** “医疗PINN漏物理约束,器官运动预测全错,补动力学方程才稳住”
  • 玩转 Linux passwd 命令:从密码修改到批量运维,一篇吃透!
  • 从Bash脚本到Firebase数据库:解决JSON上传问题
  • Comsol 超构表面远场偏振态绘制那些事儿
  • 基于大数据的手机商品电商数据分析系统Scrapy+hadoop
  • BepInEx模组开发终极指南:5步搞定Unity游戏插件框架
  • 永磁同步电机滑模观测器Simulink搭建模型探索
  • 【新】基于SSM的实验室管理系统【包括源码+文档+调试】
  • 【MCP AZ-500安全防护终极指南】:掌握云Agent安全加固的7大核心策略
  • Wan2.2-T2V-A14B助力元宇宙内容生产:虚拟人视频自动生成
  • 为什么顶尖数据团队都在用R Shiny做多模态展示?真相令人震惊
  • Android数据库MVC模式应用——数据查询(用户登陆)
  • Easily Program Borgward Keys: Lonsdor K518 PRO FCV License Activation
  • XUnity.AutoTranslator游戏翻译工具:5分钟实现游戏文本实时翻译的完整教程
  • Wan2.2-T2V-A14B支持长时间序列生成吗?实测60秒连续输出
  • 【R语言高手进阶指南】:5步搞定农业产量的复杂数据建模
  • 关于文章仿写的专业指南与实践要点
  • 【高效运维必看】:Agent服务在Docker中跨环境迁移的7种优化方案
  • Netbank与Thredd合作,助力其在菲律宾全境推出新一代卡片即服务解决方案
  • 【企业级Agent安全配置】:Docker环境下99%的人都忽略的5大安全隐患