05-springAOP的实现

Spring IOC与AOP全面详解

一、Spring IOC 三种实现方式

1.1 IOC 容器核心概念

IOC(控制反转):将对象的创建权和控制权交给Spring容器,实现解耦

1.2 三种实现方式对比

实现方式 配置方式 创建方式 优点 缺点
XML配置 XML文件配置bean 反射自动创建 配置集中,修改方便 类型不安全,配置繁琐
注解方式 类上添加注解 反射自动创建 简洁方便,类型安全 分散在各个类中
配置类 Java类替代XML 手动new对象 灵活,可编程配置 需要手动创建对象

1.3 XML配置方式(传统)

<!-- applicationContext.xml -->
<beans>
    <bean id="student" class="com.zhongge.entity.Student">
        <property name="id" value="1001"/>
        <property name="name" value="张三"/>
        <property name="age" value="20"/>
    </bean>
</beans>
// 使用
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) context.getBean("student");

1.4 注解方式(常用)

@Component  // 标记为Spring组件
public class Student {
    @Value("1001") private Integer id;
    @Value("张三") private String name;
    @Value("20") private Integer age;
    
    // getter/setter
}

// 配置类开启组件扫描
@Configuration
@ComponentScan("com.zhongge.entity")
public class AppConfig {
}

1.5 配置类方式(灵活)

@Configuration
public class StuConfig {
    
    @Bean("student")
    public Student createStudent() {
        Student student = new Student();
        student.setId(1001);
        student.setName("张三");
        student.setAge(20);
        return student;  // 手动创建对象
    }
}

二、AOP 面向切面编程深度解析

2.1 AOP 核心概念

问题场景:计算器类需要添加日志功能

public class CalImpl implements Cal {
    @Override
    public int add(int num1, int num2) {
        // 业务代码前:打印参数
        System.out.println("add方法的参数是[" + num1 + "," + num2 + "]");
        
        int result = num1 + num2;  // 业务逻辑
        
        // 业务代码后:打印结果
        System.out.println("add方法的结果是" + result);
        return result;
    }
    
    // 其他方法也需要相同的日志代码...
}

传统方式的问题

  • 代码重复:每个方法都要写日志代码
  • 耦合度高:业务代码和非业务代码混合
  • 维护困难:修改日志格式需要修改所有方法

2.2 AOP 解决方案

2.2.1 计算器接口和实现类

// 计算器接口
public interface Cal {
    int add(int num1, int num2);
    int sub(int num1, int num2);
    int mul(int num1, int num2);
    int div(int num1, int num2);
}

// 实现类 - 只关注业务逻辑
@Component
public class CalImpl implements Cal {
    @Override
    public int add(int num1, int num2) {
        return num1 + num2;  // 纯业务代码
    }

    @Override
    public int sub(int num1, int num2) {
        return num1 - num2;
    }

    @Override
    public int mul(int num1, int num2) {
        return num1 * num2;
    }

    @Override
    public int div(int num1, int num2) {
        return num1 / num2;
    }
}

2.2.2 切面类实现

package com.southwind.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
@Aspect  // 标记这是一个切面类
public class LoggerAspect {

    /**
     * 前置通知:在目标方法执行前执行
     * @param joinPoint 连接点,包含目标方法信息
     * 
     * execution(public int com.southwind.aop.CalImpl.*(..)) 解释:
     * - public: 方法修饰符
     * - int: 返回值类型  
     * - com.southwind.aop.CalImpl: 目标类全限定名
     * - *: 所有方法
     * - (..): 任意参数
     */
    @Before("execution(public int com.southwind.aop.CalImpl.*(..))")
    public void before(JoinPoint joinPoint){
        // 获取方法名
        String methodName = joinPoint.getSignature().getName();
        // 获取参数列表
        Object[] args = joinPoint.getArgs();
        String params = Arrays.toString(args);
        System.out.println(methodName + "方法的参数是" + params);
    }

    /**
     * 返回通知:在目标方法正常返回后执行
     * @param joinPoint 连接点
     * @param result 目标方法返回值
     */
    @AfterReturning(
        value = "execution(public int com.southwind.aop.CalImpl.*(..))",
        returning = "result"  // 绑定返回值
    )
    public void after(JoinPoint joinPoint, Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName + "方法的结果是" + result);
    }
}

