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 {@Overridepublic 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 {@Overridepublic int add(int num1, int num2) {return num1 + num2; // 纯业务代码}@Overridepublic int sub(int num1, int num2) {return num1 - num2;}@Overridepublic int mul(int num1, int num2) {return num1 * num2;}@Overridepublic 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*/@Overridepublic int add(int a, int b) {return a + b;}/*** 核心方法:减* @param a* @param b* @return*/@Overridepublic 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就明确知道:
LogCal
是切面原材料- 通过
@Before("execution(public int com.zhongge.aop.CapImpl.*(..))")
知道CapImpl
是目标原材料 - 通过
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 {@Beanpublic 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 {@Overridepublic 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
七、最终结论
基于以上分析,我们可以得出明确的结论:
- ✅
@Aspect
+@EnableAspectJAutoProxy
确实让Spring知道了LogCal
是切面原材料 - ✅
@Before("execution(...CapImpl...)")
确实让Spring知道了CapImpl
是目标原材料 - ✅
ioc.getBean("capImpl")
返回的确实是动态代理对象
证明方式:
- 注解解析日志显示原材料被识别
- 代理创建日志显示代理对象被创建
- 运行时调用序列证明切面逻辑先执行
- 类名检查证明返回的是代理类
这就是Spring AOP从注解声明到运行时代理的完整证据链!每个环节都有明确的代码证据支持这些结论。
对应的图解