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

相关推荐
没有bug.的程序员15 小时前
服务安全:内部服务如何防止“裸奔”?
java·网络安全·云原生安全·服务安全·零信任架构·微服务安全·内部鉴权
一线大码15 小时前
SpringBoot 3 和 4 的版本新特性和升级要点
java·spring boot·后端
weixin_4407305015 小时前
java数组整理笔记
java·开发语言·笔记
weixin_4250230015 小时前
Spring Boot 配置文件优先级详解
spring boot·后端·python
weixin_4250230015 小时前
Spring Boot 实用核心技巧汇总:日期格式化、线程管控、MCP服务、AOP进阶等
java·spring boot·后端
一线大码15 小时前
Java 8-25 各个版本新特性总结
java·后端
2501_9061505616 小时前
私有部署问卷系统操作实战记录-DWSurvey
java·运维·服务器·spring·开源
better_liang16 小时前
每日Java面试场景题知识点之-TCP/IP协议栈与Socket编程
java·tcp/ip·计算机网络·网络编程·socket·面试题
VX:Fegn089516 小时前
计算机毕业设计|基于springboot + vue校园社团管理系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·后端·课程设计
niucloud-admin16 小时前
java服务端——controller控制器
java·开发语言