手撸 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 注解

java 复制代码
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 处理逻辑。

scss 复制代码
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. 定义接口和实现类
java 复制代码
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. 编写切面类
kotlin 复制代码
@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)
kotlin 复制代码
@MyConfiguration(scanPackage = "org.example.spring6.aop")
public class AppConfig {
}
4. 测试主类
arduino 复制代码
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 切面与通知注解
MyInvocationHandler JdkDynamicAopProxy JDK 动态代理处理器
aspectBeans + adviceMethods AdvisorRegistry + PointcutAdvisor 切面注册与匹配
手动解析切点 PointcutExpression + AspectJExpressionPointcut Spring 使用 AspectJ 表达式

📌 关注我 ,每天5分钟,带你从 Java 小白变身编程高手!

👉 点赞 + 关注+私信 "AOP源码"获取手撸源码,让更多小伙伴一起进步!

相关推荐
code_li2 小时前
聊聊支付宝架构
java·开发语言·架构
CC.GG3 小时前
【Linux】进程概念(五)(虚拟地址空间----建立宏观认知)
java·linux·运维
以太浮标3 小时前
华为eNSP模拟器综合实验之- AC+AP无线网络调优与高密场景
java·服务器·华为
Mr__Miss3 小时前
JAVA面试-框架篇
java·spring·面试
Python算法实战3 小时前
《大模型面试宝典》(2026版) 正式发布!
人工智能·深度学习·算法·面试·职场和发展·大模型
小马爱打代码4 小时前
SpringBoot:封装 starter
java·spring boot·后端
STARSpace88884 小时前
SpringBoot 整合个推推送
java·spring boot·后端·消息推送·个推
码农幻想梦4 小时前
实验八 获取请求参数及域对象共享数据
java·开发语言·servlet
a努力。4 小时前
2026 AI 编程终极套装:Claude Code + Codex + Gemini CLI + Antigravity,四位一体实战指南!
java·开发语言·人工智能·分布式·python·面试
Dylan的码园4 小时前
功能包介绍 : calendar
java·jvm·eclipse