news 2026/8/31 7:08:11

手撸 Spring 简易版 AOP

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手撸 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源码“获取手撸源码,让更多小伙伴一起进步!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/31 6:49:00

别再把数据管道当“体力活”了:从单体任务到事件驱动的升级之路

别再把数据管道当“体力活”了&#xff1a;从单体任务到事件驱动的升级之路 作者&#xff1a;Echo_Wish兄弟们&#xff0c;咱们今天聊点“掏心窝子”的大数据经验&#xff1a;现代数据管道到底应该怎么设计&#xff1f; 很多公司到现在还在用“单体式任务管道”——Airflow 一堆…

作者头像 李华
网站建设 2026/8/31 21:01:52

【面试现场】谢飞机大战Java面试官:从基础到架构的爆笑面试实录

【面试现场】谢飞机大战Java面试官&#xff1a;从基础到架构的爆笑面试实录 第一轮&#xff1a;Java基础与集合框架 面试官&#xff1a;谢飞机同学&#xff0c;你好。我是今天的面试官&#xff0c;我们开始第一轮技术面试。首先问个基础问题&#xff1a;Java中ArrayList和Linke…

作者头像 李华
网站建设 2026/9/1 2:45:03

2025.12.11总结

工作日精进&#xff1a;明天版本过点&#xff0c;今晚每个人都搞得很晚&#xff0c;我也是晚上11&#xff1a;30才回到租房。每天就这么起床&#xff0c;上班&#xff0c;吃饭&#xff0c;下班。租房&#xff0c;工位&#xff0c;食堂&#xff0c;三点一线。下班后&#xff0c;…

作者头像 李华
网站建设 2026/9/1 1:52:53

C++提高编程学习(更新中....)

1 模板1.1 模板的概念模板是一种通用的编程工具&#xff0c;允许使用虚拟类型&#xff08;泛型&#xff09;定义函数或类&#xff0c;从而实现对不同数据类型的复用。通过模板编写的代码可以自动适应多种数据类型&#xff0c;减少重复代码。模板的特点&#xff1a;通用性&#…

作者头像 李华
网站建设 2026/8/31 8:16:10

编程范式悄然转舵:从“规则编织”到“模型生长”​

我们正站在编程史的拐点——从“基于规则编程”的精密齿轮时代&#xff0c;迈入“基于基础模型编程”的智能涌现纪元。过去&#xff0c;我们用代码刻写每一条逻辑边界&#xff0c;像搭积木般构建功能&#xff1b;如今&#xff0c;大模型成了“会学习的土壤”&#xff0c;高价值…

作者头像 李华