本文纲要

一、Spring 整合 JUnit

  1. 整合步骤
  2. 项目结构
  3. 核心代码实现

二、AOP 简介

  1. 什么是 AOP
  2. AOP 核心概念
  3. 概念关系图

三、AOP 入门案例

  1. 案例分析
  2. 开发步骤
  3. 项目结构
  4. 核心代码实现

四、AOP 工作流程

  1. 工作流程分析
  2. 流程图示
  3. 目标对象与代理对象验证

五、总结

一、Spring 整合 JUnit

在 Spring 环境中进行单元测试时,如果每次都要手动创建 ApplicationContext 并获取 Bean,测试代码会变得非常冗长。Spring 提供了与 JUnit 的完美整合,让我们可以在测试类中直接注入 Spring 管理的 Bean,极大简化测试代码。

1 ) 整合步骤

  1. 导入依赖坐标
    需引入 junitspring-test 两个依赖。
  2. 设置类运行器
    在测试类上使用 @RunWith(SpringJUnit4ClassRunner.class)
  3. 指定 Spring 配置
    使用 @ContextConfiguration(classes = SpringConfig.class) 告诉 Spring 从哪里加载配置。
  4. 注入待测试组件
    通过 @Autowired 自动装配需要测试的 Bean。
  5. 编写测试方法
    使用 @Test 标记测试方法,直接调用业务方法进行验证。

2 ) 项目结构

spring_16_spring_junit 
├── pom.xml 
└── src 
    ├── main 
    │   ├── java 
    │   │   └── com/wb 
    │   │       ├── App2.java 
    │   │       ├── config 
    │   │       │   ├── JdbcConfig.java 
    │   │       │   ├── MybatisConfig.java 
    │   │       │   └── SpringConfig.java 
    │   │       ├── dao 
    │   │       │   └── AccountDao.java 
    │   │       ├── domain 
    │   │       │   └── Account.java 
    │   │       └── service 
    │   │           ├── AccountService.java 
    │   │           └── impl 
    │   │               └── AccountServiceImpl.java 
    │   └── resources 
    │       └── jdbc.properties 
    └── test 
        └── java 
            └── com/wb/service 
                └── AccountServiceTest.java 

3 ) 核心代码实现

pom.xml 关键依赖

<dependencies>
    <!-- Spring 上下文 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    <!-- JUnit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!-- Spring Test -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    <!-- 其他依赖(MyBatis、Druid等)省略 -->
</dependencies>

Spring 配置类 SpringConfig.java

package com.wb.config;
 
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
 
@Configuration 
@ComponentScan("com.wb")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MybatisConfig.class})
public class SpringConfig {
}

测试类 AccountServiceTest.java

package com.wb.service;
 
import com.wb.config.SpringConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
// 设置类运行器 
@RunWith(SpringJUnit4ClassRunner.class)
// 设置Spring环境对应的配置类 
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    
    // 支持自动装配注入bean 
    @Autowired 
    private AccountService accountService;
 
    @Test 
    public void testFindById() {
        System.out.println(accountService.findById(1));
    }
 
    @Test 
    public void testFindAll() {
        System.out.println(accountService.findAll());
    }
}

通过以上配置,测试类中的 accountService 直接由 Spring 注入,无需手动加载容器,代码简洁且易于维护。

二、AOP 简介

AOP(Aspect Oriented Programming,面向切面编程)是 Spring 框架的另一大核心特性(另一个是 IOC)。它是一种编程思想,用于在不修改原始代码的前提下,为程序动态添加额外功能。

1 ) 什么是 AOP

AOP 全称 Aspect Oriented Programming,即面向切面编程。它的核心理念是 无侵入式编程:在不惊动原始设计(不修改源码)的情况下,为方法增强功能。

例如,在不改动业务方法的前提下,为其添加日志记录、性能统计、事务管理等共性功能。

2 ) AOP 核心概念

术语 英文 说明
连接点 JoinPoint 程序执行过程中的某个位置,在 Spring AOP 中特指方法的执行。所有方法都是连接点。
切入点 Pointcut 匹配连接点的表达式,用来定义哪些方法需要增强。例如:匹配所有 update 开头的方法。
通知 Advice 在切入点处执行的共性功能,在 Spring AOP 中通常以一个方法的形式存在。
通知类 - 定义通知方法的类。
切面 Aspect 切入点与通知的绑定关系。描述在哪些连接点(切入点)执行什么通知。
目标对象 Target 被代理的原始对象。

3 ) 概念关系图

匹配

所有方法
(连接点)

切入点
(需增强的方法)

共性功能
(通知)

通知类

切面
(绑定关系)

AOP 代理对象
(执行增强后的方法)

三、AOP 入门案例

我们通过一个简单的需求来体验 AOP:在 BookDaoupdate() 方法执行前打印当前系统时间,且不修改 BookDaoImpl 的任何代码。

1 ) 案例分析

原始功能:save()update() 两个业务方法。
期望增强:在 update() 方法执行前自动输出系统时间。
实现方式:基于注解的 AOP 开发。