2.2.3 配置类启用AOP

@Configuration
@ComponentScan("com.southwind.aop")  // 扫描组件
@EnableAspectJAutoProxy  // 启用AOP自动代理
public class AopConfig {
}

2.2.4 测试类

public class Test {
    public static void main(String[] args) {
        // 传统方式:直接创建对象,AOP不生效
        // Cal cal = new CalImpl();
        
        // Spring IOC + AOP:从容器获取代理对象
        ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
        Cal cal = context.getBean(Cal.class);  // 获取的是代理对象
        
        System.out.println("实际对象类型: " + cal.getClass());
        // 输出: class com.sun.proxy.$ProxyXX (JDK动态代理)
        
        cal.add(10, 3);
        cal.sub(10, 3);
        cal.mul(10, 3);
        cal.div(10, 3);
    }
}

输出结果

实际对象类型: class com.sun.proxy.$Proxy18
add方法的参数是[10, 3]
add方法的结果是13
sub方法的参数是[10, 3]  
sub方法的结果是7
mul方法的参数是[10, 3]
mul方法的结果是30
div方法的参数是[10, 3]
div方法的结果是3

2.3 AOP 底层原理:动态代理

2.3.1 为什么AOP需要接口?

// Spring AOP默认使用JDK动态代理,基于接口
public interface Cal {
    int add(int num1, int num2);
}

// JDK动态代理创建过程:
// 1. 在运行时动态创建实现Cal接口的代理类
// 2. 代理类重写接口方法,加入切面逻辑
// 3. 实际调用时,先执行切面逻辑,再调用目标方法

// 如果没有接口,Spring会使用CGLIB代理
// 但推荐使用接口,更符合面向接口编程原则

2.3.2 动态代理工作流程

调用cal.add(10, 3)
    ↓
代理对象拦截方法调用
    ↓
执行@Before前置通知
    ↓
调用真实CalImpl.add()方法
    ↓  
执行@AfterReturning返回通知
    ↓
返回结果给调用者

2.4 AOP 核心注解详解

注解 作用 执行时机
@Aspect 标记切面类 -
@Before 前置通知 目标方法执行前
@AfterReturning 返回通知 目标方法正常返回后
@AfterThrowing 异常通知 目标方法抛出异常后
@After 后置通知 目标方法执行后(无论是否异常)
@Around 环绕通知 包围目标方法执行

2.5 AOP 切入点表达式

// 常用切入点表达式示例:
@Before("execution(public * com.southwind.aop.*.*(..))")
// 解释:com.southwind.aop包下所有类的所有公共方法

@Before("execution(* com.southwind.aop.CalImpl.add(int, int))")
// 解释:CalImpl类的add方法,参数为两个int

@Before("execution(* *(..))")
// 解释:任意类的任意方法(不推荐,范围太广)

2.6 Spring Boot 中的AOP应用

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        
        Cal cal = context.getBean(Cal.class);
        System.out.println("=== AOP演示 ===");
        System.out.println(cal.add(10, 3));
        System.out.println(cal.sub(10, 3));
        System.out.println(cal.mul(10, 3));
        System.out.println(cal.div(10, 3));
    }
}

三、AOP 实际应用场景

3.1 日志记录

@Aspect
@Component
public class LoggingAspect {
    
    @Before("execution(* com.example.service.*.*(..))")
    public void logMethodCall(JoinPoint joinPoint) {
        // 记录方法调用日志
    }
}

3.2 性能监控

@Aspect  
@Component
public class PerformanceAspect {
    
    @Around("execution(* com.example.service.*.*(..))")
    public Object monitorPerformance(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();  // 执行目标方法
        long time = System.currentTimeMillis() - start;
        
        if(time > 1000) {  // 超过1秒记录警告
            System.out.println("方法执行过慢: " + pjp.getSignature() + ", 耗时: " + time + "ms");
        }
        return result;
    }
}

3.3 事务管理

@Aspect
@Component
public class TransactionAspect {
    
    @Around("@annotation(org.springframework.transaction.annotation.Transactional)")
    public Object manageTransaction(ProceedingJoinPoint pjp) throws Throwable {
        // 开启事务、提交/回滚事务逻辑
        return pjp.proceed();
    }
}

