Java注解(2)-注解处理器(运行时|RetentionPolicy.RUNTIME)

如果没有用来读取注解的工具,那注解将基本没有任何作用,它也不会比注释更有用。读取注解的工具叫作注解处理器。Java提供了两种方式来处理注解:第一种是利用运行时反射机制;另一种是使用Java提供的API来处理编译期的注解。

反射机制方式的注解处理器

仅当定义的注解的@Retention为RUNTIME时,才能够通过运行时的反射机制来处理注解。下面结合例子来说明这种方式的处理方法。

Java中的反射API(如java.lang.Class、java.lang.reflect.Field等)都实现了接口java.lang.reflect.AnnotatedElement,来提供获取类、方法和域上的注解的实用方法。

通过JavaBean上定义的注解来生成相应的SQL。

1.1、定义注解

1.1.1、类注解映射表名

package com.zenfery.example.annotation.sql;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

@Target(ElementType.TYPE)//定义注解应用于类

@Retention(RetentionPolicy.RUNTIME)//定义注解在JVM运行时保留

public @interface TableSQL {

    String value() default "";//指定对应的表名

}

定义注解@TableSQL,只定义一个value值来映射表名,默认值为空,如果程序不给此值,将使用类名(小写)来作为表名。

1.1.2、属性与字段对应注解

package com.zenfery.example.annotation.sql;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

@Target(ElementType.FIELD)//定义注解应用于成员变量

@Retention(RetentionPolicy.RUNTIME)//定义注解在JVM运行时保留

public @interface TableColumnSQL {

 String value() default "";

 Constraint constraint() default @Constraint();

}

定义注解@TableColumnSQL的目标为FIELD,仅能在类的属性上使用;value()属性定义对应的字段名;constraint()定义字段的约束,它是由注解@Constraint定义,其定义如下:

package com.zenfery.example.annotation.sql;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

@Target(ElementType.FIELD)//定义注解应用于成员变量

@Retention(RetentionPolicy.RUNTIME)//定义注解在JVM运行时保留

public @interface Constraint {

 boolean allowNull() default true//是否允许为空

 boolean isPrimary() default false//是否为主键

}

@Constraint注解仅定义了两个注解元素,allowNull()指定字段是否允许为空值;isPrimary()指定字段是否是主键。

1.2、定义JavaBean

package com.zenfery.example.annotation.clazz;

import com.zenfery.example.annotation.sql.Constraint;

import com.zenfery.example.annotation.sql.TableColumnSQL;

import com.zenfery.example.annotation.sql.TableSQL;

/**

 * 定义User类与数据库映射

 * @author zenfery

 */

@TableSQL()

public class User {

 

 //定义id字段,与表user的列id相映射,指定约束为:不为空,为主键。

 @TableColumnSQL(value="id",constraint=@Constraint(allowNull=false,isPrimary=true))

 String id;

 

 //只为注解指定value字段,可省略value。

 @TableColumnSQL("name")

 String name;

}

定义User类。类上使用@TableSQL来注解映射表名;字段id和name使用@TableColumnSQL来注解映射字段名。

1.2、注解处理器

在以上工作做完后,注解没有任何意义(它没有做任何事情),下面来为RUNTIME级别的注解来编写处理器,在此编写的处理器和真实的处理器相差比较多,无通用性,仅为演示理解用。详细代码如下:

package com.zenfery.example.annotation.clazz;

import java.lang.annotation.Annotation;

import java.lang.reflect.Field;

import java.util.ArrayList;

import java.util.List;

import com.zenfery.example.annotation.sql.Constraint;

import com.zenfery.example.annotation.sql.TableColumnSQL;

import com.zenfery.example.annotation.sql.TableSQL;

public class TableSQLHandler {

  /**

   * 注解处理器:读取User类中注解,生成对应的SQL并打印出来

   * 在此假设表的所有字段均为varchar(10)

   * @since JDK 1.6

   * @param args

   */

