Spring 框架的两大核心基石:IoC(控制反转) 和 AOP(面向切面编程) 理解这两者是如何协同工作的,是掌握 Spring 精髓的关键
一、Spring IoC(控制反转)
1. 核心思想:观念的转变
传统方式(正转) :在普通程序中,对象 A 如果需要使用对象 B,会由对象 A 主动 通过 new关键字来创建对象 B。即,程序的控制权在对象 A
手中。
java
public class OrderService {
// 主动创建依赖
private UserDao userDao = new UserDaoImpl();
}
IoC 方式(反转):控制权被"反转"了。对象 A 不再主动创建对象 B,而是被动等待。由一个外部的容器(Spring IoC 容器)来负责对象的创建、组装和管理。当需要时,容器会将对象 B"注入"到对象 A 中。
java
public class OrderService {
// 被动接收依赖
@Autowired
private UserDao userDao; // 容器会负责把 UserDaoImpl 的实例注入到这里
}
2. 实现方式:DI(依赖注入)
DI 是 IoC 思想的一种具体实现方式。所谓"依赖注入",就是由 IoC 容器在运行期,动态地将某个对象所依赖的其他对象(即它的属性或参数)注入给它
主要的注入方式:
构造器注入(推荐):通过构造函数传递依赖。
java
@Component
public class OrderService {
private final UserDao userDao;
// 构造器注入
@Autowired // Spring 4.3 后,如果只有一个构造器,可省略 @Autowired
public OrderService(UserDao userDao) {
this.userDao = userDao;
}
}
Setter 方法注入:通过 Setter 方法设置依赖
java
@Component
public class OrderService {
private UserDao userDao;
// Setter 注入
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
字段注入(不推荐):通过 @Autowired直接标注在字段上。这种方式不利于测试和不变性
java
@Component
public class OrderService {
@Autowired // 字段注入(不推荐)
private UserDao userDao;
}
3. IoC 容器
Spring IoC 容器的核心是 BeanFactory接口,而 ApplicationContext是其更强大的子接口,提供了更多企业级功能(如国际化、事件发布等)。容器负责:
实例化 Bean
配置 Bean(如注入依赖)
组装 Bean 之间的关系
管理 Bean 的整个生命周期
二、Spring AOP(面向切面编程)
1. 核心思想:分离关注点
在业务系统中,除了核心业务逻辑(如创建订单、查询用户),还存在大量横切关注点,例如:日志记录、事务管理、安全校验、性能监控
这些代码如果直接写在业务方法中,会导致:
代码冗余 :每个业务方法都要写一遍。
代码混乱 :核心业务逻辑被非核心代码淹没。
难以维护 :修改日志或事务策略时,需要改动所有相关方法
AOP 的解决方案是:将这些横切关注点模块化为"切面",然后通过声明的方式,定义这些切面应该在何处、何时被应用。 从而使得业务类只关注核心逻辑。
2. AOP 关键术语
Aspect(切面) :一个横切关注点的模块化。就是一个用 @Aspect注解的类,里面包含了通知和切点。
Advice(通知) :切面在特定连接点上执行的动作。例如"日志记录"这个动作。类型有:
@Before:在方法执行前通知。
@AfterReturning:在方法成功执行后通知。
@AfterThrowing:在方法抛出异常后通知。
@After(finally):在方法执行后(无论成功与否)通知。
@Around:最强大的通知,环绕方法执行,可以控制是否执行目标方法。
Pointcut(切点) :一个表达式,用于匹配哪些类的哪些方法需要被增强。它定义了通知(Advice)被应用的"位置"。
Join Point(连接点) :程序执行过程中可以插入切面的点。在 Spring AOP 中,这总是代表一个方法的执行。
Weaving(织入):将切面应用到目标对象,从而创建代理对象的过程。Spring AOP 在运行时通过动态代理完成织入
3. 代码示例
假设我们想在所有 Service 层方法执行前后记录日志。
①定义切面(Aspect):
java
@Aspect
@Component
public class LoggingAspect {
// 定义切点:匹配 com.example.service 包下所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
// 定义前置通知
@Before("serviceLayer()")
public void logBeforeMethod(JoinPoint joinPoint) {
System.out.println("准备执行方法: " + joinPoint.getSignature().getName());
}
// 定义环绕通知(功能最全)
@Around("serviceLayer()")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
// 执行目标方法
Object result = joinPoint.proceed();
long elapsedTime = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature() + " 执行耗时: " + elapsedTime + "ms");
return result;
}
}
② 业务类(完全不知道日志的存在):
java
@Service
public class UserService {
public User findUserById(Long id) {
// ... 纯业务逻辑,没有日志代码
return user;
}
}
当调用 userService.findUserById(1L)时,控制台会输出:
java
准备执行方法: findUserById
User findUserById 执行耗时: 15ms
三、IoC 与 AOP 的协同工作:珠联璧合
下图清晰地展示了 Spring 如何将 IoC 和 AOP 完美结合,共同构建应用程序:

1.IoC 是基础 :IoC 容器负责创建和管理所有对象(Bean),包括你的业务核心类(如 UserService)和切面类(如 LoggingAspect)。
2.AOP 是增强 :在 IoC 容器完成 Bean 的依赖注入后、初始化之前(具体是在 BeanPostProcessor后置处理阶段),容器会检查是否有切面与当前 Bean 匹配。
3.动态代理 :如果匹配,Spring 不会将原始的 UserService实例直接交给其他组件。相反,它会创建一个代理对象。这个代理对象包装了原始对象。
4.协同工作 :当你的 OrderController(由 IoC 容器注入 UserService时,它实际得到的是这个代理对象,而非原始对象。
5.最终效果 :当调用 userService.findUserById()时,调用首先会进入代理对象。代理对象先执行切面逻辑(如日志记录),然后再将调用委托给原始的 UserService对象执行核心业务逻辑。这样,IoC 保证了对象的组装,而 AOP 实现了无侵入式的功能增强。
总结

简单来说:IoC 让对象之间的依赖关系变得清晰、可管理;而 AOP 则让这些对象能够"免费"获得像日志、事务这样的通用能力,而无需污染自己的代码。 二者结合,使得 Spring 能够构建出高度解耦、可维护、可扩展的企业级应用程序。