四、总结

4.1 IOC 核心要点

  • 控制反转:对象创建权交给容器
  • 依赖注入:对象依赖关系由容器注入
  • 三种方式:XML、注解、配置类,各有适用场景

4.2 AOP 核心要点

  • 横切关注点:将非业务代码(日志、事务等)从业务代码中分离
  • 动态代理:AOP底层基于JDK动态代理或CGLIB
  • 切面编程:通过切入点表达式定义在哪些方法上织入增强逻辑

4.3 AOP 优势

  • 解耦合:业务代码与非业务代码分离
  • 可维护性:集中管理横切关注点
  • 代码复用:一次编写,多处使用
  • 灵活性:通过配置动态添加/移除功能

补充知识:如何理解底层实现是AOP

代码

Cal接口 为了底层使用JDK动态代理

package com.zhongge.aop;

/**
 * @InterfaceName Cal
 * @Description TODO
 * @Author zhongge
 * @Version 1.0
 */
public interface Cal {
    int add(int a, int b);//加
    int sub(int a, int b);//减
}

Cal接口的实现类(目标类)

package com.zhongge.aop;

import org.springframework.stereotype.Component;

/**
 * @ClassName CapImpl
 * @Description TODO
 * @Author zhongge
 * @Version 1.0
 */
@Component//交给IOC容器管理
public class CapImpl implements Cal{
    /**
     * 核心方法:加
     * @param a
     * @param b
     * @return
     */
    @Override
    public int add(int a, int b) {
        return a + b;
    }

    /**
     * 核心方法:减
     * @param a
     * @param b
     * @return
     */
    @Override
    public int sub(int a, int b) {
        return a - b;
    }
}

切面类(辅助方法)

package com.zhongge.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * @ClassName LogCal
 * @Description TODO 计算器日志类
 * @Author zhongge
 * @Version 1.0
 */
@Component//交给IOC容器管理
@Aspect//这个注解告诉IOC此类是一个切面类
@EnableAspectJAutoProxy//翻译为:启动切面自动代理
public class LogCal {
    /**
     * 辅助方法:在核心方法之前执行
     */
    @Before("execution(public int com.zhongge.aop.CapImpl.*(..))")
    public void before(JoinPoint joinPoint) {
        //joinPoint就是核心方法
        String methodName = joinPoint.getSignature().getName();//获取核心方法的名称
        Object[] args = joinPoint.getArgs();//获取方法的参数
        String s = Arrays.toString(args);
        System.out.println(methodName + "方法的参数: "+s);
    }

    /**
     * 辅助方法:在核心方法之后执行
     */
    @AfterReturning(value = "execution(public int com.zhongge.aop.CapImpl.*(..))",returning = "result")
    public void after(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName + "方法的结果是" + result);
    }
}

测试类

package com.zhongge;

import com.zhongge.aop.CapImpl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class Springboot002AopApplication {

    public static void main(String[] args) {
        //springboot所返回的这个对象就是IOC容器
        ConfigurableApplicationContext ioc = SpringApplication.run(Springboot002AopApplication.class, args);
        CapImpl capImpl = (CapImpl) ioc.getBean("capImpl");
        capImpl.add(10,20);
        capImpl.sub(10,20);
    }

}

Spring AOP底层原理:注解如何驱动代理对象创建

核心结论

肯定的结论:一旦加上@Aspect@EnableAspectJAutoProxy注解,Spring就明确知道:

  1. LogCal是切面原材料
  2. 通过@Before("execution(public int com.zhongge.aop.CapImpl.*(..))")知道CapImpl是目标原材料
  3. 通过ioc.getBean("capImpl")获取的确实是动态代理对象

下面我详细解释这三点是如何在底层实现的。

一、注解的明确作用:Spring的"指令系统"

1.1 @Aspect = "我是切面类"

@Component
@Aspect  // ← 这个注解明确告诉Spring:"把我当成切面原材料!"
public class LogCal {
    // ...
}

Spring看到@Aspect时的反应