  public static void main(String[] args) {

    /**

     * 注解的@Retention均为JVM保留注解(RetentionPolicy.RUNTIME)

     * ,在此直接使用main方法启动JVM,通过Java提供的反射机制来处

     * 理。

     */

    try {

      //指定在JVM需要处理注解的类

      Class userClass = null;

      //userClass = Class.forName("com.zenfery.example.annotation.clazz.User");

      userClass = User.class;

      

      //打印所有类的注解

      Annotation[] annotations = userClass.getDeclaredAnnotations();

      for(int i=0; i<annotations.length; i++){

        System.out.println("注解["+(i+1)+"] = "+annotations[i].toString());

      }

      

      //检查类是否有@TableSQL注解

      if( userClass.isAnnotationPresent(TableSQL.class) ){

        //sql

        String sql = "\nCREATE TABLE ";

        //注解了TableSQL注解

        TableSQL ts = (TableSQL)userClass.getAnnotation(TableSQL.class);

        String tableName = ts.value();

        if("".equals(tableName)){//如果获取的值为TableSQL的默认值,则使用类名来做为表名

          tableName = userClass.getSimpleName().toLowerCase();

        }

        System.out.println("获取"+userClass.getName()+"对应的表名为:"+tableName);

        sql += tableName + " ( \n";

        

        //从User类的属性中获取需要与数据库映射的字段

        Field[] fields = userClass.getDeclaredFields();

        

        List<String> primaryKeys = new ArrayList<String>();//存储主键

        for(int i=0; i<fields.length; i++){

          Field field = fields[i];

          if( field.isAnnotationPresent(TableColumnSQL.class) ){

            TableColumnSQL tcs = (TableColumnSQL)field.getAnnotation(TableColumnSQL.class);

            String fieldName = tcs.value();//表中的字段名

            Constraint c = tcs.constraint();//字段对应的约束

            boolean allowNull = c.allowNull();//是否可为空

            boolean isPrimary = c.isPrimary();//是否为主键

            

            //拼接SQL

            sql += "\t" + fieldName +" VARCHAR(10)";

            if(!allowNull) sql += " NOT NULL";//不允许为空

            if(i<fields.length-1) sql+= ",\n";

            

            //主键

            if(isPrimary) primaryKeys.add(fieldName);

          }else{

            System.out.println("字段"+field.getName()+"未使用注解@TableColumnSQL!");

          }

        }

        if(primaryKeys.size()>0){

          StringBuilder keys = new StringBuilder();

          for(int k=0; k<primaryKeys.size(); k++){

            keys.append(primaryKeys.get(k));

            if(k<primaryKeys.size()-1)keys.append(",");

          }

          

          sql += ",\n\tPRIMARY KEY "+keys.toString();

        }

        sql += "\n) DEFAULT CHARSET=utf8";

        // ====> 打印SQL

        System.out.println("生成的SQL:"+sql);

        

      }else{

        System.out.println("警告:"+userClass.getName()+"未使用@TableSQL注解!");

      }

    catch (Exception e) {

      e.printStackTrace();

    }

  }

}

