news 2026/5/8 2:25:19

手撸 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/4 0:21:07

【企业级Agent安全配置】:Docker环境下99%的人都忽略的5大安全隐患

第一章&#xff1a;企业级Agent安全配置的核心挑战在现代分布式系统架构中&#xff0c;Agent作为连接终端节点与中央管理平台的关键组件&#xff0c;承担着数据采集、指令执行和状态上报等核心职责。然而&#xff0c;随着攻击面的不断扩展&#xff0c;企业级Agent的安全配置面临…

作者头像 李华
网站建设 2026/4/30 14:53:01

Comsol 超构表面远场偏振态绘制那些事儿

Comsol绘制超构表面远场偏振态 动量空间远场偏振far field polarization 绘制教程。 C点 V点识别 Comsol 超构表面动量空间参数图绘制在超构表面的研究领域中&#xff0c;利用 Comsol 绘制远场偏振态以及动量空间相关参数图是非常重要的工作。今天就来跟大家唠唠这其中的门道…

作者头像 李华
网站建设 2026/5/5 0:38:39

1.19 UGUI的准备数据流程

1.UGUI准备数据的流程a.PostLateUpdate.PlayerUpdateCanvases- 作用: 这是Unity在每帧的晚期更新(LateUpdate)之后, 专门用于更新所有Canvas(UI画布)的系统函数; 它负责驱动整个UI渲染流程- 详细流程: 它会调用Canvas.SendWillRenderCanvases(), 从而触发一系列UI更新操作, 包括…

作者头像 李华
网站建设 2026/4/28 13:41:50

MCP SC-400量子加密实战指南(从零到企业级安全架构)

第一章&#xff1a;MCP SC-400量子安全配置实务概述在当前量子计算快速发展的背景下&#xff0c;传统加密体系面临前所未有的破解风险。MCP SC-400作为新一代量子安全通信协议标准&#xff0c;旨在提供抗量子攻击的安全配置框架&#xff0c;保障关键基础设施与敏感数据的长期安…

作者头像 李华
网站建设 2026/5/2 19:19:35

JavaEE进阶——MyBatis动态SQL与图书管理系统实战

目录 MyBatis 进阶详解与图书管理系统实战 第一部分&#xff1a;核心知识点深度解析 1. 什么是动态 SQL&#xff1f;为什么需要它&#xff1f; 2. 动态 SQL 标签详解&#xff08;文档核心点扩展&#xff09; 2.1 标签&#xff1a;最常用的判断逻辑2.2 标签&#xff1a;万能…

作者头像 李华
网站建设 2026/4/21 0:39:15

初始网络原理

理论知识网络的发展历程单机时代->局域网->广域网->移动互联网时代(国内的网络发展相对较慢,2000年前后,才真正的进入了网络时代)组建网络的核心设备路由器和交换机是组建网络的核心设备(交换机可以认为是对路由器的接口进行拓展)网络通信基础知识网络互联的目的是为了…

作者头像 李华