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

相关推荐
Java天梯之路2 小时前
Spring AOP 源码深度解析:从代理创建到通知执行的完整链路
java·spring boot·面试
小光学长2 小时前
基于web的影视网站设计与实现14yj533o(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
java·前端·数据库
何中应2 小时前
【面试题-2】Java集合
java·开发语言·后端·面试题
BullSmall2 小时前
Tomcat SSL 配置及常见问题
java·tomcat·ssl
璞瑜无文2 小时前
Unity 游戏开发之方块随机生成(三)
java·unity·游戏引擎
周杰伦_Jay2 小时前
【JVM深度解析】运行时数据区+类加载+GC+调优实战(附参数示例)
java·jvm·spring boot·分布式·架构·java-ee
松莫莫2 小时前
【Spring Boot 实战】使用 Server-Sent Events (SSE) 实现实时消息推送
java·spring boot·后端
SoleMotive.2 小时前
springai和langchain4j的区别
java
子超兄2 小时前
GC/OOM问题处理思路
java·jvm