Java 高级技术详解:单元测试、反射、注解与动态代理(从零基础到精通)
Java 高级技术是构建灵活、可扩展系统的核心,单元测试保障代码质量,反射与注解支撑框架底层,动态代理实现面向切面编程(AOP)。本文从零基础角度出发,通过实例代码+详细注释,带你逐步掌握这些技术的核心原理与实战用法。
一、单元测试:保障代码质量的第一道防线
单元测试(Unit Testing)是对软件中最小可测试单元(如方法、类)的验证,目的是尽早发现代码缺陷,提高重构安全性。Java 中最常用的单元测试框架是 JUnit 5。
1. JUnit 5 基础入门
(1)环境准备
在 Maven 项目的 pom.xml 中添加依赖:
<dependencies>
<!-- JUnit 5 核心依赖 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
</dependencies>(2)核心注解
JUnit 5 通过注解标识测试方法和生命周期,常用注解:
| 注解 | 作用 |
|---|---|
@Test | 标识测试方法(无需 public void) |
@BeforeEach | 每个测试方法执行前运行(初始化资源) |
@AfterEach | 每个测试方法执行后运行(释放资源) |
@BeforeAll | 所有测试方法执行前运行(静态方法) |
@AfterAll | 所有测试方法执行后运行(静态方法) |
@Disabled | 禁用测试方法/类 |
(3)第一个单元测试
假设我们有一个简单的计算器类,需要测试其加法和减法功能:
// 业务类:src/main/java/com/example/Calculator.java
package com.example;
public class Calculator {
// 加法
public int add(int a, int b) {
return a + b;
}
// 减法
public int subtract(int a, int b) {
return a - b;
}
}测试类需放在 src/test/java 目录,包结构与业务类一致:
// 测试类:src/test/java/com/example/CalculatorTest.java
package com.example;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*; // 断言工具类
class CalculatorTest {
private Calculator calculator;
// 所有测试前初始化(静态方法)
@BeforeAll
static void beforeAll() {
System.out.println("=== 所有测试开始 ===");
}
// 每个测试前初始化
@BeforeEach
void setUp() {
calculator = new Calculator(); // 初始化计算器实例
System.out.println("--- 开始单个测试 ---");
}
// 测试加法
@Test
void testAdd() {
// 断言:预期结果与实际结果是否一致
assertEquals(5, calculator.add(2, 3)); // 2+3=5
assertEquals(-1, calculator.add(-2, 1)); // 边界值测试
}
// 测试减法
@Test
void testSubtract() {
assertEquals(1, calculator.subtract(3, 2));
assertNotEquals(0, calculator.subtract(5, 5)); // 断言不等
}
// 禁用该测试
@Test
@Disabled("暂未实现乘法功能")
void testMultiply() {
fail("未实现的测试"); // 强制失败
}
// 每个测试后清理
@AfterEach
void tearDown() {
System.out.println("--- 单个测试结束 ---");
}
// 所有测试后清理
@AfterAll
static void afterAll() {
System.out.println("=== 所有测试结束 ===");
}
}运行测试:在 IDE 中右键测试类选择「Run Tests」,或通过 Maven 命令 mvn test 执行。
2. 高级特性:参数化测试
参数化测试允许用多组数据执行同一个测试方法,避免重复代码。需添加 @ParameterizedTest 注解并指定数据源。
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.jupiter.params.provider.CsvSource;
class ParameterizedTestDemo {
// 单参数测试(字符串数组)
@ParameterizedTest
@ValueSource(strings = {"apple", "banana", "cherry"})
void testStringLength(String fruit) {
assertTrue(fruit.length() > 2); // 断言字符串长度>2
}
// 多参数测试(CSV格式:a, b, 预期结果)
@ParameterizedTest
@CsvSource({
"2, 3, 5", // 2+3=5
"-1, 1, 0", // -1+1=0
"0, 0, 0" // 0+0=0
})
void testAddWithParams(int a, int b, int expected) {
Calculator calculator = new Calculator();
assertEquals(expected, calculator.add(a, b));
}
}3. Mock 测试:隔离外部依赖
当代码依赖数据库、网络等外部资源时,可通过 Mockito 模拟这些依赖,专注测试目标逻辑。
(1)添加 Mockito 依赖
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>(2)Mock 测试示例
假设我们有一个依赖用户服务的订单服务,需要测试订单创建逻辑,而不实际调用用户服务:
// 依赖接口
interface UserService {
boolean exists(Long userId); // 检查用户是否存在
}
// 待测试类
class OrderService {
private final UserService userService;
OrderService(UserService userService) {
this.userService = userService; // 构造器注入依赖
}
// 创建订单(需先检查用户是否存在)
public String createOrder(Long userId) {
if (userService.exists(userId)) {
return "ORDER_" + System.currentTimeMillis();
}
return "ERROR: 用户不存在";
}
}
// Mock 测试
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class) // 启用Mockito
class OrderServiceTest {
@Mock // 模拟依赖对象
private UserService userService;
@InjectMocks // 将mock对象注入到待测试类
private OrderService orderService;
@Test
void testCreateOrder_UserExists() {
// 1. 定义mock行为:当调用exists(1L)时返回true
when(userService.exists(1L)).thenReturn(true);
// 2. 执行测试
String result = orderService.createOrder(1L);
// 3. 验证结果
assertTrue(result.startsWith("ORDER_"));
// 验证userService的exists方法是否被调用过一次
verify(userService, times(1)).exists(1L);
}
@Test
void testCreateOrder_UserNotExists() {
when(userService.exists(999L)).thenReturn(false);
String result = orderService.createOrder(999L);
assertEquals("ERROR: 用户不存在", result);
}
}二、反射:突破编译期限制的"黑科技"
反射(Reflection)允许程序在运行时获取类的信息(属性、方法、构造器等)并操作其私有成员,是框架(如 Spring、MyBatis)的核心底层技术。
1. 反射的核心:Class 类
Java 中每个类被加载后,JVM 会为其创建一个 java.lang.Class 对象,包含该类的所有信息。获取 Class 对象的 3 种方式:
package com.example.reflection;
public class ReflectionDemo {
public static void main(String[] args) throws ClassNotFoundException {
// 方式1:通过对象.getClass()
User user = new User();
Class<? extends User> clazz1 = user.getClass();
// 方式2:通过类名.class(编译期已知类型)
Class<User> clazz2 = User.class;
// 方式3:通过Class.forName("全类名")(动态加载,最常用)
Class<?> clazz3 = Class.forName("com.example.reflection.User");
// 验证:同一个类的Class对象唯一
System.out.println(clazz1 == clazz2); // true
System.out.println(clazz1 == clazz3); // true
}
}
// 测试类
class User {
private Long id;
private String name;
public User() {}
public User(Long id, String name) {
this.id = id;
this.name = name;
}
private String getInfo() {
return id + ":" + name;
}
public void setName(String name) {
this.name = name;
}
}2. 反射操作类成员
(1)获取并调用构造器
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ReflectionConstructor {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Class<?> clazz = Class.forName("com.example.reflection.User");
// 1. 获取无参构造器并创建对象(等价于 new User())
Constructor<?> constructor1 = clazz.getConstructor();
User user1 = (User) constructor1.newInstance(); // 无参构造
System.out.println(user1); // User@xxx
// 2. 获取有参构造器(Long.class, String.class 是参数类型)
Constructor<?> constructor2 = clazz.getConstructor(Long.class, String.class);
User user2 = (User) constructor2.newInstance(1L, "张三"); // 有参构造
System.out.println(user2);
}
}(2)获取并操作属性(包括私有属性)
import java.lang.reflect.Field;
public class ReflectionField {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.example.reflection.User");
User user = (User) clazz.getConstructor().newInstance();
// 1. 获取公有属性(实际User中没有公有属性,此处仅为示例)
// Field publicField = clazz.getField("publicField");
// 2. 获取私有属性(必须用 getDeclaredField)
Field nameField = clazz.getDeclaredField("name");
// 暴力破解私有属性访问权限(关键)
nameField.setAccessible(true);
// 设置属性值
nameField.set(user, "李四"); // 等价于 user.name = "李四"
// 获取属性值
String name = (String) nameField.get(user); // 等价于 user.name
System.out.println("修改后name:" + name); // 李四
}
}(3)获取并调用方法(包括私有方法)
import java.lang.reflect.Method;
public class ReflectionMethod {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.example.reflection.User");
User user = (User) clazz.getConstructor(Long.class, String.class).newInstance(2L, "王五");
// 1. 调用公有方法 setName
Method setNameMethod = clazz.getMethod("setName", String.class);
setNameMethod.invoke(user, "赵六"); // 等价于 user.setName("赵六")
// 2. 调用私有方法 getInfo
Method getInfoMethod = clazz.getDeclaredMethod("getInfo");
getInfoMethod.setAccessible(true); // 允许访问私有方法
String info = (String) getInfoMethod.invoke(user); // 等价于 user.getInfo()
System.out.println("私有方法返回:" + info); // 2:赵六
}
}3. 反射的应用场景与局限
(1)典型应用
- 框架底层:Spring 的依赖注入(DI)通过反射创建对象并注入属性;
- 序列化/反序列化:JSON 工具(如 Jackson)通过反射将 JSON 转换为对象;
- 单元测试:Mockito 模拟对象时使用反射修改私有成员。
(2)局限
- 性能损耗:反射操作绕过编译期检查,性能比直接调用低约 10-100 倍;
- 破坏封装:直接访问私有成员可能导致代码逻辑混乱;
- 可读性差:反射代码较繁琐,不如直接调用直观。
三、注解:给代码添加"元数据"
注解(Annotation)是 Java 5 引入的特殊标记,用于为代码添加元数据(描述数据的数据),可被编译器或运行时解析。
1. 注解的分类
| 类型 | 说明 | 示例 |
|---|---|---|
| 内置注解 | JDK 自带的注解 | @Override、@Deprecated |
| 元注解 | 修饰其他注解的注解 | @Target、@Retention |
| 自定义注解 | 开发者根据需求定义的注解 | 如 @Log、@Validate |
2. 元注解:定义注解的"注解"
元注解用于约束自定义注解的行为,常用元注解:
| 元注解 | 作用 | 可选值 |
|---|---|---|
@Target | 限制注解可修饰的元素 | TYPE(类)、METHOD(方法)等 |
@Retention | 定义注解的保留策略 | SOURCE(源码)、CLASS(字节码)、RUNTIME(运行时) |
@Documented | 注解是否包含在 Javadoc 中 | - |
@Inherited | 注解是否可被继承 | - |
3. 自定义注解与解析
(1)定义自定义注解
import java.lang.annotation.*;
// 元注解:该注解可修饰方法和类
@Target({ElementType.METHOD, ElementType.TYPE})
// 元注解:保留到运行时(可通过反射解析)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
// 注解属性(默认值为"操作日志")
String value() default "操作日志";
// 可选属性:是否记录参数
boolean recordParams() default true;
}(2)使用自定义注解
// 注解修饰类
@Log("用户服务")
public class UserService {
// 注解修饰方法(覆盖类上的注解值)
@Log(value = "查询用户", recordParams = true)
public String getUser(Long id) {
return "用户" + id;
}
@Log(value = "删除用户", recordParams = false)
public void deleteUser(Long id) {
System.out.println("删除用户" + id);
}
}(3)通过反射解析注解
import java.lang.reflect.Method;
public class AnnotationParser {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = Class.forName("com.example.annotation.UserService");
// 1. 解析类上的注解
if (clazz.isAnnotationPresent(Log.class)) {
Log classLog = clazz.getAnnotation(Log.class);
System.out.println("类注解值:" + classLog.value()); // 用户服务
}
// 2. 解析方法上的注解
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(Log.class)) {
Log methodLog = method.getAnnotation(Log.class);
System.out.println(
"方法名:" + method.getName() +
",注解值:" + methodLog.value() +
",是否记录参数:" + methodLog.recordParams()
);
}
}
}
}
// 输出:
// 类注解值:用户服务
// 方法名:getUser,注解值:查询用户,是否记录参数:true
// 方法名:deleteUser,注解值:删除用户,是否记录参数:false4. 注解的典型应用
- 框架配置:Spring 的
@Controller、@Service标识组件,通过注解扫描实现自动装配; - 参数验证:JSR-303 规范的
@NotNull、@Size用于验证请求参数; - AOP 切点:Spring 的
@Transactional标识事务方法,通过 AOP 自动开启/提交事务。
四、动态代理:无侵入式增强方法功能
动态代理(Dynamic Proxy)允许在不修改原始类代码的情况下,为方法添加额外功能(如日志、事务、权限校验),是 AOP(面向切面编程)的核心实现。
1. 代理模式基础
代理模式包含三个角色:
- 目标对象(Target):原始业务对象(如
UserService); - 代理对象(Proxy):增强目标对象功能的对象;
- 抽象接口(Interface):目标对象和代理对象共同实现的接口。

2. JDK 动态代理(基于接口)
JDK 动态代理是 JDK 自带的代理实现,要求目标对象必须实现接口。核心是 InvocationHandler 接口和 Proxy 类。
(1)定义业务接口和实现类
// 业务接口
public interface OrderService {
void createOrder(Long userId);
void cancelOrder(Long orderId);
}
// 目标对象(实现接口)
public class OrderServiceImpl implements OrderService {
@Override
public void createOrder(Long userId) {
System.out.println("创建用户" + userId + "的订单");
}
@Override
public void cancelOrder(Long orderId) {
System.out.println("取消订单" + orderId);
}
}(2)实现 InvocationHandler 接口(增强逻辑)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
// 代理处理器:实现增强逻辑
public class LogInvocationHandler implements InvocationHandler {
private final Object target; // 目标对象(被代理的对象)
public LogInvocationHandler(Object target) {
this.target = target;
}
/**
* 代理方法:每次调用目标方法时都会触发
* @param proxy 代理对象
* @param method 目标方法
* @param args 方法参数
* @return 方法返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强:方法执行前添加日志
System.out.println("===== 日志开始 =====");
System.out.println("调用方法:" + method.getName());
System.out.println("参数:" + Arrays.toString(args));
// 调用目标方法(核心:执行原始业务逻辑)
Object result = method.invoke(target, args);
// 后置增强:方法执行后添加日志
System.out.println("方法执行完成");
System.out.println("===== 日志结束 =====");
return result;
}
}(3)创建代理对象并使用
import java.lang.reflect.Proxy;
public class JdkProxyDemo {
public static void main(String[] args) {
// 1. 创建目标对象
OrderService target = new OrderServiceImpl();
// 2. 创建代理处理器
LogInvocationHandler handler = new LogInvocationHandler(target);
// 3. 生成代理对象(核心方法:Proxy.newProxyInstance)
OrderService proxy = (OrderService) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 目标对象实现的接口
handler // 代理处理器
);
// 4. 调用代理对象的方法(实际会触发handler的invoke方法)
proxy.createOrder(100L);
System.out.println("---");
proxy.cancelOrder(999L);
}
}
// 输出:
// ===== 日志开始 =====
// 调用方法:createOrder
// 参数:[100]
// 创建用户100的订单
// 方法执行完成
// ===== 日志结束 =====
// ---
// ===== 日志开始 =====
// 调用方法:cancelOrder
// 参数:[999]
// 取消订单999
// 方法执行完成
// ===== 日志结束 =====3. CGLIB 动态代理(基于继承)
JDK 动态代理要求目标对象实现接口,而 CGLIB(Code Generation Library) 通过继承目标类生成代理对象,无需接口。
(1)添加 CGLIB 依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>(2)实现 MethodInterceptor 接口(CGLIB 增强逻辑)
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.Arrays;
// CGLIB 代理拦截器
public class LogMethodInterceptor implements MethodInterceptor {
private final Object target; // 目标对象
public LogMethodInterceptor(Object target) {
this.target = target;
}
// 创建代理对象
public Object createProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 设置父类(目标类)
enhancer.setCallback(this); // 设置拦截器
return enhancer.create(); // 生成代理对象
}
// 拦截方法调用(类似JDK的invoke)
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 前置增强
System.out.println("===== CGLIB 日志开始 =====");
System.out.println("调用方法:" + method.getName());
// 调用目标方法
Object result = method.invoke(target, args);
// 后置增强
System.out.println("===== CGLIB 日志结束 =====");
return result;
}
}(3)使用 CGLIB 代理(目标类无需实现接口)
// 目标类(无需实现接口)
public class PayService {
public void pay(Long orderId) {
System.out.println("支付订单" + orderId);
}
}
// 测试 CGLIB 代理
public class CglibProxyDemo {
public static void main(String[] args) {
// 1. 目标对象(无需接口)
PayService target = new PayService();
// 2. 创建 CGLIB 代理
LogMethodInterceptor interceptor = new LogMethodInterceptor(target);
PayService proxy = (PayService) interceptor.createProxy();
// 3. 调用代理方法
proxy.pay(12345L);
}
}
// 输出:
// ===== CGLIB 日志开始 =====
// 调用方法:pay
// 支付订单12345
// ===== CGLIB 日志结束 =====4. 两种动态代理的对比
| 特性 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 依赖 | JDK 自带(无需额外依赖) | 需导入 CGLIB 库 |
| 原理 | 实现目标接口生成代理类 | 继承目标类生成代理类 |
| 目标类要求 | 必须实现接口 | 无要求(不能是 final 类) |
| 性能 | 调用效率高,创建代理慢 | 创建代理快,调用效率略低 |
| 典型应用 | Spring AOP(目标有接口时) | Spring AOP(目标无接口时) |
总结
本文系统讲解了 Java 高级技术的核心知识点,从单元测试到动态代理,覆盖了框架开发的底层基石:
- 单元测试:通过 JUnit 5 实现代码自动化验证,Mockito 隔离外部依赖,是持续集成的基础;
- 反射:突破编译期限制,运行时操作类成员,支撑 Spring 等框架的依赖注入和配置解析;
- 注解:作为元数据描述代码,结合反射实现灵活的配置与增强(如日志、事务);
- 动态代理:JDK 基于接口,CGLIB 基于继承,是 AOP 的核心,实现无侵入式功能增强。
这些技术的核心价值在于解耦:单元测试解耦测试与业务,反射与注解解耦框架与业务,动态代理解耦增强逻辑与原始逻辑。掌握它们,不仅能读懂框架源码,更能设计出灵活、可扩展的系统。
建议结合实际场景练习(如实现一个简单的注解式日志框架),逐步加深理解,从"会用"到"精通"。