Spring 核心深度解析:IoC, DI 与 AOP
·
Spring 核心深度解析:IoC, DI 与 AOP
1. 控制反转 (IoC) 与 依赖注入 (DI)
概念区别
- IoC (Inversion of Control - 控制反转):这是一种设计思想。传统开发中,我们通过
new手动创建对象;在 Spring 中,对象的创建、销毁、生命周期控制全部交给 Spring 容器。控制权从程序员手里反转到了框架手里。 - DI (Dependency Injection - 依赖注入):这是 IoC 的具体实现方式。容器在运行期间,动态地将某种依赖关系注入到组件之中。
使用场景
- 解耦:当 Service 层需要调用 Repository 层时,通过 DI 注入接口,无需关心具体实现。
- 资源管理:单例对象的全局管理(如数据库连接池、配置类)。
源码级原理解析
Spring IoC 的核心是 BeanFactory 和 ApplicationContext。其初始化流程简化如下:
- Resource 定位:找到 XML 或注解定义的 Bean。
- BeanDefinition 载入:将 Bean 的元数据(类名、属性、作用域)封装成
BeanDefinition对象。 - 注册 Bean:存入一个
ConcurrentHashMap中。 - 实例化与 DI:在
getBean()时,通过反射创建对象并注入属性。
Java
// 简化版的 IoC 容器实现逻辑
public class SimpleIoCContainer {
private Map<String, Object> beans = new ConcurrentHashMap<>();
public void registerBean(String id, Class<?> clazz) throws Exception {
Object bean = clazz.getDeclaredConstructor().newInstance();
beans.put(id, bean);
}
public Object getBean(String id) {
return beans.get(id);
}
}
实战代码演示
1. 定义接口与实现类
Java
public interface MessageService {
String getMessage();
}
@Service
public class EmailServiceImpl implements MessageService {
@Override
public String getMessage() {
return "Email: Hello Spring IoC!";
}
}
2. 依赖注入 (DI)
Java
@Component
public class NotificationClient {
// 构造器注入(推荐:保证依赖不可变且不为null)
private final MessageService messageService;
@Autowired
public NotificationClient(MessageService messageService) {
this.messageService = messageService;
}
public void send() {
System.out.println(messageService.getMessage());
}
}
2. 面向切面编程 (AOP)
核心术语
- Aspect (切面):关注点的模块化(如日志、事务)。
- Join Point (连接点):程序执行的某个特定位置(如方法执行前)。
- Advice (通知):切面在特定连接点执行的动作(Before, After, Around)。
- Pointcut (切点):匹配连接点的表达式。
使用场景
- 日志记录:记录所有 Controller 的请求参数与响应时间。
- 权限校验:执行敏感方法前检查用户权限。
- 声明式事务:在 Service 方法开始前开启事务,结束后提交或回滚。
源码级原理解析
Spring AOP 的底层实现是 动态代理:
- JDK 动态代理:目标类实现了接口,Spring 使用
java.lang.reflect.Proxy。 - CGLIB 代理:目标类没有实现接口,Spring 通过修改字节码生成子类。
核心拦截链 (Interceptor Chain):
当调用代理对象的方法时,会触发 ReflectiveMethodInvocation.proceed(),它会按顺序执行所有的 Advice。
2.1 AOP 核心注解详解
在 Spring 中使用 AOP,通常配合 AspectJ 语法的注解。
注解 功能点描述 使用方法 / 触发时机 @Aspect声明切面 用于类上,标识这是一个切面类,包含通知和切点。 @Pointcut定义切点 声明哪些方法需要被拦截。常用表达式: execution(* com.xxx.service.*.*(..))。@Before前置通知 在目标方法执行前运行。常用于参数校验、数据初始化。 @After后置通知 在目标方法执行后运行(无论成功还是异常)。常用于资源释放。 @AfterReturning返回通知 在目标方法正常返回后运行。可以获取方法的返回值。 @AfterThrowing异常通知 在目标方法抛出异常后运行。用于异常监控、日志记录。 @Around环绕通知 最强大。手动控制 proceed(),可在方法前后自定义逻辑,甚至改变返回值。
Spring AOP 的工作流程可以分为三个核心阶段:
2.1.1. 代理对象的创建 (AnnotationAwareAspectJAutoProxyCreator)
Spring 在初始化 Bean 的生命周期中,有一个 postProcessAfterInitialization 步骤。
- 源码逻辑:容器扫描所有
@Aspect切面,匹配当前 Bean 是否符合切点表达式。 - 结果:如果匹配,不再返回原始对象,而是通过
ProxyFactory返回一个代理对象(JDK 或 CGLIB)。
2.1.2. 拦截器链的获取
当调用代理对象的方法时,会触发 AdvisedSupport.getInterceptorsAndDynamicInterceptionAdvice()。
- 源码逻辑:将所有的通知(Before, After 等)转换成 MethodInterceptor 列表,并进行排序。
2.1.3. 链式调用 (ReflectiveMethodInvocation)
这是 AOP 执行的核心递归算法。
- 源码关键方法:
proceed() - 逻辑流程:
- 维护一个索引
currentInterceptorIndex。 - 每次调用
proceed(),索引自增,获取下一个拦截器执行。 - 直到最后一个拦截器执行完,才真正调用目标对象(Target)的方法。
- 维护一个索引
实战代码演示
1. 引入依赖
在 pom.xml 中添加 spring-boot-starter-aop。
2. 编写切面类
Java
@Aspect
@Component
public class LogAspect {
// 定义切点:匹配 com.example.service 包下所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
// 环绕通知
@Around("serviceLayer()")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
// 执行目标方法
Object proceed = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature() + " 执行耗时: " + executionTime + "ms");
return proceed;
}
}
3、 代码实战:全注解 AOP 示例
3.1. 定义业务接口与实现
Java
public interface OrderService {
void createOrder(String productId);
}
@Service
public class OrderServiceImpl implements OrderService {
@Override
public void createOrder(String productId) {
System.out.println("正在处理业务逻辑:创建订单 " + productId);
}
}
3.2. 编写详细注释的切面类
Java
@Aspect // 标识这是一个切面
@Component // 交给 Spring 管理
public class GlobalLogAspect {
/**
* @Pointcut: 定义切入点
* execution: 匹配方法执行
* 第一个 *: 任意返回值类型
* com.example.service..: 该包及其子包下
* *.*(..): 任意类、任意方法、任意参数
*/
@Pointcut("execution(* com.example.service..*.*(..))")
public void servicePointcut() {}
/**
* @Around: 环绕通知
* ProceedingJoinPoint: 只有环绕通知才有,用于手动触发目标方法
*/
@Around("servicePointcut()")
public Object handleTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("[环绕通知] 方法开始前:模拟开启事务");
try {
// 执行目标方法
Object result = joinPoint.proceed();
System.out.println("[环绕通知] 方法正常结束后:模拟提交事务");
return result;
} catch (Exception e) {
System.out.println("[环绕通知] 方法异常后:模拟回滚事务");
throw e;
}
}
/**
* @Before: 前置通知
* JoinPoint: 获取方法名、参数等元数据
*/
@Before("servicePointcut()")
public void doBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("[前置通知] 准备执行方法: " + methodName + ",参数: " + Arrays.toString(args));
}
/**
* @AfterReturning: 正常返回通知
* returning: 指定接收返回值的变量名
*/
@AfterReturning(pointcut = "servicePointcut()", returning = "res")
public void doAfterReturning(Object res) {
System.out.println("[返回通知] 结果是: " + res);
}
}
4. IoC 与 AOP 的技术区别总结
| 特性 | IoC / DI (控制反转/依赖注入) | AOP (面向切面编程) |
|---|---|---|
| 核心目标 | 解耦对象之间的依赖关系。 | 解耦业务逻辑与系统级服务(如日志)。 |
| 关注点 | 关注的是类与类的纵向关系。 | 关注的是方法层面的横向切入。 |
| 实现手段 | 反射 (Reflection)、工厂模式。 | 动态代理 (JDK Proxy / CGLIB)。 |
| 关键词 | 容器、注入、Bean、单例。 | 切面、通知、切点、代理。 |
| 解决问题 | 谁来创建对象?对象怎么获取? | 如何在不侵入代码的前提下增加新功能? |
建议:
先掌握 IoC,理解 Bean 的生命周期,这是 Spring 的根基。
再进阶 AOP,学会使用 AspectJ 表达式,这能极大精简您的冗余代码。
更多推荐




所有评论(0)