技术小谈|反射和类加载的一个简单应用
小明是一个刚入职不久的初级开发,虽然他掌握了很多Java的基础知识,但工作中一直没有机会用上,时常感到有些迷茫。某天,领导突然安排他编写一个系统的数据字典,由于项目代码风格良好,实体类中的字段都有中文名称,并且使用注解注释上了。面对这个任务,小明觉得如果手动维护太麻烦了,既费时间,又废精力,但他突然想起学习时提到的反射机制以及类加载,似乎可以用来动态获取类的信息。于是他深入研究,成功利用反射自动生成数据字典。
好了,刚刚那个是需求背景,接下来我们简单聊一下这个小功能,说实话这个事情谁都可以做,无非就是做的快慢的事情,这里想用这个无非就是想说平常我们有些东西用起来代码写确实会时间更长一些,但是现在有了AI,我们有这个想法后在让AI来帮我们做这个事情确实会很容易。
当然这里 作者只是抛砖引玉,这个东西实际上确实有这么个活,然后我把想法告诉了chatgpt,让他按照我说的写出了这段代码,加上调试,以及运行一共也就不到一个小时就搞定了,最后输出成了一个txt的文档,然后转换一下 直接复制到了excel,确实比纯手工维护方便很多,当然肯定会有其他问题,只不过在我这边跑通了而已,这个其实像网上的逆向原理也是差不多的。话不多说上代码:
package com.demo;// todo 这个包这里的注解是作者公司封装的
import com.demo.Comment;import javax.persistence.Column;
import javax.persistence.Table;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;public class AnnotationProcessor {public static void main(String[] args) {// todo 只需要在这里定义路径即可,避免重复硬编码String basePackagePath = "/Users/xx/job/com/demo/xxx/data/dao";String entityPath = basePackagePath + "/target/classes/com/demo/xxx/data/dao/entity"; // 只需调整这里路径,其他地方动态引用 注意 这里读取的是编译后的文件File dir = new File(entityPath);List<String> result = new ArrayList<>();// 检查目录是否存在且为有效目录if (dir.exists() && dir.isDirectory()) {File[] files = dir.listFiles((file) -> file.getName().endsWith(".class"));if (files != null) {URL[] urls;try {// 将目录路径转换为URL,用于类加载器urls = new URL[]{dir.toURI().toURL()};} catch (MalformedURLException e) {throw new RuntimeException("URL转换失败: " + dir.getAbsolutePath(), e);}// 使用自定义类加载器加载类文件try (URLClassLoader classLoader = new URLClassLoader(urls)) {for (File file : files) {// 根据文件路径获取类名String className = getClassNameFromFile(file, entityPath, basePackagePath);try {// 处理类中的注解processClassAnnotations(className, classLoader, result);} catch (NoClassDefFoundError e) {System.err.println("加载类时出现NoClassDefFoundError: " + className);e.printStackTrace();} catch (Exception e) {System.err.println("处理类时出错: " + className);e.printStackTrace();}}} catch (Exception e) {System.err.println("类加载时出错: " + e.getMessage());e.printStackTrace();}}} else {System.err.println("目录 " + entityPath + " 不存在或不是有效目录。");}// 写入结果到文件writeToFile(result);}// 从文件名获取类的全限定名,避免硬编码package,动态构建类路径private static String getClassNameFromFile(File file, String basePath, String basePackagePath) {String absolutePath = file.getAbsolutePath();String classPath = absolutePath.replace(basePath + "/", "").replace("/", ".").replace(".class", "");return basePackagePath.replace("/", ".") + "." + classPath; // 根据基础包路径动态拼接类名}// 处理类中的注解,提取表名、列名及注释信息private static void processClassAnnotations(String className, ClassLoader classLoader, List<String> result) {try {// 使用类加载器加载指定的类Class<?> clazz = Class.forName(className, true, classLoader);Table tableAnnotation = clazz.getAnnotation(Table.class);if (tableAnnotation != null) {String tableName = tableAnnotation.name();for (Field field : clazz.getDeclaredFields()) {Column columnAnnotation = field.getAnnotation(Column.class);if (columnAnnotation != null) {String columnName = columnAnnotation.name();Comment commentAnnotation = field.getAnnotation(Comment.class);String comment = commentAnnotation != null ? commentAnnotation.value() : "";// 构建结果行,格式:表名 | 列名 | 注释String line = tableName + " | " + columnName + " | " + comment;result.add(line);}}}} catch (NoClassDefFoundError e) {System.err.println("加载类时出现NoClassDefFoundError: " + className);e.printStackTrace();} catch (Exception e) {System.err.println("处理类时出错: " + className);e.printStackTrace();}}// 将结果写入到txt文件中private static void writeToFile(List<String> result) {System.out.println("开始写入文件……");System.out.println("当前工作目录: " + System.getProperty("user.dir"));try (FileWriter writer = new FileWriter("output.txt")) {for (String line : result) {writer.write(line + System.lineSeparator());}} catch (IOException e) {System.err.println("写入文件时出错: " + e.getMessage());e.printStackTrace();}}
}