美文网首页Java注解
Java注解应用案例--实现.class文件到sql建表语句的转

Java注解应用案例--实现.class文件到sql建表语句的转

作者: androidjp | 来源:发表于2016-11-15 15:40 被阅读382次

    项目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<File> 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<File> 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<Integer, Integer> offsetMap = new HashMap<Integer, Integer>();
            Map<Integer, String> classNameMap = new HashMap<Integer, String>();
            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:

    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<String,ColumnInfo> columnInfos = new HashMap<>();

    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;
    );
    

    相关文章

      网友评论

        本文标题:Java注解应用案例--实现.class文件到sql建表语句的转

        本文链接:https://www.haomeiwen.com/subject/wzzkpttx.html