项目Demo地址:点击这里
整体的思路:利用自定义的注解处理器(IProcessor及其实现),配合Class的Annotation相关API,对目标的.class文件进行解析,最终处理得到SQL建表语句(这期间用到注解解析辅助bean类:TableInfo和ColumnInfo,以及工具类:ClassFileUtils)
一、注解的定义
说了是Java注解的应用,当然得先自定义注解。
///@Entity:用于修饰类,表示此类需要转换为数据库表
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Entity {
String value() default "";
}
///@Column:用于修饰属性,用于映射到数据库表字段
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
String value() default "";// 字段值
boolean nullable() default true;///是否可空
boolean autoIncrement() default false;//是否自增
int length() default -1;///列(字段) 长度
}
///@ID:用于修饰属性,表示此属性将映射为数据库表主键
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ID {
String value() default "";///表示映射成的注解名称
}
二、根据目的(输入对应文件目录,得到需要的SQL建表语句),创建自定义的注解处理器:
public interface IProcessor {
String process(String url) throws Exception;
}
public class TableProcessor implements IProcessor {
@Override
public String process(String url) throws Exception {
///① 利用工具类,深度优先遍历的形式获取所有的文件
List classFiles = ClassFileUtils.getClassFiles(url);
StringBuilder sql = new StringBuilder();
///② 对每一个文件都进行类的获取,转成类列表
for (File file : classFiles){
Class> clazz = ClassFileUtils.loadClass(file);
///③ 分析Class类中的注解,并生成SQL语句,存储到StringBuilder中
TableInfo tableInfo = new TableInfo();
tableInfo = tableInfo.parse(clazz);
if (tableInfo!=null){
sql.append(tableInfo.toString());
}
}
return sql.toString();
}
}
三、在写自定义的注解处理器时,我们发现,以上代码的①、②、③的过程,是需要比较复杂的逻辑的,那么,就一步步去实现。
- 首先, 遍历目录下的所有.class文件:
/** * 获取目录下所有.class文件 * @param url 查找目录的绝对路径 * @return 所有Class文件(类文件) */ public static List
getClassFiles(String url){ allClassFile = new ArrayList<>(); File file = new File(url); //如果是目录,则递归搜索该目录 if (file.isDirectory()){ fillClassFiles(file); }else{ ///否则,如果是 if (isClassFile(file)){ allClassFile.add(file); } } return allClassFile; } /** * 递归函数:递归搜索目录 * @param directory 目录 */ private static void fillClassFiles(File directory) { File[] list = directory.listFiles(); for(File file: list){ if (file.isDirectory()){ fillClassFiles(file); }else{ if (isClassFile(file)){ allClassFile.add(file); } } } } /** * 是否是class文件 * @param file 目标文件 * @return 是否匹配 */ private static boolean isClassFile(File file) { return getFileType(file).equals("class"); } /** * 获取文件类型 * @param file 目标文件(编译之后的.class文件) * @return 文件后缀名 */ private static String getFileType(File file) { String fileName= file.getName(); return fileName.substring(fileName.lastIndexOf(".")+1); } - 然后,对每一个.class文件都获取到它的Class类:
/** * .class文件解析出 Class对象的过程(重点) * 关键点:读取.class文件中的内容,找到类名,并通过Class.forName(类名)得到Class>对象 * @param file .class文件 * @return 类类型对象 * @throws Exception 解析过程中的异常 */ public static Class> loadClass(File file) throws Exception { Map
offsetMap = new HashMap (); Map classNameMap = new HashMap (); DataInputStream data = new DataInputStream(new FileInputStream(file));///读取原始数据类型 int magic = data.readInt(); // 0xcafebabe int minorVersion = data.readShort(); int majorVersion = data.readShort(); int constant_pool_count = data.readShort(); int[] constant_pool = new int[constant_pool_count]; for (int i = 1; i < constant_pool_count; i++) { int tag = data.read(); int tableSize; switch (tag) { case 1: // UTF int length = data.readShort(); char[] bytes = new char[length]; for (int k = 0; k < bytes.length; k++) bytes[k] = (char) data.read(); String className = new String(bytes); classNameMap.put(i, className); break; case 5: // LONG case 6: // DOUBLE data.readLong(); // discard 8 bytes i++; // Special skip necessary break; case 7: // CLASS int offset = data.readShort(); offsetMap.put(i, offset); break; case 8: // STRING data.readShort(); // discard 2 bytes break; case 3: // INTEGER case 4: // FLOAT case 9: // FIELD_REF case 10: // METHOD_REF case 11: // INTERFACE_METHOD_REF case 12: // NAME_AND_TYPE data.readInt(); // discard 4 bytes; break; default: throw new RuntimeException("Bad tag " + tag); } } short access_flags = data.readShort(); int this_class = data.readShort(); int super_class = data.readShort(); int classNameOffset = offsetMap.get(this_class); String className = classNameMap.get(classNameOffset).replace("/", "."); Class> clazz = Class.forName(className); return clazz; } - 最后,对Class类中的注解的分析和生成对应的SQL建表语句:
前两步,我们可以用一个工具类ClassFileUtils来封装,而最后一步,除了事先创建一个临时数据存储的StringBuilder对象来准备SQL语句的存储工作之外,我们用两个类:①ColumnInfo ②TableInfo 来分别对【类<->数据库表】和【类属性<->数据库表字段】两个过程来做处理:- ColumnInfo.java:
- ColumnInfo.java:
public class ColumnInfo {
private String columnName;///字段名称
private Class> type;//字段类型
private boolean isID = false;//主键?
private boolean nullable = true;//非空?
private boolean isAutoIncrement = false;//自增?
private int length = 32;//字段长度(默认)
private boolean needPersist = false; ///是否保存本字段到数据库中
public ColumnInfo parse(Field field) {
this.columnName = field.getName();
this.type = field.getType();
Annotation[] annotations = field.getAnnotations();
////做注解的两个判断:①如果是@Column 则说明有字段别名,需要更新字段名 ②如果是@ID,说明是主键
for (Annotation annotation:annotations){
if (annotation.annotationType().equals(Column.class)){
this.needPersist = true;
Column column = (Column)annotation;//强转赋值
if (!column.value().equals("")) this.columnName=column.value();
this.nullable = column.nullable();
this.isAutoIncrement = column.autoIncrement();
if (column.length()!=-1) this.length = column.length();
}else if (annotation.annotationType().equals(ID.class)){
this.needPersist = true;
ID id = (ID)annotation;
isID = true;
if (!id.value().equals("")){
this.columnName = id.value();
}
}
}
if (this.needPersist)
return this;
else
return null;
}
@Override
public String toString() {
StringBuilder sql = new StringBuilder(columnName);
if (this.type.equals(String.class)){
sql.append(" VARCHAR(").append(this.length).append(")");
}else if (this.type.equals(Integer.class)){
sql.append(" INT");
}
if (this.isID){
sql.append(" PRIMARY KEY");
}
if (this.isAutoIncrement){
sql.append(" AUTO INCREMENT");
}
if (!this.nullable){
sql.append(" NOT NULL");
}
sql.append(";");
return sql.toString();
}
}
```
- TableInfo.java:
public class TableInfo {
private String tableName;
private Class> clazz;
private boolean needPersist = false;
private Map
public TableInfo parse(Class> clazz){
this.clazz = clazz;
this.tableName = this.clazz.getSimpleName();
Annotation[] annotations = this.clazz.getAnnotations();
for (Annotation annotation:annotations){
if (annotation.annotationType().equals(Entity.class)){
this.needPersist = true;
Entity entity = (Entity)annotation;
if (!entity.value().equals("")){
this.tableName = entity.value();
}
break;
}
}
if (this.needPersist){//说明有 @Entity 注解
Field[] fields = this.clazz.getDeclaredFields();
for (Field field:fields){
ColumnInfo columnInfo = new ColumnInfo();
columnInfo = columnInfo.parse(field);
if (columnInfo!=null){
this.columnInfos.put(field.getName(),columnInfo);
}
}
return this;
}else{
return null;
}
}
@Override
public String toString() {
StringBuilder sql = new StringBuilder();
sql.append("\n");
sql.append("CREATE TABLE ");
sql.append(this.tableName).append(" (");
for (ColumnInfo columnInfo: this.columnInfos.values()){
sql.append("\n ");
sql.append(columnInfo.toString());
}
sql.append("\n);");
return sql.toString();
}
}
```
四、测试类
@Entity("person")
public class Person {
@ID("pid")
@Column(autoIncrement=true,length = 10)
public Integer id;
@Column(value = "pname",length = 255)
public String name;
}
开始测试:
public class Main {
public static void main(String[] args) throws Exception {
Person person = new Person();
///注解处理器(内部use了 TableInfo 和 ColumnInfo),将类转成SQL语句
TableProcessor processor = new TableProcessor();
String sql = processor.process(System.getProperty("user.dir"));
System.out.println(sql);
}
}
得到:
CREATE TABLE person (
pname VARCHAR(255);
pid INT PRIMARY KEY AUTO_INCREMENT;
);