// Spring内部逻辑
if (class.hasAnnotation(Aspect.class)) {
    // 立即将此类标记为"切面原材料"
    aspectCandidates.add(beanDefinition);
    System.out.println("✅ 发现切面原材料: " + className);
}

1.2 @EnableAspectJAutoProxy = "请启用AOP代理功能"

@SpringBootApplication
@EnableAspectJAutoProxy  // ← 这个注解明确告诉Spring:"请开启AOP自动代理模式!"
public class Springboot002AopApplication {
    // ...
}

这个注解的实际效果

// @EnableAspectJAutoProxy背后的魔法
public class EnableAspectJAutoProxyConfiguration {
    
    @Bean
    public AnnotationAwareAspectJAutoProxyCreator aspectJAutoProxyCreator() {
        // 创建AOP的核心处理器
        return new AnnotationAwareAspectJAutoProxyCreator();
    }
}

二、原材料识别的完整过程

2.1 Spring启动时的"原材料收集"阶段

// Spring容器的启动过程
public class SpringContainerStartup {
    
    public void processAnnotations() {
        // 步骤1:发现切面原材料
        for (Class<?> candidate : allClasses) {
            if (candidate.isAnnotationPresent(Aspect.class)) {
                // 找到LogCal,标记为切面原材料
                registerAspect(candidate);
                System.out.println("🔍 找到切面原材料: LogCal");
            }
        }
        
        // 步骤2:解析切面中的目标类信息
        for (Aspect aspect : allAspects) {
            for (Method method : aspect.getMethods()) {
                if (method.isAnnotationPresent(Before.class)) {
                    String expression = method.getAnnotation(Before.class).value();
                    // 解析execution(public int com.zhongge.aop.CapImpl.*(..))
                    String targetClassName = extractTargetClass(expression);
                    // 找到CapImpl,标记为目标原材料
                    markTargetClass(targetClassName);
                    System.out.println("🎯 找到目标原材料: " + targetClassName);
                }
            }
        }
    }
}

2.2 具体的解析逻辑

// Spring如何从@Before注解中提取目标类信息
public class PointcutExpressionParser {
    
    public String extractTargetClass(String expression) {
        // execution(public int com.zhongge.aop.CapImpl.*(..))
        // ↓ 解析得到 ↓
        return "com.zhongge.aop.CapImpl";
    }
    
    public void processAspectMethod(Method method) {
        Before before = method.getAnnotation(Before.class);
        if (before != null) {
            String expression = before.value();
            
            // 明确的结论:从这里Spring知道了两个原材料的关系
            System.out.println("明确关系建立:");
            System.out.println("  切面: " + method.getDeclaringClass().getSimpleName());
            System.out.println("  目标: " + extractTargetClass(expression));
            System.out.println("  时机: 方法执行前");
        }
    }
}

三、动态代理对象的创建过程

3.1 Bean创建时的代理介入

// Spring创建Bean时的关键拦截点
public class AnnotationAwareAspectJAutoProxyCreator extends AbstractAutoProxyCreator {
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        // 检查当前Bean是否需要被代理
        if (isEligibleForProxy(bean, beanName)) {
            // 明确的结论:这里创建代理对象!
            Object proxy = createProxy(bean.getClass(), beanName, null, new SingletonTargetSource(bean));
            System.out.println("🚀 为 " + beanName + " 创建了动态代理对象");
            return proxy; // 返回代理对象,不是原始对象!
        }
        return bean;
    }
    
    private boolean isEligibleForProxy(Object bean, String beanName) {
        // 检查这个Bean是否匹配任何切面的切点
        // 对于capImpl,检查结果:true(因为匹配LogCal的切点)
        return !getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null).isEmpty();
    }
}

3.2 代理对象的具体创建

// 创建代理对象的详细过程
protected Object createProxy(Class<?> beanClass, String beanName, 
                           Object[] specificInterceptors, TargetSource targetSource) {
    
    // 明确的结论:这里根据条件选择代理方式
    if (shouldUseJdkDynamicProxy(beanClass, beanName)) {
        // 因为CapImpl实现了Cal接口,所以使用JDK动态代理
        return createJdkDynamicProxy(beanClass, beanName, specificInterceptors, targetSource);
    } else {
        return createCglibProxy(beanClass, beanName, specificInterceptors, targetSource);
    }
}

