美文网首页
Java注解之深入浅出

Java注解之深入浅出

作者: Kris_Ni | 来源:发表于2019-04-17 15:53 被阅读0次

官方文档定义:注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。

简单来说,你可以理解为一种标签,例如一提到自己心中的女神,脑海中自然会为她贴上"beautiful"这个标签,但是你的这个"beautiful"标签并不属于她本身,你的这个标签对她来说没有直接影响,你只是条单纯的舔狗而已......

好吧,回到正题,注解存在的意义是什么呢?
注解的用处主要如下:

  • 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
  • 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
  • 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取

还是回到官方文档的解释上,注解主要针对的是编译器和其它工具软件(SoftWare tool)。
当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。
所以呢,注解是给编译器或APT用的

例如:

public @interface AnnotationTest() {
}

@AnnotationTest
public void Test() {
}

上述代码简单来说就是把AnnotationTest作为一个标签贴到Test类上面去了
但是实际上并没有什么卵用,因为根本没有实现注解功能

想要注解能够正常工作,还是要依赖于元注解这个东西
元注解是一种基本注解,可以注解到其他的注解上面去
主要有@Retention@Document@Target@Inherited@Repeatable5种

@Retention

当它应用到一个注解上的时候,说明了这个注解的存活时间
取值如下:

  • RetentionPolicy.SOURCE: 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
  • RetentionPolicy.CLASS: 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
  • RetentionPolicy.RUNTIME: 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
@Retention(RetentionPolicy.CLASS)
public @interface AnnotationTest() {
}

@Document

将被标注的注解生成到javadoc中

@Target

用于指定被此元注解标注的注解可以标注的程序元素
可以看一下@Target的源码

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

可以看到有一个value属性,返回一个枚举ElementType类型的数组,这个数组的值就代表了可以使用的程序元素。

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,
    /** Field declaration (includes enum constants) */
    FIELD,
    /** Method declaration */
    METHOD,
    /** Formal parameter declaration */
    PARAMETER,
    /** Constructor declaration */
    CONSTRUCTOR,
    /** Local variable declaration */
    LOCAL_VARIABLE,
    /** Annotation type declaration */
    ANNOTATION_TYPE,
    /** Package declaration */
    PACKAGE,
    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,
    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

@Target取值如下

  • ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
  • ElementType.FIELD 可以给属性进行注解
  • ElementType.METHOD 可以给方法进行注解
  • ElementType.PARAMETER 可以给一个方法内的参数进行注解
  • ElementType.CONSTRUCTOR 可以给构造方法进行注解
  • ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
  • ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
  • ElementType.PACKAGE 可以给一个包进行注解
  • ElementType.TYPE_PARAMETER标明注解可以用于类型参数声明(1.8新加入)
  • ElementType.TYPE_USE类型使用声明(1.8新加入),可以标注除class之外的任意类型
    当注解未指定Target值时,则此注解可以用于任何元素之上,多个值使用{}包含并用逗号隔开
@Target(value = {ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.TYPE_PARAMETER})

@Inherited

其让被修饰的注解拥有被继承的能力,但是它并不是说注解本身可以继承,而是说如果一个父类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了父类的注解。

@Documented
@Retention(RetentionPolicy.CLASS)
@Target(value = {ElementType.TYPE})
@Inherited
public @interface TestAnnotation {}

@TestAnnotation
public class A {}

public class B extends A{}

也就是说B也拥有TestAnnotation 这个注解

@Repeatable

表示注解可以多次应用,通常是注解的值可以取多个
一个很简单的例子:我是一个程序员,同时也是美食家和摄影师

@interface Live {
    People[] value();
}

@Repeatable(Live.class)
@interface People {
    String role() default "";
}

@People(role = "programmer")
@People(role = "gastronome")
@People(role = "photographer")
public class Me {
}

上述代码通过@Repeatable注解了People ,而@Repeatable 后面括号中的Live类相当于一个容器注解(A fucking new concept)

容器注解:就是用来存放其它注解的地方,它本身也是一个注解

按java规定,容器注解里面必须要有一个value属性,属性类型是一个被 @Repeatable 注解过的注解数组

注解的属性也叫成员变量,注解只有成员变量,没有方法,而且成员变量在注解的定义中以“无形参的方法”的形式来声明,方法名定义了成员变量的名字,返回值定义了成员变量的类型。

在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组

注解中属性可以有默认值,默认值需要用 default 关键值指定,这样做的好处是可以省略赋值操作

@People()
public class Me {}

如果成员变量仅有一个名字叫做value的属性时,你甚至可以这样写

@People("programmer")
public class Me {}

如果没有成员变量的话,括号都可以省略哟!

