1. 控制反转 (IoC) 与 依赖注入 (DI)

概念区别

  • IoC (Inversion of Control - 控制反转):这是一种设计思想。传统开发中,我们通过 new 手动创建对象;在 Spring 中,对象的创建、销毁、生命周期控制全部交给 Spring 容器。控制权从程序员手里反转到了框架手里。
  • DI (Dependency Injection - 依赖注入):这是 IoC 的具体实现方式。容器在运行期间,动态地将某种依赖关系注入到组件之中。

使用场景

  • 解耦:当 Service 层需要调用 Repository 层时,通过 DI 注入接口,无需关心具体实现。
  • 资源管理:单例对象的全局管理(如数据库连接池、配置类)。

源码级原理解析

Spring IoC 的核心是 BeanFactoryApplicationContext。其初始化流程简化如下:

  1. Resource 定位:找到 XML 或注解定义的 Bean。
  2. BeanDefinition 载入:将 Bean 的元数据(类名、属性、作用域)封装成 BeanDefinition 对象。
  3. 注册 Bean:存入一个 ConcurrentHashMap 中。
  4. 实例化与 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 的底层实现是 动态代理

  1. JDK 动态代理:目标类实现了接口,Spring 使用 java.lang.reflect.Proxy
  2. 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、单例。 切面、通知、切点、代理。
解决问题 谁来创建对象?对象怎么获取? 如何在不侵入代码的前提下增加新功能?

建议:

  1. 先掌握 IoC,理解 Bean 的生命周期,这是 Spring 的根基。

  2. 再进阶 AOP,学会使用 AspectJ 表达式,这能极大精简您的冗余代码。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