  1. 获取使用了注解的User类。
  2. 根据类上的注解@TableSQL获取表名。
  3. 根据类中所有的字段上的注解@TableColumnSQL来获取字段名,并获取字段的特性。
  4. 根据获取的表名和字段名拼接SQL。
  5. 打印SQL。

运行TableSQLHandler的main()方法,输出:

注解[1] = @com.zenfery.example.annotation.sql.TableSQL(value=)

获取com.zenfery.example.annotation.clazz.User对应的表名为:user

生成的SQL:

CREATE TABLE user (

 id VARCHAR(10) NOT NULL,

 name VARCHAR(10),

 PRIMARY KEY id

) DEFAULT CHARSET=utf8

转载请注明:子暃之路 ? Java注解(2)-注解处理器(运行时|RetentionPolicy.RUNTIME)

时间: 2024-10-15 01:37:42

Java注解(2)-注解处理器(运行时|RetentionPolicy.RUNTIME)的相关文章

Java虚拟机 - 结构原理与运行时数据区域

http://liuwangshu.cn/java/jvm/1-runtime-data-area.html 前言 本来计划要写Android内存优化的,觉得有必要在此之前介绍一下Java虚拟机的相关知识,Java虚拟机也并不是三言两语能够介绍完的,因此开了Java虚拟机系列,这一篇文章我们来学习Java虚拟机的结构原理与运行时数据区域. 1.Java虚拟机概述 Oracle官方定义的Java技术体系主要包括以下几个部分: Java程序设计语言 各种平台的Java虚拟机 Class文件格式 Ja

《深入理解Java虚拟机》笔记01 -- 运行时数据区

运行时数据区示意图 1. 程序计数器 占用一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器.主要用来记录线程执行到哪条语句了,分支.循环.跳转.异常处理.线程恢复等功能都需要依赖这个计数器来完成. 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址:如果正在执行的时Native方法,这个计数器值则为空.此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域. 2. Java 虚拟机栈 线程私有,生命

iOS运行时编程(Runtime Programming)和Java的反射机制对比

运行时进行编程,类似Java的反射.运行时编程和Java反射的对比如下: 1.相同点 都可以实现的功能:获取类信息.属性设置获取.类的动态加载(NSClassFromString(@“className”)).方法的动态调用 下面是iOS中涉及到的相关使用方法 类的动态加载:NSClassFromString(@“className”),方法的动态调用:NSSelectorFormString(@”doSonethingMethod:”) 常见的方法: isKindOfClass: isMemb

Java进阶(四)Java反射TypeToken解决泛型运行时类型擦除的问题解决

在开发时,遇到了下面这条语句,不懂,然习之. private List<MyZhuiHaoDetailModel> listLottery = new ArrayList<MyZhuiHaoDetailModel>(); Gson gson=new Gson(); JSONObject object=new JSONObject(callbackValue); listLottery =  gson.fromJson(object.getString("lists&quo

Java虚拟机原理图解----JVM运行时数据区

     JVM运行时数据区(JVM Runtime Area) 其实就是指JVM在运行期间,其对计算机内存空间的划分和分配.本文将通过以下几个话题来讨论JVM运行时数据区. Topic 1. JVM运行时数据区 里有什么? Topic 2. 虚拟机栈 是什么?虚拟机栈 里有什么? Topic 3. 栈帧是什么?栈帧 里有什么? Topic 4. 方法区 是什么?方法区 里有什么? Topic 1.JVM运行时数据区里有什么? Topic 2. 虚拟机栈是什么?虚拟机栈里有什么? Topic 3

聊聊高并发(四)Java对象的表示模型和运行时内存表示

在继续了解Java内存模型之前,最好先理解Java对象的内存表示.在网上搜了下Java对象内存表示,说得都不够系统和到位.之前看了<Hotspot实战>一书,对JVM如何表示对象这块说得挺好,推荐一下.如果不理解JVM运行时的各种内存区域以及Java调用的过程,那么很难把Java内存模型理解到位.这个是一个比较大的主题,以后会陆续写一些JVM相关的.这里单把Java对象的内存拿出来聊聊,文中内容都基于Hotspot虚拟机. Hotspot主要是用C++写的,所以它定义的Java对象表示模型也是

Java导入package编译后运行时提示无法加载主类的解决办法

Java代码中通过package导入包后,用命令行编译可以成功,但是运行时提示无法加载主类,这时候可以把编译后的class文件放在导入包的上一层文件夹,然后用 java 包名.类名 的命令形式运行 参考链接:点击这里 原文地址:https://www.cnblogs.com/excellent-ship/p/9134994.html

java虚拟机(1)--运行时数据区

一.              运行时数据区域 1.1 程序计数器 记录正在执行的虚拟机字节码指令的地址(如果正在执行的是本地方法则为空). 1.2 Java 虚拟机栈 每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表.操作数栈.常量池引用等信息.从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程. 可以通过 -Xss 这个虚拟机参数来指定每个线程的 Java 虚拟机栈内存大小: java -Xss512M HackTheJava 该区域可能抛

Java进阶 四 Java反射TypeToken解决泛型运行时类型擦除问题

在开发时,遇到了下面这条语句,不懂,然习之. private List<MyZhuiHaoDetailModel> listLottery = new ArrayList<MyZhuiHaoDetailModel>(); Gson gson=new Gson(); JSONObject object=new JSONObject(callbackValue); listLottery =  gson.fromJson(object.getString("lists&quo