@People
public class Me {}
Java语言本身也提供了预置的注解

@Deprecated
这个元素是用来标记过时的元素,会有一条横线,这是编译器识别后的提醒效果
@SuppressWarnings
阻止警告的意思。之前说过调用被 @Deprecated 注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,他们可以在调用的地方通过 @SuppressWarnings 达到目的。
@SafeVarargs
参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。它是在 Java 1.7 的版本中加入的。
@FunctionalInterface
函数式接口注解,这个是 Java 1.8 版本引入的新特性。函数式编程很火,所以 Java 8 也及时添加了这个特性。
函数式接口 (Functional Interface) 就是一个具有一个方法的普通接口。
PS:函数式接口标记有什么用,这个原因是函数式接口可以很容易转换为 Lambda 表达式

想必上面的理论知识都能很快理解吧,接下来就手撕代码吧

注解只是给我们想要的东西去贴上一个标签,那么怎么去阅读标签呢?
注解通过反射获取
查看一下Class.java源码,可以找到isAnnotationPresent()这个方法,用来判断它是否应用了某个注解

    /**
     * {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     * @since 1.5
     */
    @Override
    public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return GenericDeclaration.super.isAnnotationPresent(annotationClass);
    }

上面代码可以看出Annotation接口是所有注解的父接口
此外,可以通过getAnnotation(Class<A> annotationClass())获取指定类型的Annotation对象

    /**
     * @throws NullPointerException {@inheritDoc}
     * @since 1.5
     */
    @SuppressWarnings("unchecked")
    public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
        Objects.requireNonNull(annotationClass);

        return (A) annotationData().annotations.get(annotationClass);
    }

getAnnotations()可以获取到这个元素上的所有类型的Annotation对象

    /**
     * @since 1.5
     */
    public Annotation[] getAnnotations() {
        return AnnotationParser.toArray(annotationData().annotations);
    }

getDeclaredAnnotation(Class<A> annotationClass)返回该元素上存在的直接修饰该元素的指定类型的注解,如果不存在则返回null.

    /**
     * @throws NullPointerException {@inheritDoc}
     * @since 1.8
     */
    @Override
    @SuppressWarnings("unchecked")
    public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass) {
        Objects.requireNonNull(annotationClass);

        return (A) annotationData().declaredAnnotations.get(annotationClass);
    }

getDeclaredAnnotations()返回该元素上存在的直接修饰该元素的所有注解

    /**
     * @since 1.5
     */
    public Annotation[] getDeclaredAnnotations()  {
        return AnnotationParser.toArray(annotationData().declaredAnnotations);
    }

okay,枯燥无聊的源码还有很多,例如getDeclaredAnnotationsByType()、getAnnotationsByType()等等,接下来用一个简单的例子通过反射来获取Annotation的属性
首先,定义一个注解

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    public int id() default 1;
    public String name() default "Kris";
}

然后呢

@TestAnnotation(id = 666,name = "Kris")
public class Me {
    public static void main(String[] args) {
        boolean hasAnnotation = Me.class.isAnnotationPresent(TestAnnotation.class);
        if (hasAnnotation) {
            TestAnnotation testAnnotation = Me.class.getAnnotation(TestAnnotation.class);
            System.out.println("id:"+testAnnotation.id());
            System.out.println("name:"+testAnnotation.name());
        }
    }
}

okay,接下来执行这个程序就可以发现:
Amazing!!!


