2025年11月23日

[手写 MiniSpring 框架教程] 第 10 讲:AOP 实现

📖 本讲目标

  • 实现 @Aspect 切面类
  • 实现 @Before 和 @After 通知
  • 实现切点表达式匹配
  • 创建代理对象
  • 集成 AOP 到 ApplicationContext

🏷️ AOP 注解实现

@Aspect 注解

创建 src/main/java/com/minispring/aop/Aspect.java

package com.minispring.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Aspect 注解用于标记一个类为切面类
 * 切面类中包含通知方法(@Before、@After)
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
    /**
     * 切面类的名称
     */
    String value() default "";
}

@Before 注解

创建 src/main/java/com/minispring/aop/Before.java

package com.minispring.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Before 注解用于标记前置通知方法
 * 在目标方法执行之前执行
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Before {
    /**
     * 切点表达式,用于匹配目标方法
     * 格式:类名.方法名 或 类名.*(匹配该类的所有方法)
     * 例如:com.example.UserService.saveUser
     *       com.example.UserService.*
     */
    String value();
}

@After 注解

创建 src/main/java/com/minispring/aop/After.java

package com.minispring.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @After 注解用于标记后置通知方法
 * 在目标方法执行之后执行(无论是否抛出异常)
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface After {
    /**
     * 切点表达式,用于匹配目标方法
     * 格式:类名.方法名 或 类名.*(匹配该类的所有方法)
     * 例如:com.example.UserService.saveUser
     *       com.example.UserService.*
     */
    String value();
}

🔧 AOP 代理实现

AopProxy 类设计

创建 src/main/java/com/minispring/aop/AopProxy.java

package com.minispring.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * AOP 代理工厂,负责创建代理对象
 */
public class AopProxy {

    /**
     * 切点映射:类名 -> 通知列表
     */
    private final Map<String, List<Advice>> pointcutMap = new ConcurrentHashMap<>();

    /**
     * 注册切面
     * @param aspectBean 切面 Bean 实例
     * @param aspectClass 切面类
     */
    public void registerAspect(Object aspectBean, Class<?> aspectClass) {
        Method[] methods = aspectClass.getDeclaredMethods();

        for (Method method : methods) {
            // 处理 @Before 注解
            Before before = method.getAnnotation(Before.class);
            if (before != null) {
                String pointcut = before.value();
                registerAdvice(pointcut, aspectBean, method, AdviceType.BEFORE);
            }

            // 处理 @After 注解
            After after = method.getAnnotation(After.class);
            if (after != null) {
                String pointcut = after.value();
                registerAdvice(pointcut, aspectBean, method, AdviceType.AFTER);
            }
        }
    }

    /**
     * 注册通知
     */
    private void registerAdvice(String pointcut, Object aspectBean, Method adviceMethod, AdviceType type) {
        // 解析切点表达式
        // 支持格式:com.example.UserService.saveUser 或 com.example.UserService.*
        String[] parts = pointcut.split("\.");
        if (parts.length < 2) {
            throw new IllegalArgumentException("无效的切点表达式: " + pointcut);
        }

        // 构建类名和方法名模式
        String className = String.join(".", java.util.Arrays.copyOf(parts, parts.length - 1));
        String methodPattern = parts[parts.length - 1];

        String key = className;
        pointcutMap.computeIfAbsent(key, k -> new ArrayList<>())
                .add(new Advice(className, methodPattern, aspectBean, adviceMethod, type));
    }

