【Spring】两大核心基石 IoC和 AOP

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 能够构建出高度解耦、可维护、可扩展的企业级应用程序。

相关推荐
明有所思1 小时前
springsecurity更换加密方式
java·spring
却话巴山夜雨时i1 小时前
295. 数据流的中位数【困难】
java·服务器·前端
java干货1 小时前
优雅停机!Spring Boot 应用如何使用 Hook 线程完成“身后事”?
java·spring boot·后端
tealcwu1 小时前
【Unity技巧】实现在Play时自动保存当前场景
java·unity·游戏引擎
uup1 小时前
Java 多线程下的可见性问题
java
用户8307196840821 小时前
通过泛型限制集合只读或只写
java
Pluchon1 小时前
硅基计划4.0 算法 记忆化搜索
java·数据结构·算法·leetcode·决策树·深度优先
大飞哥~BigFei1 小时前
deploy发布项目到国外中央仓库报如下错误Project name is missing
java