1. Java 注解和反射在代码生成工具中的联系
- 注解定义规则:Java 注解用于在代码中添加元数据,为代码生成工具提供特定的指令和配置信息。例如,定义一个
@DatabaseTable
注解,用于标识一个类对应数据库中的表,以及 @Column
注解标识类中的字段对应数据库表的列。
- 反射获取信息:反射机制允许在运行时获取类的各种信息,包括注解。通过反射可以遍历类及其成员(字段、方法等),获取它们所包含的注解信息。例如,通过
Class.getAnnotations()
方法获取类上的所有注解,通过 Field.getAnnotation(AnnotationClass.class)
获取字段上特定类型的注解。
- 代码生成依据:获取到的注解信息作为代码生成的依据,代码生成工具(如 Freemarker)根据这些信息填充预先定义好的代码模板,生成实际的代码。
2. 实际场景 - 根据特定注解生成数据库访问层代码
2.1 定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DatabaseTable {
String tableName();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
String columnName();
String dataType();
boolean isPrimaryKey() default false;
}
2.2 使用反射获取注解信息
import java.lang.reflect.Field;
public class AnnotationProcessor {
public static void process(Class<?> clazz) {
DatabaseTable tableAnnotation = clazz.getAnnotation(DatabaseTable.class);
if (tableAnnotation != null) {
String tableName = tableAnnotation.tableName();
System.out.println("Table Name: " + tableName);
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
Column columnAnnotation = field.getAnnotation(Column.class);
if (columnAnnotation != null) {
String columnName = columnAnnotation.columnName();
String dataType = columnAnnotation.dataType();
boolean isPrimaryKey = columnAnnotation.isPrimaryKey();
System.out.println("Column Name: " + columnName + ", Data Type: " + dataType + ", Is Primary Key: " + isPrimaryKey);
}
}
}
}
}
2.3 使用 Freemarker 生成代码模板
- 引入 Freemarker 依赖:在
pom.xml
中添加依赖
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
- 定义 Freemarker 模板(如
dao.ftl
):
package com.example.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class ${className}Dao {
private static final String TABLE_NAME = "${tableName}";
public ${className} findById(int id) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = DatabaseUtil.getConnection();
pstmt = conn.prepareStatement("SELECT * FROM " + TABLE_NAME + " WHERE id =?");
pstmt.setInt(1, id);
rs = pstmt.executeQuery();
if (rs.next()) {
${className} entity = new ${className}();
<#list columns as column>
<#if column.isPrimaryKey>
entity.set${column.propertyName}(rs.getInt("${column.columnName}"));
<#elseif column.dataType == "varchar">
entity.set${column.propertyName}(rs.getString("${column.columnName}"));
</#if>
</#list>
return entity;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DatabaseUtil.close(rs, pstmt, conn);
}
return null;
}
}
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CodeGenerator {
public static void generateCode(Class<?> clazz) {
DatabaseTable tableAnnotation = clazz.getAnnotation(DatabaseTable.class);
if (tableAnnotation != null) {
String tableName = tableAnnotation.tableName();
String className = clazz.getSimpleName();
List<ColumnInfo> columns = new ArrayList<>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
Column columnAnnotation = field.getAnnotation(Column.class);
if (columnAnnotation != null) {
ColumnInfo columnInfo = new ColumnInfo();
columnInfo.columnName = columnAnnotation.columnName();
columnInfo.dataType = columnAnnotation.dataType();
columnInfo.isPrimaryKey = columnAnnotation.isPrimaryKey();
columnInfo.propertyName = field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
columns.add(columnInfo);
}
}
Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
try {
cfg.setDirectoryForTemplateLoading(new File("src/main/resources/templates"));
Template template = cfg.getTemplate("dao.ftl");
Map<String, Object> data = new HashMap<>();
data.put("className", className);
data.put("tableName", tableName);
data.put("columns", columns);
File outputFile = new File("src/main/java/com/example/dao/" + className + "Dao.java");
try (FileWriter writer = new FileWriter(outputFile)) {
template.process(data, writer);
}
} catch (IOException | TemplateException e) {
e.printStackTrace();
}
}
}
static class ColumnInfo {
String columnName;
String dataType;
boolean isPrimaryKey;
String propertyName;
}
}
3. 处理不同类型的注解参数及嵌套注解情况
- 不同类型注解参数:如上述
@Column
注解中,columnName
为字符串类型,isPrimaryKey
为布尔类型。在反射获取注解信息时,根据注解中定义的参数类型进行相应的取值。在 Freemarker 模板中,针对不同类型参数在代码逻辑中有不同的处理方式,如布尔类型用于判断是否为主键等逻辑分支。
- 嵌套注解情况:假设存在嵌套注解,如
@InnerAnnotation
嵌套在 @OuterAnnotation
中。首先通过反射获取外层注解 @OuterAnnotation
,然后通过外层注解的方法获取内层注解,如 OuterAnnotation.getInnerAnnotation()
。在代码生成时,根据内层注解的信息进一步细化生成的代码逻辑,如在内层注解定义了特殊的查询条件等,在生成的数据库访问层代码中添加相应的逻辑。