Java框架快速入门:Spring 整合 JUnit 与 AOP
本文纲要
一、Spring 整合 JUnit
- 整合步骤
- 项目结构
- 核心代码实现
二、AOP 简介
- 什么是
AOP AOP核心概念- 概念关系图
三、AOP 入门案例
- 案例分析
- 开发步骤
- 项目结构
- 核心代码实现
四、AOP 工作流程
- 工作流程分析
- 流程图示
- 目标对象与代理对象验证
五、总结
一、Spring 整合 JUnit
在 Spring 环境中进行单元测试时,如果每次都要手动创建 ApplicationContext 并获取 Bean,测试代码会变得非常冗长。Spring 提供了与 JUnit 的完美整合,让我们可以在测试类中直接注入 Spring 管理的 Bean,极大简化测试代码。
1 ) 整合步骤
- 导入依赖坐标
需引入junit和spring-test两个依赖。 - 设置类运行器
在测试类上使用@RunWith(SpringJUnit4ClassRunner.class)。 - 指定 Spring 配置
使用@ContextConfiguration(classes = SpringConfig.class)告诉 Spring 从哪里加载配置。 - 注入待测试组件
通过@Autowired自动装配需要测试的 Bean。 - 编写测试方法
使用@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:在 BookDao 的 update() 方法执行前打印当前系统时间,且不修改 BookDaoImpl 的任何代码。
1 ) 案例分析
原始功能:save() 与 update() 两个业务方法。
期望增强:在 update() 方法执行前自动输出系统时间。
实现方式:基于注解的 AOP 开发。
2 ) 开发步骤
- 导入依赖
需要spring-context和aspectjweaver。 - 定义原始接口与实现类
如BookDao接口及其实现BookDaoImpl。 - 编写通知类
使用@Component将类注册为 Spring Bean。
使用@Aspect标记该类为切面类。
使用@Pointcut定义切入点,绑定到某个私有的空方法上。
定义通知方法,并使用 @Before 等注解关联切入点。 - 开启 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 ) 流程图示
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 工作本质是代理模式的运用。
更多推荐




所有评论(0)