MST

星途 面试题库

面试题:Java注解与反射在代码生成工具中的深度应用

阐述Java注解和反射在代码生成工具中的紧密联系。以一个实际场景为例,如根据特定注解生成数据库访问层代码,说明如何利用反射获取注解信息,并基于这些信息使用代码生成工具(如Freemarker)生成高质量、可复用的代码模板,同时要考虑如何处理不同类型的注解参数及嵌套注解情况。
41.5万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

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()。在代码生成时,根据内层注解的信息进一步细化生成的代码逻辑,如在内层注解定义了特殊的查询条件等,在生成的数据库访问层代码中添加相应的逻辑。