那么,继续加把力呗!
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface A {
    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface B {
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface C {
}

@TestAnnotation(id = 666,name = "Kris")
public class Me {

    @A(value = "This is A!")
    int a;

    @B
    @C
    public void methodTest() {
    }

    public static void main(String[] args) {
        boolean hasAnnotation = Me.class.isAnnotationPresent(TestAnnotation.class);
        if (hasAnnotation) {
            TestAnnotation testAnnotation = Me.class.getAnnotation(TestAnnotation.class);
            System.out.println("id:"+testAnnotation.id());
            System.out.println("name:"+testAnnotation.name());
        }
        try {
            Field a = Me.class.getDeclaredField("a");
            a.setAccessible(true);
            A annotation = a.getAnnotation(A.class);
            if (annotation != null) {
                System.out.println("A value:"+annotation.value());
            }
            Method methodTest = Me.class.getDeclaredMethod("methodTest");
            if (methodTest != null) {
                Annotation[] ans = methodTest.getAnnotations();
                for (int i=0;i<ans.length;i++) {
                    System.out.println("methodTest Annotation:"+ans[i].annotationType().getSimpleName());
                }
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

显而易见的得到结果:


综上所述,只要通过给元素添加注解,再利用反射就可以获取注解啦,但是获取到有什么用呢?下面就通过数据库SQL创建语句实例来讲解一下APT(Annotation Process Tool)吧。

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

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
    boolean primaryKey() default false;
    boolean allowNull() default false;
    boolean unique() default false;
}

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

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

@DBTable(name = "MEMBER")
public class Member {

    @SQLString(name = "ID",value = 50,constraint = @Constraints(primaryKey = true))
    private int id;

    @SQLString(name = "NAME",value = 30)
    private String name;

    @SQLInteger(name = "AGE")
    private int age;

    @SQLString(name = "DESCRIPTION",value = 150,constraint = @Constraints(allowNull = true))
    private String description;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    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 class TableCreator {
    public static void main(String[] args) {
        String[] arg={"AnnotationTest.SQLAnnotation.Member"};
        for(String className : arg) {
            System.out.println("Table Creation SQL for " +
                    className + " is :\n" + createTableSql(className));
        }
    }

    private static String createTableSql(String className) {
        Class<?> cl = null;
        DBTable dbTable = null;
        try {
            cl = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        if (cl != null) {
            dbTable = cl.getAnnotation(DBTable.class);
        }
        if (dbTable == null) {
            System.out.println("No DBTable Annotations int class!");
            return null;
        }
        String tableName = dbTable.name();

        if (tableName.length() == 0) {
            tableName = cl.getName().toUpperCase();
        }
        List<String> columnDefs = new ArrayList<>();
        for (Field field : cl.getDeclaredFields()) {
            String columnName = null;
            if (field.isAnnotationPresent(SQLInteger.class)) {
                SQLInteger sqlInteger = (SQLInteger) field.getDeclaredAnnotation(SQLInteger.class);
                if (sqlInteger.name().length() == 0) {
                    columnName = field.getName().toUpperCase();
                } else {
                    columnName = sqlInteger.name();
                }
                columnDefs.add(columnName + " INT" + getConstraints(sqlInteger.constraint()));
            } else if (field.isAnnotationPresent(SQLString.class)) {
                SQLString sqlString = (SQLString) field.getDeclaredAnnotation(SQLString.class);
                if (sqlString.name().length() == 0) {
                    columnName = field.getName().toUpperCase();
                } else {
                    columnName = sqlString.name();
                }
                columnDefs.add(columnName+" VARCHAR(" + sqlString.value() + ")" + getConstraints(sqlString.constraint()));
            }
        }
        StringBuilder createCommand = new StringBuilder("CREATE TABLE " + tableName + "(");
        for (String columnDef : columnDefs) {
            createCommand.append("\n " + columnDef + ",");
        }
        return createCommand.substring(0,createCommand.length()-1)+");";
    }

    private static String getConstraints(Constraints con) {
        StringBuilder constraints = new StringBuilder();
        if (!con.allowNull()) {
            constraints.append(" NOT NULL");
        }
        if (con.primaryKey()) {
            constraints.append(" PRIMARY KEY");
        }
        if (con.unique()) {
            constraints.append(" UNIQUE");
        }
        return constraints.toString();
    }
}

OK,这样就可以成功得通过APT得到所需要的SQL语句啦~

相关文章

  • Java注解之深入浅出

    官方文档定义:注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码...

  • Java——注解(Annotation)入门学习

    学习资料: Java编程思想 ——第20章 公共技术点之 Java 注解 Annotation 注解(Annota...

  • Spring注解原理探索(三)

    之 Java如何识别注解 关键词:Java 反射java.lang.reflect 包,实现反射功能的工具类。注解...

  • Spring注解原理探索(一)

    之 Java元注解释义 Question 注解在Java中如何起作用? Spring是如何识别注解? 如何自定义注...

  • Java正则表达式参考

    Java正则表达式入门 java正则表达式应用 深入浅出之正则表达式(一) 深入浅出之正则表达式(二) 正则表达式...

  • 深入浅出Java注解

    一、什么是注解? 注解对于开发人员来讲既熟悉又陌生,熟悉是因为只要你是做开发,都会用到注解(常见的@Overrid...

  • 深入浅出Java注解

    什么是注解 注解对于开发人员来讲既熟悉又陌生,熟悉是因为只要你是做开发,都会用到注解(常见的@Override);...

  • 干货系列之java注解

    干货系列之java注解 前言 java反射和注解在java里面很重要,但是很多人对这方面的知识理解不是很好,我来说...

  • 菜鸟学服务端技术----Spirng基础

    注解 Java基础加强总结(一)——注解(Annotation) java中的注解是如何工作的? java 注解 ...

  • 自定义注解

    java annotation基础 java注解分为标准注解和元注解。 标准注解是java为我们提供的预定义的注解...

网友评论

      本文标题:Java注解之深入浅出

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