    /**
     * 创建代理对象
     * @param target 目标对象
     * @return 代理对象
     */
    public Object createProxy(Object target) {
        Class<?> targetClass = target.getClass();
        String className = targetClass.getName();

        // 检查是否有匹配的切点
        List<Advice> advices = pointcutMap.get(className);
        if (advices == null || advices.isEmpty()) {
            return target; // 没有切点,直接返回原对象
        }

        // 使用 JDK 动态代理
        ClassLoader classLoader = targetClass.getClassLoader();
        Class<?>[] interfaces = targetClass.getInterfaces();

        if (interfaces.length == 0) {
            // 如果没有接口,无法使用 JDK 动态代理,返回原对象
            System.out.println("  ⚠ 类 " + className + " 没有实现接口,无法创建代理");
            return target;
        }

        return Proxy.newProxyInstance(classLoader, interfaces, 
                new AopInvocationHandler(target, advices));
    }

    /**
     * 通知类型
     */
    enum AdviceType {
        BEFORE, AFTER
    }

    /**
     * 通知信息
     */
    static class Advice {
        final String className;
        final String methodPattern;
        final Object aspectBean;
        final Method adviceMethod;
        final AdviceType type;

        Advice(String className, String methodPattern, Object aspectBean, 
               Method adviceMethod, AdviceType type) {
            this.className = className;
            this.methodPattern = methodPattern;
            this.aspectBean = aspectBean;
            this.adviceMethod = adviceMethod;
            this.type = type;
        }

        /**
         * 检查方法是否匹配切点
         */
        boolean matches(String methodName) {
            if ("*".equals(methodPattern)) {
                return true;
            }
            return methodPattern.equals(methodName);
        }
    }

    /**
     * AOP 调用处理器
     */
    static class AopInvocationHandler implements InvocationHandler {
        private final Object target;
        private final List<Advice> advices;

        AopInvocationHandler(Object target, List<Advice> advices) {
            this.target = target;
            this.advices = advices;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();

            // 执行前置通知
            for (Advice advice : advices) {
                if (advice.type == AdviceType.BEFORE && advice.matches(methodName)) {
                    advice.adviceMethod.setAccessible(true);
                    advice.adviceMethod.invoke(advice.aspectBean);
                }
            }

            // 执行目标方法
            Object result = null;
            try {
                result = method.invoke(target, args);
            } finally {
                // 执行后置通知(无论是否抛出异常)
                for (Advice advice : advices) {
                    if (advice.type == AdviceType.AFTER && advice.matches(methodName)) {
                        advice.adviceMethod.setAccessible(true);
                        advice.adviceMethod.invoke(advice.aspectBean);
                    }
                }
            }

            return result;
        }
    }
}

🔗 集成到 ApplicationContext

添加 AopProxy

ApplicationContext 中添加 AOP 支持:

/**
 * AOP 代理工厂
 */
private final AopProxy aopProxy = new AopProxy();

注册切面

在创建 Bean 时注册切面:

private Object createBean(String beanName, BeanDefinition bd) throws Exception {
    // ... 前面的代码 ...

    // 6. 注册切面(如果是切面类)
    if (bd.getBeanClass().isAnnotationPresent(Aspect.class)) {
        aopProxy.registerAspect(bean, bd.getBeanClass());
        System.out.println("  ✓ 注册切面: " + beanName);
        // 切面 Bean 本身不需要代理
        return bean;
    }

    // 7. 应用 AOP 代理(非切面 Bean)
    Object proxy = aopProxy.createProxy(bean);
    if (proxy != bean) {
        System.out.println("  ✓ 创建 AOP 代理: " + beanName);
    }

    return proxy;
}

优先创建切面 Bean

确保切面 Bean 在其他 Bean 之前创建:

