继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

通过例子理解Java注解

郎朗坤
关注TA
已关注
手记 173
粉丝 47
获赞 212

完整代码: 代码

前言

先做个简单介绍. java1.5开始支持注解,有三个内置注解(1.8以后又增加了几个)

三个内置注解
内置注解作用
@Override表示当前的方法定义覆盖超类的方法.
@Deprecated如果程序中使用了注解为它的元素,编译器会发出警告信息
@SuppressWarnings关闭不当的编译器警告信息
四种元注解

负责新注解的创建,专门用于注解其他的注解的.

元注解作用
@Target表示注解可以用于ElementType什么地方
CONSTRUCTOR:构造器的声明
FIELD:域声明(包括enum实例)
LOCAL_VARIABLE:局部变量声明
METHOD:方法声明
PACKAGE:包声明
PARAMETER:参数声明
TYPE:类,接口(包括注解类型)或enum声明
@Retention表示需要在什么级别保存该注解信息
RetentionPolicy.SOURCE:注解将被编译器丢弃
RetentionPolicy.CLASS:注解在class文件中可用,会被VM丢弃
RetentionPolicy.RUNTIME:VM将在运行期也保留注解,因此可以通过反射机制来获取注解的信息
@Documented将此注解包含在Javadoc
@Inherited允许子类继承父类中的注解

例子1:计算缺失的测试用例

接下来将用一个例子来看看注解在程序运行期间是如何工作的,所以这里主要用到的是RetentionPolicy.RUNTIME,别的特性会另外说明.

下面的例子是要统计一下有哪些TestCase是没有测试到的

先自定义一个注解TestCase

import java.lang.annotation.*;@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface TestCase {    int id();    String name() default "no description";
}

然后再写一个Cases类包含了所有的测试用例

public class Cases {    
    @TestCase(id=1)    public void test_1() {}    
    @TestCase(id=2, name="test_2")    public void test_2() {}    
    @TestCase(id=3, name="test_3")    public void test_3() {}
}

如果我们不解析这个注解,那这个注解跟注释就没有太大区别,接下来就用java反射机制来解析注解(如果不了解反射机制没关系,这里只利用其中的一些简单方法,后续会有相应博客来说明java反射机制)

定义一个TestCaseParse

public class TestCaseParse {
    public static void main(String[] args) {
        testCaseParse(Cases.class);
    }    
    public static void testCaseParse(Class<?> clazz) {        // 拿到所有的方法(包括private)
        Method[] methods = clazz.getDeclaredMethods(); 
        for (Method m : methods) {            //两种方式都可以拿到m方法上的TestCase注解实例
            //TestCase testCase1 = (TestCase)m.getAnnotations()[0];
            //TestCase testCase2 = m.getAnnotation(TestCase.class);
            TestCase testCase = m.getAnnotation(TestCase.class);
            System.out.println("id=" + testCase.id() + ", name=" + testCase.name());
        }
    }
}

1: 其中Class.getDeclaredMethods()方法是可以拿到该Class的所有方法包括private,default,protected,public方法,但是没有包括父类中的方法.

2.其中可以有两种方法拿到对应方法上面的TestCase实例,至于Annotation和实例之间有什么联系或者区别,以后会进行讨论,目前我们只关心怎么可以拿到这个实例,因为拿到实例就可以拿到相应的属性了.

TestCase testCase1 = (TestCase)m.getAnnotations()[0];
TestCase testCase2 = m.getAnnotation(TestCase.class);

结果输出: 对于id=1的时候因为没有给name赋值,所以采用的是默认值.

id=3, name=test_3id=1, name=no descriptionid=2, name=test_2
计算缺失的测试用例

既然我们已经可以拿到测试方法上面的注解的实例,那统计一下数据就是自热而然的事情了. 在TestCaseParse类中再加入一个方法.

public class TestCaseParse {
    public static void main(String[] args) {
        Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5, 6));
        countMissingTestCase(Cases.class, set);
    }    
    public static void countMissingTestCase(Class<?> clazz, Set<Integer> set) {
        Method[] methods = clazz.getDeclaredMethods(); 
        for (Method m : methods) {
            TestCase testCase = m.getAnnotation(TestCase.class);            int id = testCase.id();            if (set.contains(id)) set.remove(id);
        }
        System.out.println("missing testcase : ");        for (int id : set) System.out.println("id=" + id);
    }
}

输出:

missing testcase : 
id=4id=5id=6

相信到这里对注解应该有了一个稍微直观一点的感受了.

自定义注解的规则

注解元素可用的类型如下:

内置注解可用类型
自定义注解1. 所有基本类型(int,float,boolean等等)
2. String
3. Class
4. enum
5. Annotation
6. 以上类型的数组

默认值限制:

1.元素不能有不确定的值,所以必须要么有默认值,要么在使用注解时提供元素的值
2.对于非基本类型的元素,不能以null作为它的值,所以在定义时的默认值也不能为null.

关于Value()

Note:当注解中有value值时,如果传入的值没有指定任何元素,默认是给value的

import java.lang.annotation.*;import java.lang.reflect.*;@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Testable {    //String name();
    String name() default "no name";    //String value() default "no value";
    String value();
}class TestValue {        // 默认给value的值
    @Testable("value")    public void test_1() {}
}class Parse {    public static void main(String[] args) throws NoSuchMethodException, SecurityException {
        Method m = TestValue.class.getMethod("test_1");
        Testable testable = m.getAnnotation(Testable.class);
        System.out.println("name=" + testable.name() + ", value=" + testable.value());
    }
}

此时按下面这种写法会默认把值给value,不管Testablevalue()是否有默认值,结果都是

