当前位置: 首页 > 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.zskr.cn/news/84183.html

相关文章:

  • 从零配置到高效开发,Cirq代码补全插件实战教程,量子程序员必备技能
  • **主题:** 医疗数据标准化漏异常值,后来补鲁棒缩放才稳住多中心模型预测
  • **主题:** “医疗PINN漏物理约束,器官运动预测全错,补动力学方程才稳住”
  • 【最详细】Kubernetes探针介绍、应用与最佳实践
  • Android数据库MVC模式应用——数据查询(用户登陆)
  • XUnity.AutoTranslator游戏翻译工具:5分钟实现游戏文本实时翻译的完整教程
  • Netbank与Thredd合作,助力其在菲律宾全境推出新一代卡片即服务解决方案
  • 【企业级Agent安全配置】:Docker环境下99%的人都忽略的5大安全隐患
  • 终极指南:深度解析Intel CPU电压调节的完整技术方案
  • 京东健康联合京东金榜发布2025年度三大品类金榜
  • BepInEx框架实战指南:从入门到精通的Unity模组开发全解析
  • 告别模糊卡顿!Wan2.2-T2V-A14B实现高分辨率视频流畅生成
  • Windows右键菜单大扫除:从杂乱无章到高效简洁的完整改造方案
  • 德意志飞机莱比锡总装线封顶庆典圆满举行 加速D328eco产业化进程
  • Lonsdor K518 Pro FCV Volvo LYNK CO License Activation – Key Programming for Mechanics Car Owners
  • 算法题 数据流中的第 K 大元素
  • 互聯網幻覺
  • OpenHarmony Flutter 分布式设备发现与组网:跨设备无感连接与动态组网方案
  • 解决力扣第26题,论删除重复项
  • vivo端侧AI新突破:30亿参数模型实现GUI界面深度理解,多模态能力领跑行业
  • 人工智能深度学习实战:手写数字识别指南
  • ISO图接点显示分区号
  • Hadoop-动态刷新hdfs/yarn配置
  • BetterGI深度评测:原神自动化工具的效率革命实战体验
  • Bili2text:重新定义视频内容处理效率
  • MoE架构加持的Wan2.2-T2V-A14B,如何提升动态细节表现力?
  • 揭秘空间转录组数据分析:如何用R语言完成单细胞分辨率下的精准定位
  • 从C++/MFC到CEF与TypeScript的桌面架构演进
  • 基于CANoe的CAPL语言打造UDS Bootloader刷写上位机程序
  • 【OD刷题笔记】- 分糖果