private void preInstantiateSingletons() {
    System.out.println("n开始预实例化单例 Bean...");

    // 第一阶段:优先创建切面 Bean
    for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
        String beanName = entry.getKey();
        BeanDefinition beanDefinition = entry.getValue();

        if (beanDefinition.isSingleton() && !beanDefinition.isLazy() &&
                beanDefinition.getBeanClass().isAnnotationPresent(Aspect.class)) {
            try {
                getBean(beanName);
            } catch (Exception e) {
                throw new RuntimeException("预实例化切面 Bean 失败: " + beanName, e);
            }
        }
    }

    // 第二阶段:创建其他 Bean(此时切面已注册,可以应用代理)
    for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
        String beanName = entry.getKey();
        BeanDefinition beanDefinition = entry.getValue();

        if (beanDefinition.isSingleton() && !beanDefinition.isLazy() &&
                !beanDefinition.getBeanClass().isAnnotationPresent(Aspect.class)) {
            try {
                getBean(beanName);
            } catch (Exception e) {
                throw new RuntimeException("预实例化 Bean 失败: " + beanName, e);
            }
        }
    }

    System.out.println("单例 Bean 预实例化完成n");
}

🧪 测试示例

测试类

package com.minispring.demo;

import com.minispring.aop.After;
import com.minispring.aop.Aspect;
import com.minispring.aop.Before;
import com.minispring.stereotype.Component;
import com.minispring.context.ApplicationContext;

// 接口
interface IUserService {
    void saveUser(String name);
}

// 实现类
@Component
class UserService implements IUserService {
    public void saveUser(String name) {
        System.out.println("保存用户: " + name);
    }
}

// 切面类
@Component
@Aspect
class LoggingAspect {
    @Before("com.minispring.demo.UserService.*")
    public void beforeMethod() {
        System.out.println("  [AOP-Before] 准备执行 UserService 方法...");
    }

    @After("com.minispring.demo.UserService.*")
    public void afterMethod() {
        System.out.println("  [AOP-After] UserService 方法执行完成");
    }
}

public class AopTest {
    public static void main(String[] args) throws Exception {
        ApplicationContext context = new ApplicationContext("com.minispring.demo");

        // 获取 Bean(返回代理对象)
        IUserService service = context.getBean(IUserService.class);

        // 调用方法(会触发 AOP)
        service.saveUser("张三");
    }
}

运行结果

注册 Bean: userService [scope=singleton, lazy=false]
注册 Bean: loggingAspect [scope=singleton, lazy=false]

开始预实例化单例 Bean...
创建 Bean: loggingAspect (LoggingAspect)
  ✓ 注册切面: loggingAspect
创建 Bean: userService (UserService)
  ✓ 创建 AOP 代理: userService

调用方法:
  [AOP-Before] 准备执行 UserService 方法...
保存用户: 张三
  [AOP-After] UserService 方法执行完成

📝 本讲总结

本讲我们完成了:

  1. ✅ 实现了 @Aspect、@Before、@After 注解
  2. ✅ 实现了 AopProxy 代理工厂
  3. ✅ 实现了切点表达式匹配
  4. ✅ 实现了 JDK 动态代理
  5. ✅ 集成了 AOP 到 ApplicationContext
  6. ✅ 实现了切面 Bean 的优先创建机制

🎯 关键点回顾

  1. 切面注册:扫描切面类的方法,注册通知
  2. 切点匹配:通过类名和方法名匹配切点
  3. 代理创建:使用 JDK 动态代理创建代理对象
  4. 通知执行:在方法调用前后执行通知
  5. 创建顺序:切面 Bean 需要优先创建

⚠️ 注意事项

  1. 接口要求:JDK 动态代理需要目标类实现接口
  2. 切点表达式:当前实现支持简单的类名.方法名格式
  3. 切面顺序:切面 Bean 必须在其他 Bean 之前创建
  4. 性能考虑:代理对象的方法调用有性能开销

🚀 下一讲预告

在下一讲中,我们将进行 项目总结与扩展,包括:

  • 功能总结
  • 性能优化建议
  • 扩展方向
  • 与 Spring 框架对比

准备好了吗?让我们继续 第 11 讲:项目总结与扩展


思考题

  1. 如何实现更复杂的切点表达式?
  2. 如何支持环绕通知(@Around)?
  3. 如何优化 AOP 的性能?

“以书为舟,遨游尘世”,
最好的免费 kindle 电子书分享站:

You may also like...

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注


*