手撸 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源码"获取手撸源码,让更多小伙伴一起进步!

相关推荐
snow@li11 小时前
Java:事务管理全景梳理/解决业务系统数据一致性问题
java
额额额对了11 小时前
Linux 进程管理详解:从概念到实战
java·服务器·前端
evans在进步11 小时前
Spring Boot 启动流程与 @SpringBootApplication 核心原理详解
java·spring boot·后端
许彰午11 小时前
11-rawSql占位符替换
java·低代码·架构
我叫黑大帅11 小时前
关于没有对生产者做校验的思考
前端·面试·架构
摇滚侠11 小时前
《SpringBoot 3:入门与应用实战》第 6 章 Spring Boot 最佳实践 阅读笔记 12
android·spring boot·笔记
码匠许师傅11 小时前
【C++ 面试真题】聊聊 C++ 的类型特征( type_traits)
java·c++·面试
java1234_小锋11 小时前
Spring框架的创始人开发了一个Java AI智能体框架
java·人工智能·spring
FPC工厂——皇榜科技11 小时前
【皇榜科技线路板质量课堂·第40篇】PPAP(生产件批准程序):批量生产前的“终极面试”
科技·面试·职场和发展·制造·pcb工艺
夏天拐跑了西瓜11 小时前
Eureka注册中心——单机与高可用集群搭建
java·spring cloud·微服务·eureka