Java 注解(Annotation)的简介与使用实例

注解和注释的联系与区别

注解和注释都有对目标(类、方法、参数等)进行补充说明的作用,它们的区别在于:

  • 注释只存在于类文件中,不参与编译;注解可以参与编译,也可以不参与编译。
  • 注释内容仅供开发者和用户在编写程序时查看,对程序本身不会造成任何影响;注解可以在程序运行时利用反射读取,可以对程序本身造成影响。

java.lang中定义的三种注解

java.lang中定义了三种常用的注解:

  • @Override:表示当前的方法定义将覆盖超类中的方法。如果把方法的名字拼错,或是参数列表与超类不一致(变成了重载),编译器就会发出错误提示。
  • @Deprecated:表示该方法已经被废弃。如果在代码中使用被@Deprecated注解的方法,编译器会发出警告。
  • @SuppressWarnings:关闭不当的编译器警告信息。

标记注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {

}

上面的代码段中就定义了一个最简单的自定义注解。这个注解中没有任何元素,因此被称为标记注解。可以看出,注解的定义和接口很像,仅仅是在interface关键字前面加上了一个@。与类和接口的不同的是,在创建注解时需要加上两个元注解:@Target与@Retention。元注解是Java系统提供的,负责新注解的创建。

@Target

@Target用于定义该注解将应用于什么地方,可以是类、方法、域、参数等等。它的取值(存在于枚举类ElementType中)有以下几种:

  • TYPE:类、接口(包括注解)、枚举
  • FIELD:域、枚举实例
  • METHOD:方法
  • PARAMETER:参数声明
  • CONSTRUCTOR:构造器
  • LOCAL_VARIABLE:局部变量
  • ANNOTATION_TYPE:注解
  • PACKAGE:包
  • (Java 8)TYPE_PARAMETER:类型变量的声明。例:String str= new @NotNull String();
  • (Java 8)TYPE_USE:使用类型的任何语句(声明语句、泛型和强制转换语句中的类型)。例:myString = (@NonNull String) str;

@DBTable被@Target(ElementType.TYPE)标注,因此它可以被用在类、接口、枚举上。

@Retention

@Retention用于定义注解在哪个级别可用。它的取值(存在于RetentionPolicy枚举类中)有以下几种:

  • SOURCE:注解会被编译器抛弃
  • CLASS:注解在class文件中可用,但会在运行时被VM抛弃
  • RUNTIME:注解在运行期也将被保留,可以利用反射读取

@DBTable被@Retention(RetentionPolicy.RUNTIME)标注,因此它可以存在于运行期。

注解的简单使用

@DBTable
public class Table {
    ...
}

这样就给Table类加上了@DBTable注解。可以看出,注解的使用几乎和其他修饰符(public、static等)相同。
现在,这个注解还没有任何意义,后面会演示它的作用。

为注解添加元素

注解中可以添加以下类型的元素:

  • 所有基本类型(int、long)
  • String
  • Class
  • enum
  • Annotation
  • 以上类型的数组

下面为@DBTable注解添加一个String类型的tableName元素:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
    public String tableName() default "";
}

注意:元素名称后面要加上一对圆括号。这是因为注解本身是一种继承自接口java.lang.annotation.Annotation的特殊接口。
可以利用default为元素设置默认值,当使用注解时没有给出该元素的值时,会使用预先设置的默认值。如果元素没有提供默认值,那么在使用时就必须给定一个值,否则编译器会报错。

有元素的注解的使用

@DBTable(tableName = "Table1")
public class Table {

}

这样就将@DBTable中的tableName设置成了“Table1”。
如果注解有多个元素需要设置,只需用逗号分隔开:

@User(id = 100, name = "Bob")

如果某个元素是数组,那么需要加上一对大括号:

@SuiteClasses({
    Test1.class,
    Test2.class,
    Test3.class
})

上面的例子中还有一个特殊的地方:前面在为元素赋值时都使用“名 = 值”的模式,而这里没有显式标明元素名。实际上,这个是Java系统提供的一种“快捷方式”:如果注解中某个元素是唯一需要赋值的元素(唯一一个没有提供默认值的元素,或者是唯一一个元素),并且该元素值名为value,那么在赋值时就可以省去元素名与等号。
下面是SuiteClasses的定义,可以看到它只有一个元素,并且名称为value:

public @interface SuiteClasses {
    public Class<?>[] value();
}

注解处理器实例:根据JavaBean生成SQL语句

相比于注释,注解的最大优点就是可以在运行时通过反射读取其内容,这为编写各种工具类乃至框架提供了极大的便利。下面的例子中提供了一个工具类以及一系列注解,可以根据满足JavaBean规范的类动态地生成SQL语句。
首先是使用到的自定义注解:
(1)表名注解@DBTable:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
    public String tableName() default "";
}

(2)整型注解@SQLInteger:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
    String name() default "";
}

(3)字符串型注解@SQLString:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
    public String name() default "";
    public int length();
}

下面是注解处理器,基本都是通过反射读取注解内容实现。需要注意的一点是,JavaBean模式下所有域的访问权限都是private的,因此需要利用reflect包内的AccessibleObject类提供的方法获取其访问权限。

public class TableCreater {

    public static String printSQL(Class<?> clazz){
        //判断clazz是否有DBTable注解,若没有则返回
        DBTable dbTable;
        if(clazz == null || (dbTable = clazz.getAnnotation(DBTable.class)) == null) {
            return null;
        }

        //获得表名
        String tableName = dbTable.tableName().length() < 1 ?
                clazz.getName().toUpperCase() : dbTable.tableName() .toUpperCase();

        //获得所有域及其访问权限
        Field[] fields = clazz.getDeclaredFields();
        AccessibleObject.setAccessible(fields, true);

        //生成SQL语句CREATE TABLE中每一行的内容
        List<String> columns = new LinkedList<>();
        for(Field field : fields) {
            String column = null;
            Annotation[] annotations = field.getDeclaredAnnotations();
            if(annotations.length < 1) {
                continue;
            }
            for(Annotation annotation : annotations) {
                if(annotation instanceof SQLInteger) {
                    SQLInteger sInteger = (SQLInteger)annotation;
                    String name = sInteger.name().length() < 1 ?
                            field.getName().toUpperCase() : sInteger.name().toUpperCase();
                    column = name + " " + "INT";
                }else if (annotation instanceof SQLString) {
                    SQLString sString = (SQLString)annotation;
                    String name = sString.name().length() < 1 ?
                            field.getName().toUpperCase() : sString.name().toUpperCase();
                    String type = "VARCHAR(" + String.valueOf(sString.length()) + ")";
                    column = name + " " + type;
                }
            }
            if(column != null){
                columns.add(column);
            }
        }

        //将表名和各行内容拼接成SQL语句
        StringBuilder sql = new StringBuilder();
        sql.append("CREATE TABLE ");
        sql.append(tableName);
        sql.append("(\n");
        for(int i = 0 ; i < columns.size() ; i++){
            sql.append("    ");
            sql.append(columns.get(i));
            if(i < columns.size() - 1){
                sql.append(',');
            }
            sql.append('\n');
        }
        sql.append(")\n");

        //返回结果
        return sql.toString();
    }

}

下面看一个运行实例。首先是需要生成SQL语句的UserInfo类:

@DBTable(tableName = "USER")
public class UserInfo {
    public UserInfo() {

    }

    @SQLString(length = 10)
    private String name;

    @SQLInteger
    private int age;

    @SQLString(length = 30)
    private String job;

    @SQLString(length = 30)
    private String email;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getJob() {
        return job;
    }
    public void setJob(String job) {
        this.job = job;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
}

将UserInfo.class作为参数传入TableCreater.printSQL方法中:

public static void main(String[] args) {
    System.out.println(TableCreater.printSQL(UserInfo.class));
}

输出结果:

CREATE TABLE USER(
    NAME VARCHAR(10),
    AGE INT,
    JOB VARCHAR(30),
    EMAIL VARCHAR(30)
)

@Documented与@Inherited

@Documented与@Inherited也是创建自定义注解时使用的元注解,它们的意义如下:

  • @Documented:将此注解包含在Javadoc中。
  • @Inherited:允许子类继承父类的注解。

(Java 8)重复注解

一般的注解不能在一个目标上重复使用。不过,在Java 8中新增了一个@Repeatable注解,通过它可以实现重复注解。使用方法如下:
(1)创建一个需要实现重复注解功能的注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface User {
    String value();
}

(2)创建一个注解容器:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Users {
    User[] value();
}

(3)为(1)中的注解加上@Repeatable并赋值,值为(2)中的注解容器的Class对象:

@Repeatable(Users.class)

使用示例:

@User("John")
@User("Bob")
public class SomeClass {

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值