private Object createJdkDynamicProxy(Class<?> beanClass, String beanName, 
                                   Object[] specificInterceptors, TargetSource targetSource) {
    // 明确的结论:这里创建JDK动态代理对象!
    return Proxy.newProxyInstance(
        beanClass.getClassLoader(),
        beanClass.getInterfaces(), // 获取Cal接口
        new JdkDynamicAopProxy(targetSource, specificInterceptors, beanName)
    );
}

四、获取代理对象的证据

4.1 ioc.getBean("capImpl")返回的是什么?

// 你的代码:
CapImpl capImpl = (CapImpl) ioc.getBean("capImpl");

// 实际上发生的是:
Object actualObject = ioc.getBean("capImpl");
System.out.println("实际对象类型: " + actualObject.getClass().getName());
// 输出: com.sun.proxy.$Proxy123 或 CapImpl$$EnhancerBySpringCGLIB$$...

// 明确的结论:这里获取的是代理对象,不是原始CapImpl实例!

4.2 证明这是代理对象的方法

public class ProofOfProxy {
    
    public static void main(String[] args) {
        ConfigurableApplicationContext ioc = SpringApplication.run(Application.class, args);
        
        // 方法1:打印类名
        Object bean = ioc.getBean("capImpl");
        System.out.println("Bean类名: " + bean.getClass().getName());
        // 如果输出包含 $Proxy 或 $$EnhancerBySpringCGLIB$$,说明是代理对象
        
        // 方法2:检查接口
        if (bean instanceof Cal) {
            System.out.println("✅ 实现了Cal接口");
        }
        
        // 方法3:检查是否是代理
        if (AopUtils.isAopProxy(bean)) {
            System.out.println("✅ 这是AOP代理对象");
        }
        
        // 方法4:获取原始目标对象
        Object target = AopProxyUtils.getSingletonTarget(bean);
        System.out.println("原始目标对象: " + (target != null ? target.getClass().getName() : "null"));
    }
}

五、完整的证据链条

5.1 从注解到代理的完整证据

证据链开始:
1. @Aspect注解 → Spring识别LogCal为切面 ✅
2. @Before注解 → Spring知道要增强CapImpl ✅  
3. @EnableAspectJAutoProxy → Spring启用代理创建 ✅
4. Bean创建时 → Spring检查到CapImpl需要代理 ✅
5. 代理创建 → Spring创建JDK动态代理对象 ✅
6. getBean() → 返回代理对象,不是原始对象 ✅
7. 方法调用 → 先执行切面逻辑,证明是代理 ✅

5.2 运行时证据

当你执行:

capImpl.add(10, 20);

实际发生的调用序列证明了一切:

1. 代理对象的add方法被调用
2. LogCal.before() 先执行 ← 这证明是代理对象!
3. 然后才是真实的add逻辑
4. LogCal.after() 最后执行

如果这不是代理对象,第2步根本不会发生!

六、底层源码的关键位置

6.1 原材料识别的关键类

// Spring扫描和识别原材料的核心类
org.springframework.context.annotation.ClassPathBeanDefinitionScanner
org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder

6.2 代理创建的关键类

// 创建代理对象的核心类
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator
org.springframework.aop.framework.JdkDynamicAopProxy
org.springframework.aop.framework.CglibAopProxy

6.3 注解处理的关键类

// 处理注解的核心类
org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory

七、最终结论

基于以上分析,我们可以得出明确的结论:

  1. @Aspect + @EnableAspectJAutoProxy 确实让Spring知道了LogCal是切面原材料
  2. @Before("execution(...CapImpl...)") 确实让Spring知道了CapImpl是目标原材料
  3. ioc.getBean("capImpl") 返回的确实是动态代理对象

证明方式

  • 注解解析日志显示原材料被识别
  • 代理创建日志显示代理对象被创建
  • 运行时调用序列证明切面逻辑先执行
  • 类名检查证明返回的是代理类

这就是Spring AOP从注解声明到运行时代理的完整证据链!每个环节都有明确的代码证据支持这些结论。

对应的图解

image
image

posted on 2025-10-05 19:08  笨忠  阅读(2)  评论(0)    收藏  举报