name=no name, value=value
Testable结果
String name();
String value();
报错,因为没有给name赋值
String name() default "no name";
String value();
name=no name, value=value
String name() default "no name";
String value() default "no value";
name=no name, value=value
String name();
String value() default "no value";
报错,因为没有给name赋值
注解的嵌套

其实没有什么,就是把很多注解的公共部分提取出来,由于没有继承的概念,因此就用了这种嵌套的方式,看完代码你就懂了

import java.lang.annotation.*;@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@interface Nest {    boolean display() default true;
}@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@interface OutNest {    String name() default "no name";    Nest nest() default @Nest;
}public class TestNest {    public static void main(String[] args) throws NoSuchMethodException, SecurityException{
        OutNest outNest = TestNest.class.getMethod("test").getAnnotation(OutNest.class);
        System.out.println("name=" + outNest.name() + ", nest=" + outNest.nest());
        System.out.println("display=" + outNest.nest().display());
    } 
    
    @OutNest(name="test", nest=@Nest(display=false))    public static void test() {}
}

输出:

name=test, nest=@com.jianshu.annotaion.Nest(display=false)
display=false

下面的例子中会用到嵌套,会有更深的体会.

例子2:根据实例生成数据库语句

先定义一个共有的注解Constraints

import java.lang.annotation.*;@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface Constraints {    boolean primaryKey() default false;    boolean allowNull()  default true;    boolean unique()     default false;
}

再定义一系列用于标注属性的注解SQLBoolean,SQLInteger,SQLCharacter,SQLString

import java.lang.annotation.*;@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface SQLBoolean {    String name() default "";    Constraints constraints() default @Constraints;
}
import java.lang.annotation.*;@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface SQLInteger {    String name() default "";    Constraints constraints() default @Constraints;
}
@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface SQLCharacter {    String name() default "";    int value() default 0;    Constraints constraints() default @Constraints;
}
import java.lang.annotation.*;@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface SQLString {    String name() default "";    int value() default 0;    Constraints constraints() default @Constraints;
}

再定义一个设置数据表的注解DBTable

import java.lang.annotation.*;@Target(ElementType.TYPE)   //因为要放到类上@Retention(RetentionPolicy.RUNTIME)public @interface DBTable {    String name();
}

定义一下User类并且在各个成员变量中加入合适的注解

@DBTable(name="user")
public class User {
    @SQLString(30) String username;
    @SQLString(value=20,
            constraints=@Constraints(allowNull=false)) 
            String password;
    @SQLInteger int age;
    @SQLCharacter(value=15,
            constraints=@Constraints(primaryKey=true))
            String handle;
    @SQLBoolean(name="VIP") boolean isVIP;
}

最后加上用于生成SQL语句的类CreateSQL

import java.lang.annotation.Annotation;import java.lang.reflect.Field;public class CreateSQL {    public static void main(String[] args) {
        System.out.println(createSQL(User.class));
    }    
    public static String createSQL(Class<?> clazz) {
        StringBuffer sbuffer = new StringBuffer();
        DBTable table = clazz.getAnnotation(DBTable.class);        if (table == null) return null;
        sbuffer.append("create table " + table.name() + "(\n");
        Field[] fields = clazz.getDeclaredFields();        for (Field f : fields) {
            String columnName = f.getName();
            Annotation anno = f.getAnnotations()[0];            if (anno instanceof SQLString) {
                sbuffer.append("   " + sqlStr((SQLString)anno, columnName) + "\n");
            } else if (anno instanceof SQLInteger) {
                sbuffer.append("   " + sqlInt((SQLInteger)anno, columnName) + "\n");
            } else if (anno instanceof SQLBoolean) {
                sbuffer.append("   " + sqlBool((SQLBoolean)anno, columnName) + "\n");
            } else if (anno instanceof SQLCharacter) {
                sbuffer.append("   " + sqlCharacter((SQLCharacter)anno, columnName) + "\n");
            }
        }        return sbuffer.append(");").toString();
    }    
    public static String sqlCharacter(SQLCharacter anno, String columnName) {        if (anno.name().length() > 0) columnName = anno.name();        return columnName + " Character(" + anno.value() + ")" + getConstraints(anno.constraints());
    }    
    public static String sqlBool(SQLBoolean anno, String columnName) {        if (anno.name().length() > 0) columnName = anno.name();        return columnName + " Boolean" + getConstraints(anno.constraints());
    }    
    public static String sqlInt(SQLInteger anno, String columnName) {        if (anno.name().length() > 0) columnName = anno.name();        return columnName + " Integer" + getConstraints(anno.constraints());
    }    
    public static String sqlStr(SQLString anno, String columnName) {        if (anno.name().length() > 0) columnName = anno.name();        return columnName + " VARCHAR(" + anno.value() + ")" + getConstraints(anno.constraints());
    }    
    public static String getConstraints(Constraints cons) {
        String str = "";        if (cons.primaryKey()) str += " PRIMARYKEY";        if (!cons.allowNull()) str += " NOT NULL";        if (cons.unique())     str += " UNIQUE";        return str;
    }

}

结果输出:

create table user(
   username VARCHAR(30)
   password VARCHAR(20) NOT NULL
   age Integer
   handle Character(15) PRIMARYKEY
   VIP Boolean
);

总结

今天主要是通过几个例子来了解一下关于注解在运行时期通过反射机制是如何工作的,后续博客还会有关于注解与类之间的关系是如何的,以及在其他的一些时期(比如编译期)是如何工作.

参考: Java编程思想第四版



作者:选择_9820
链接:https://www.jianshu.com/p/f2c2228cfd2f

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP