Java注解详解

一、介绍

1 概念

注解(Annotation),也叫元数据。一种代码级别的说明。它是Java 5新增的技术。要区别注释,注解是代码里的一种特殊标记,可在编译前、编译后、运行时等不同的时期被读取,并作出相应的处理。

2 原则

由于注解的代码是附属信息,它要遵循一个基本原则:注解不能直接干扰代码的运行,无论增加或者删除注解,代码都能正常的运行。

二、如何定义注解

1 创建

定义新的注解使用@interface关键字

public @interface MyAnnotation {
}

1.1 成员变量

Annotation只有成员变量。其成员变量以“无形参的方法”形式来声明。例:

public @interface MyAnnotation {
     //String定义了成员类型,name定义了成员名
    String name();
    //也可以在定义成员变量时指定默认值
    int age() default 18;
}

该注解如何使用呢?

class AnnoTest{
    //age已经为其指定默认值,那么在使用的时候就可以不为它指定值,age就会使用这个指定的默认值
    @MyAnnotation(name = "张三")
    public void test() {
    }
}

若是Annotation只有一个成员变量,定义是该成员名必须为value

public @interface MyAnnotation {
    String value();
}

该注解使用时也可以省略成员名和“=”号,例:

class AnnoTest{
    @MyAnnotation( "张三")
    public void test() {
    }
}

根据Annotation包含的成员数目可以将其分为两类:

  1. 标记注解:没有成员变量,如@Override
  2. 元数据注解:包含成员变量的注解,如@SuppressWarnings

1.2 元注解

我们自定义注解时可以使用jdk自带的4个元注解来修饰定义的注解

  1. @Retention
  2. @Target
  3. @Documented
  4. @Inherited

1.2.1 @Retention

作用:设置注解保留的截止时间

public @interface Retention {
	//保留策略,该变量是个枚举类型
    RetentionPolicy value();
}
public enum RetentionPolicy {
    //只保留在源代码中,编译后丢失
    SOURCE,
    //保留在源代码和class文件中,程序运行运行时丢失
    CLASS,
    //保留在源代码、class文件中,程序运行时jvm依然会保留该注解信息,可以通过反射回去该注解的信息
    RUNTIME
}

例:

//该注解在程序运行时可以利用反射获取value值
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default  "";
}

1.2.2 @Target

作用:指定该注解可以修饰哪些元素

public @interface Target {
    //指定注解修饰的元素类型,该成员是个枚举数组,一次可以指定多个可修饰元素
    ElementType[] value();
}
public enum ElementType {
    /** 能修饰类、接口或枚举类型 */
    TYPE,

    /** 能修饰成员变量 */
    FIELD,

    /** 能修饰方法 */
    METHOD,

    /** 能修饰参数 */
    PARAMETER,

    /** 能修饰构造器 */
    CONSTRUCTOR,

    /** 能修饰局部变量 */
    LOCAL_VARIABLE,

    /** 能修饰注解 */
    ANNOTATION_TYPE,

    /** 能修饰包 */
    PACKAGE,

    /**
     * 可以用在 Type 的声明式前
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     *可以用在所有使用 Type 的地方(如:泛型,类型转换等)
     * @since 1.8
     */
    TYPE_USE
}

示例1:单个ElementType

@Target(ElementType.FIELD)
public @interface MyAnnotation {
    String value() default  "";
}

示例2:多个ElementType

@Target({ElementType.FIELD,ElementType.METHOD})
public @interface MyAnnotation {
    String value() default  "";
}

1.2.3 @Documented

使用了@Documented修饰自定义注解A,在使用javadoc命令生成API文档后,所有使用A注解修饰的程序元素,将会包含注解A的说明。

1.2.4 @Inherited

使用了@Inherited修饰注解可以被继承。

三、注解的提取

Java注解可以修饰类、类属性、类方法、类构造器等等,这些属性被注解修饰后不会自动的生效,需要我们把这些信息提取出来,加上处理代码,下面列举一些注解的一些常见的使用场景。

1 修饰类

1.1 定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Entity{
    String name() default "";
    String description() default "这里可以对表模型进行一些描述";
}

1.2 使用注解

@Entity(name = "user")
class User{
}

1.3 提取注解信息

public class AnnoTest {
    public static void main(String[] args) {
        //判断User类是否被@Entity注解修饰
        boolean present = User.class.isAnnotationPresent(Entity.class);
        if (present) {
            Entity annotation = User.class.getAnnotation(Entity.class);
            System.out.println("表名:"+annotation.name()+"
"+"描述:"+annotation.description());
        }
    }
}

2 修饰类属性

2.1 定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Field{
    String name() default  "";
    boolean exist() default true;
}

2.2 使用注解

class User{
    @Field(name = "username")
    private String username;
}

2.3 提取注解信息

public class AnnoTest {
    public static void main(String[] args) throws NoSuchFieldException {
        Class<User> c = User.class;
        //根据属性名获取类属性
        java.lang.reflect.Field field = c.getDeclaredField("username");
        Field anno = field.getAnnotation(Field.class);
        if (anno != null) {
            System.out.println("表字段名:"+anno.name());
            System.out.println("是否存在该字段:"+anno.exist());
        }
    }
}

3 修饰类方法

3.1 定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Log{
    String user() default "";
    String operation() default "he";
}

3.2 使用注解

class User{
    @Log(user = "张三",operation = "删除文章")
    public void deleteArticle() {
    }
}

3.3 提取注解信息

public class AnnoTest {
    public static void main(String[] args) throws NoSuchFieldException {
        Class<User> c = User.class;
        //获取该类所有方法
        Method[] methods = c.getDeclaredMethods();
        if (methods.length>0) {
            for (Method m:methods) {
                if (m.getName().equals("deleteArticle")) {
                    Log anno = m.getDeclaredAnnotation(Log.class);
                    if (anno != null) {
                        StringBuffer sb = new StringBuffer();
                        LocalDate now = LocalDate.now();
                        sb.append(anno.user()).append("于").append(now.toString()).append(",调用了方法“"+m.getName()+"”,进行了"+anno.operation()+"操作!");
                        System.out.println(sb.toString());
                    }
                }
            }
        }
    }
}

四、注解的本质

1 新建一个注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log{
    @Deprecated
    String user() default "";
    String operation() default "喝茶";
}

2 编译注解

打开dos命令框,在该文件目录下输入命令

javac Log.java

得到Log.class文件

3 反编译log.class文件

输入命令

javap -verbose -c Log.class > Log.txt

得到Log.txt字节码文件:
@Log字节码信息说明
总结

  1. 从反编译的信息里可以知道,注解就是一个继承java.lang.annotation.Annotation接口的接口。
  2. 注解的成员变量会被编译为同名的抽象方法,成员类型就是方法返回值类型,通过调用该注解对象的方法就能得到该方法对应的成员变量值。
只有把命运掌握在自己手中,从今天起开始努力,即使暂时看不到希望,也要相信自己。因为比你牛几倍的人,依然在努力。
原文地址:https://www.cnblogs.com/freesky168/p/14358233.html