第10章 前端编译与优化 - 代码指南
一、代码结构
1.1 目录结构
src/main/java/com/linsir/abc/core/jvm/compile/
├── annotation/
│ └── AutoToString.java # 自动生成toString的注解定义
├── processor/
│ └── AutoToStringProcessor.java # 注解处理器实现
├── test/
│ ├── User.java # 测试类(排除敏感字段)
│ ├── Product.java # 测试类(包含父类字段)
│ └── BaseEntity.java # 父类测试类
├── UserToStringImpl.java # 生成的toString实现类
└── ProductToStringImpl.java # 生成的toString实现类
src/test/java/com/linsir/abc/core/jvm/compile/
└── AnnotationProcessorTest.java # 测试类1.2 类结构说明
1.2.1 AutoToString.java
- 位置:
annotation/AutoToString.java - 类型: 注解(Annotation)
- 作用: 标记需要自动生成toString方法的类
- 属性:
includeSuper(): 是否包含父类字段,默认falseexclude(): 需要排除的字段名数组,默认空数组
1.2.2 AutoToStringProcessor.java
- 位置:
processor/AutoToStringProcessor.java - 类型: 注解处理器(AbstractProcessor)
- 作用: 编译期间扫描@AutoToString注解,生成toString实现类
- 核心方法:
init(): 初始化处理器,获取Messager和Filerprocess(): 处理注解,遍历被注解的元素generateToString(): 生成toString实现类collectFields(): 收集类字段信息writeGeneratedClass(): 写入生成的类文件
1.2.3 测试实体类
- User.java: 基础测试类,演示排除敏感字段(password)
- Product.java: 继承测试类,演示包含父类字段
- BaseEntity.java: 父类,包含通用字段(id, createTime, updateTime)
1.2.4 生成的实现类
- UserToStringImpl.java: User类的toString实现
- ProductToStringImpl.java: Product类的toString实现
二、代码作用
2.1 整体作用
本代码实现了一个插入式注解处理器(Annotation Processor),演示了Java编译期代码生成技术。通过在类上添加@AutoToString注解,编译器会自动生成对应的toString()方法实现类。
2.2 各组件作用
2.2.1 注解定义(AutoToString)
@Target(ElementType.TYPE) // 只能用于类
@Retention(RetentionPolicy.SOURCE) // 只在源码阶段保留
public @interface AutoToString {
boolean includeSuper() default false; // 是否包含父类字段
String[] exclude() default {}; // 排除的字段名
}作用说明:
@Target(ElementType.TYPE): 限制注解只能用于类、接口、枚举@Retention(RetentionPolicy.SOURCE): 注解只在源码阶段存在,编译后丢弃includeSuper: 控制是否递归收集父类的字段exclude: 允许排除敏感字段(如密码、密钥等)
2.2.2 注解处理器(AutoToStringProcessor)
处理流程:
编译开始
↓
扫描@AutoToString注解
↓
收集类字段信息(排除static和指定字段)
↓
生成对应的ToStringImpl类
↓
编译结束核心功能:
- 字段收集: 遍历类的所有字段,过滤static字段和排除字段
- 父类支持: 递归收集父类字段(当includeSuper=true时)
- 代码生成: 使用JavaFileObject创建新的源文件
- 错误处理: 使用Messager输出编译期错误和警告
2.2.3 生成的toString实现
以User类为例,生成的UserToStringImpl提供静态方法:
public static String toString(User obj) {
if (obj == null) {
return "null";
}
StringBuilder sb = new StringBuilder();
sb.append("User{");
sb.append("id=").append(obj.getId());
sb.append(", ").append("username=").append(obj.getUsername());
sb.append(", ").append("age=").append(obj.getAge());
sb.append('}');
return sb.toString();
}输出示例:
User{id=1, username=zhangsan, age=25}2.3 技术要点
2.3.1 JSR 269标准
注解处理器基于**JSR 269(Pluggable Annotation Processing API)**标准:
- 在编译期间执行,不修改原始源码
- 可以读取、修改、添加抽象语法树(AST)元素
- 支持多轮次处理(Round Processing)
2.3.2 处理器生命周期
初始化阶段:
init(ProcessingEnvironment)- 获取Messager(消息报告器)
- 获取Filer(文件创建器)
- 获取ElementUtils(元素工具)
- 获取TypeUtils(类型工具)
处理阶段:
process(Set<? extends TypeElement>, RoundEnvironment)- 遍历被注解的元素
- 生成辅助代码
- 返回true表示已处理
结束阶段: 编译器继续后续编译流程
2.3.3 与Lombok的区别
| 特性 | 本实现 | Lombok |
|---|---|---|
| 实现方式 | 生成辅助类 | 直接修改AST |
| API使用 | 标准JSR 269 | 编译器内部API |
| 侵入性 | 低(不修改原类) | 高(修改原类) |
| 兼容性 | 好 | 依赖编译器版本 |
三、测试方法
3.1 测试类位置
src/test/java/com/linsir/abc/core/jvm/compile/AnnotationProcessorTest.java3.2 测试用例说明
3.2.1 测试User类的toString生成
测试方法: testUserToStringGeneration()
测试内容:
- 验证生成的toString方法存在且可调用
- 验证非排除字段包含在输出中
- 验证被排除的password字段不包含
- 验证静态字段不包含
执行命令:
cd linsir-abc-core
mvn test -Dtest=AnnotationProcessorTest#testUserToStringGeneration3.2.2 测试Product类的toString生成(包含父类字段)
测试方法: testProductToStringWithSuperFields()
测试内容:
- 验证当前类字段包含在输出中
- 验证父类字段包含在输出中(includeSuper=true)
- 验证被排除的costPrice字段不包含
执行命令:
mvn test -Dtest=AnnotationProcessorTest#testProductToStringWithSuperFields3.2.3 测试toString处理null对象
测试方法: testToStringWithNullObject()
测试内容:
- 验证传入null时返回字符串"null"
3.2.4 测试toString输出格式
测试方法: testToStringFormat()
测试内容:
- 验证格式:ClassName
- 验证以类名开头
- 验证以}结尾
- 验证字段间有逗号和空格分隔
3.2.5 测试BaseEntity字段在子类toString中的包含
测试方法: testBaseEntityFieldsInSubclassToString()
测试内容:
- 验证父类id字段包含
- 验证父类createTime字段包含
- 验证父类updateTime字段包含
3.2.6 测试User类基本功能
测试方法: testUserBasicFunctionality()
测试内容:
- 验证getter方法
- 验证setter方法
- 验证静态方法
3.2.7 测试Product类基本功能
测试方法: testProductBasicFunctionality()
测试内容:
- 验证继承的getter方法
- 验证自身的getter方法
3.3 运行所有测试
# 运行所有测试
mvn test -Dtest=AnnotationProcessorTest
# 或运行整个编译测试套件
mvn clean test四、代码执行预期结果
4.1 测试执行结果
执行mvn test -Dtest=AnnotationProcessorTest预期输出:
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.linsir.abc.core.jvm.compile.AnnotationProcessorTest
User toString输出: User{id=1, username=zhangsan, age=25}
Null对象toString输出: null
Product toString输出: Product{id=100, createTime=2026-03-28T15:54:58.474893100, updateTime=2026-03-28T15:54:58.474893100, name=iPhone 15, description=Apple iPhone 15, price=5999.00, stock=100}
包含父类字段的toString输出: Product{id=200, createTime=2024-01-01T10:00, updateTime=2024-01-02T15:30, name=MacBook Pro, description=Apple MacBook Pro, price=14999.00, stock=50}
格式验证输出: User{id=1, username=test, age=20}
[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0
[INFO] -------------------------------------------------------
[INFO] BUILD SUCCESS4.2 各测试用例预期结果
4.2.1 User类toString测试
输入:
User user = new User(1L, "zhangsan", "secret123", 25);
String result = UserToStringImpl.toString(user);预期输出:
User{id=1, username=zhangsan, age=25}验证点:
- ✅ 包含id字段
- ✅ 包含username字段
- ✅ 包含age字段
- ✅ 不包含password字段(被exclude排除)
- ✅ 不包含DEFAULT_ROLE(static字段)
4.2.2 Product类toString测试(包含父类)
输入:
Product product = new Product(
100L, now, now,
"iPhone 15", "Apple iPhone 15",
new BigDecimal("5999.00"),
new BigDecimal("4999.00"),
100
);
String result = ProductToStringImpl.toString(product);预期输出:
Product{id=100, createTime=2026-03-28T15:54:58.474893100, updateTime=2026-03-28T15:54:58.474893100, name=iPhone 15, description=Apple iPhone 15, price=5999.00, stock=100}验证点:
- ✅ 包含父类id字段
- ✅ 包含父类createTime字段
- ✅ 包含父类updateTime字段
- ✅ 包含自身name、description、price、stock字段
- ✅ 不包含costPrice字段(被exclude排除)
4.2.3 null对象处理测试
输入:
String result = UserToStringImpl.toString(null);预期输出:
null验证点:
- ✅ 返回字符串"null",不抛出异常
4.3 编译期输出
在编译时,注解处理器会输出处理日志:
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ linsir-abc-core ---
[INFO] 已为类 User 生成toString实现
[INFO] 已为类 Product 生成toString实现
[INFO] Compiling 7 source files to target/classes4.4 生成的文件
编译后会在target/generated-sources/annotations目录下生成:
com/linsir/abc/core/jvm/compile/
├── UserToStringImpl.java
└── ProductToStringImpl.java4.5 实际应用场景
场景1:日志输出
@AutoToString(exclude = {"password", "secretKey"})
public class User {
private String username;
private String password;
private String secretKey;
}
// 使用
logger.info("用户信息: {}", UserToStringImpl.toString(user));
// 输出: 用户信息: User{username=admin}
// password和secretKey不会被记录到日志场景2:调试信息
@AutoToString(includeSuper = true)
public class Order extends BaseEntity {
private String orderNo;
private BigDecimal amount;
}
// 使用
System.out.println(OrderToStringImpl.toString(order));
// 输出: Order{id=1001, createTime=2024-01-01T10:00, orderNo=ORD2024001, amount=999.99}五、注意事项
5.1 使用限制
- SOURCE保留策略: 注解只在编译期存在,运行时无法通过反射获取
- 静态字段排除: 所有static字段自动排除,不参与toString生成
- 循环依赖: 如果类之间存在循环引用,可能导致StackOverflowError
5.2 性能考虑
- 编译期开销: 注解处理器会增加编译时间
- 运行时性能: 生成的代码使用StringBuilder,性能优于字符串拼接
- 内存占用: 生成的辅助类会增加类加载数量
5.3 扩展建议
- 支持方法引用: 可以扩展支持getter方法引用,处理计算属性
- 格式化选项: 可以添加格式化选项,支持JSON、XML等输出格式
- 缓存机制: 对于复杂对象,可以添加toString结果缓存