news 2026/5/25 19:50:36

手撸 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/5/25 21:17:30

我为什么要离开家乡,来北京打拼?(说说我自己的故事...)

建了一个新号&#xff1a;1. 讲职场与第二曲线&#xff1b;2. 聊自己的故事&#xff0c;内心的感悟。谢谢大家&#xff0c;听我的故事。希望对大伙也有帮助。最近做了一个新产品&#xff1a;70天&#xff0c;每天30分钟&#xff0c;短视频行动营&#xff08;第二曲线最佳选择&a…

作者头像 李华
网站建设 2026/5/26 8:37:26

如何在 LTspice放置 .op data 并能够设置显示的小数点个数?

简 介&#xff1a; 本文介绍了在LTspice中格式化.op数据标签的方法。通过使用round函数可以设置显示数据的小数点位数&#xff0c;使仿真结果更加简洁直观。具体操作是右键点击.op数据标签&#xff0c;使用round函数调整小数位数。这种方法能有效优化电路静态偏置量的显示效果&…

作者头像 李华
网站建设 2026/5/26 6:05:20

Wan2.2-T2V-A14B支持长时间序列生成吗?实测60秒连续输出

Wan2.2-T2V-A14B支持长时间序列生成吗&#xff1f;实测60秒连续输出 在影视制作、广告创意和虚拟内容生产领域&#xff0c;一个长期悬而未决的难题是&#xff1a;AI能否真正理解“时间”&#xff1f; 不是简单拼接几帧画面&#xff0c;也不是靠后期插值强行延长视频&#xff…

作者头像 李华
网站建设 2026/5/26 6:15:45

【高效运维必看】:Agent服务在Docker中跨环境迁移的7种优化方案

第一章&#xff1a;Agent服务在Docker中跨环境迁移的核心挑战在将Agent服务通过Docker容器化部署并实现跨环境迁移的过程中&#xff0c;尽管容器技术提供了“一次构建&#xff0c;处处运行”的理想承诺&#xff0c;实际落地仍面临诸多核心挑战。这些挑战主要集中在配置管理、网…

作者头像 李华
网站建设 2026/5/26 1:03:36

深度指南:如何设计Prompt引导DeepSeek生成高效的分步故障排查流程

深度指南&#xff1a;如何设计Prompt引导DeepSeek生成高效的分步故障排查流程在当今技术驱动的世界中&#xff0c;系统、设备或应用程序出现故障几乎是不可避免的。快速、准确地定位并解决这些故障对于维持业务连续性、提升用户体验以及降低运营成本至关重要。传统的故障排查手…

作者头像 李华