2 ) 开发步骤

  1. 导入依赖
    需要 spring-contextaspectjweaver
  2. 定义原始接口与实现类
    BookDao 接口及其实现 BookDaoImpl
  3. 编写通知类
    使用 @Component 将类注册为 Spring Bean。
    使用 @Aspect 标记该类为切面类。
    使用 @Pointcut 定义切入点,绑定到某个私有的空方法上。
    定义通知方法,并使用 @Before 等注解关联切入点。
  4. 开启 AOP 注解支持
    在 Spring 配置类上添加 @EnableAspectJAutoProxy

3 ) 项目结构

spring_18_aop_quickstart 
├── pom.xml 
└── src/main/java/com/wb 
    ├── App.java 
    ├── aop 
    │   └── MyAdvice.java 
    ├── config 
    │   └── SpringConfig.java 
    └── dao 
        ├── BookDao.java 
        └── impl 
            └── BookDaoImpl.java 

4 ) 核心代码实现

pom.xml 关键依赖

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.4</version>
    </dependency>
</dependencies>

BookDao.java 接口

package com.wb.dao;
 
public interface BookDao {
    public void save();
    public void update();
}

BookDaoImpl.java 实现类

package com.wb.dao.impl;
 
import com.wb.dao.BookDao;
import org.springframework.stereotype.Repository;
 
@Repository 
public class BookDaoImpl implements BookDao {
 
    public void save() {
        System.out.println(System.currentTimeMillis());
        System.out.println("book dao save ...");
    }
 
    public void update() {
        System.out.println("book dao update ...");
    }
}

MyAdvice.java 通知类(切面)

package com.wb.aop;
 
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
 
// 通知类必须配置成Spring管理的bean 
@Component 
// 设置当前类为切面类 
@Aspect 
public class MyAdvice {
    
    // 设置切入点,要求配置在方法上方 
    @Pointcut("execution(void com.wb.dao.BookDao.update())")
    private void pt() {}
 
    // 设置在切入点pt()的前面运行当前操作(前置通知)
    @Before("pt()")
    public void method() {
        System.out.println(System.currentTimeMillis());
    }
}

SpringConfig.java 配置类

package com.wb.config;
 
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
 
@Configuration 
@ComponentScan("com.wb")
// 开启注解开发AOP功能 
@EnableAspectJAutoProxy 
public class SpringConfig {
}

App.java 测试运行

package com.wb;
 
import com.wb.config.SpringConfig;
import com.wb.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        bookDao.update();
    }
}

执行 update() 将先输出系统时间,再执行原业务逻辑,而 BookDaoImpl 的代码没有发生任何改变,这正是 AOP 的“无侵入式”增强。

四、AOP 工作流程

理解了入门案例后,我们深入探究 Spring AOP 的底层运作机制。

1 ) 工作流程分析

1.1 Spring 容器启动

读取所有切面配置中使用到的切入点(而非全部定义的切入点)。
只有与通知绑定的切入点才会被记录。

1.2 初始化 Bean

在创建每个 Bean 时,Spring 会检查该 Bean 对应类中的方法是否匹配任意切入点:

  • 未匹配:直接创建原始对象(目标对象)。
  • 匹配成功:创建原始对象的代理对象(Proxy),并存入容器。

1.3 执行方法

当通过容器获取 Bean 并调用方法时:

  • 如果是代理对象,会触发 AOP 增强逻辑(先执行通知,再调用目标方法)。
  • 如果是原始对象,则直接执行原方法。

2 ) 流程图示

匹配失败

匹配成功

Spring 容器启动

读取切面中使用的切入点

初始化 Bean

该 Bean 的方法
匹配任意切入点?

创建原始对象
(目标对象)

创建代理对象

存入容器

从容器获取 Bean

是否为代理?

直接执行原方法

执行通知增强
再执行目标方法

3 ) 目标对象与代理对象验证

修改 App.java,打印获取到的 Bean 及其类型:

package com.wb;
 
import com.wb.config.SpringConfig;
import com.wb.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        System.out.println(bookDao);
        System.out.println(bookDao.getClass());
    }
}
  • 切入点匹配 update() 时(@Pointcut("execution(void com.wb.dao.BookDao.update())")):
    • 打印结果类似 com.wb.dao.impl.BookDaoImpl$$EnhancerBySpringCGLIB...,说明返回的是 CGLIB 代理对象
  • 切入点未匹配任何方法时:
    • 打印 com.wb.dao.impl.BookDaoImpl@...,即原始对象。

这就验证了 AOP 的核心原理:借助代理模式,Spring 会在发现方法需要增强时,自动用代理对象替换原始对象,从而在不改变源码的前提下织入增强逻辑。

五、总结

  • Spring 整合 JUnit 让测试代码与 Spring 容器无缝对接,通过 @RunWith@ContextConfiguration@Autowired 即可直接注入 Bean 进行测试。

  • AOP(面向切面编程) 是一种无侵入式的功能增强思想,核心概念包括连接点、切入点、通知、切面。

  • AOP 的开发步骤:导入 aspectjweaver 依赖 → 定义切面类(@Aspect + @Component)→ 设置切入点(@Pointcut)→ 编写通知方法(@Before 等)→ 开启 @EnableAspectJAutoProxy

  • Spring AOP 底层基于动态代理实现:当 Bean 的方法匹配切入点时,容器会创建代理对象,调用时先执行通知再执行目标方法;否则返回原始对象。

  • 目标对象就是被代理的原始对象,整个 AOP 工作本质是代理模式的运用。

Logo

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

更多推荐