关于后端:Java注解有哪些

47次阅读

共计 8552 个字符,预计需要花费 22 分钟才能阅读完成。

根本语法

注解是 Java 5 所引入的泛滥语言变动之一,是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和应用,起到阐明、配置的性能。注解不会也不能影响代码的理论逻辑,仅仅起到辅助性的作用,蕴含在 java.lang.annotation 包中

注解的语法非常简略,只有在现有语法中增加 @ 符号即可,java.lang 包提供了如下五种注解:

  • @Override

    示意以后的办法定义将笼罩基类的办法,如果你不小心把办法签名拼错了,编译器就会收回谬误提醒

  • @Deprecated

    如果应用该注解的元素被调用,编译器就会收回正告信息,示意不激励程序员应用

  • @SuppressWarnings

    敞开不当的编译器正告信息

  • @SafeVarargs

    禁止对具备泛型可变参数的办法或构造函数的调用方收回正告

  • @FunctionalInterface

    申明接口类型为函数式接口

    《2020 最新 Java 根底精讲视频教程和学习路线!》

定义注解

注解的定义看起来和接口的定义很像,事实上它们和其余 Java 接口一样,也会被编译成 class 文件

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {}
复制代码 

除开 @ 符号,@Test 的定义看起来更像一个空接口。注解的定义也须要一些元注解,元注解用于注解其余的注解

注解

解释

@Target

示意注解能够用于哪些地方。可能的 ElementType 参数包含:
CONSTRUCTOR:结构器的申明
FIELD:字段申明(包含 enum 实例)
LOCAL_VARIABLE:局部变量申明
METHOD:办法申明
PACKAGE:包申明
PARAMETER:参数申明
TYPE:类、接口(包含注解类型)或者 enum 申明

@Retention

示意注解信息保留的时长。可选的 RetentionPolicy 参数包含:
SOURCE:注解将被编译器抛弃
CLASS:注解在 class 文件中可用,然而会被 VM 抛弃
RUNTIME:VM 将在运行期也保留注解,因而能够通过反射机制读取注解的信息

@Documented

将此注解保留在 Javadoc 中

@Inherited

容许子类继承父类的注解

@Repeatable

容许一个注解能够被应用一次或者屡次(Java8)

不蕴含任何元素的注解称为标记注解,上例中的 @Test 就是标记注解。注解通常也会蕴含一些示意特定值的元素,当剖析解决注解的时候,程序能够利用这些值。注解的元素看起来就像接口的办法,但能够为其指定默认值

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {int id();
    String description() default "no description";}

....

public class TestUtils {
    // 在办法上应用注解 @TestAnnotation
    @UseCase(id = 47, description = "description")
    public void test() {...}
}
复制代码 

注解元素可用的类型如下所示,如果应用了其余类型,编译器就会报错:

  • 所有根本类型(int、float、boolean 等)
  • String
  • Class
  • enum
  • Annotation
  • 以上类型的数组

如果没有给出 description 的值,在剖析解决这个类的时候会应用该元素的默认值。元素的默认值不能有不确定的值,也就是说,元素要么有默认值,要么就在应用注解时提供元素的值

这里还有另外一个限度:任何非根本类型的元素,无论是在源代码申明时还是在注解接口中定义默认值时,都不能应用 null 作为值。如果咱们心愿体现一个元素的存在或者缺失的状态,能够自定义一些非凡的值,比方空字符串或者正数用于表白某个元素不存在

注解不反对继承,你不能应用 extends 关键字来继承 @interface

注解处理器

如果没有用于读取注解的工具,那么注解不会比正文更有用。应用注解中一个很重要的作用就是创立与应用注解处理器。Java 拓展了反射机制的 API 用于帮忙你发明这类工具。同时他还提供了 javac 编译器钩子在编译时应用注解

上面是一个非常简单的注解处理器,咱们用它来读取被注解的 TestUtils 类,并且应用反射机制来寻找 @TestAnnotation 标记

public class TestAnnotationTracker {public static void trackTestAnnotation(Class<?> cl) {for(Method m : cl.getDeclaredMethods()) {TestAnnotation ta = m.getAnnotation(TestAnnotation.class);
            if(ta != null) {System.out.println(ta.id() + "n" + ta.description());
            }
        }
    }
    
    public static void main(String[] args) {trackTestAnnotation(TestUtils.class);
    }
}
复制代码 

这里用到了两个反射的办法:getDeclaredMethods() 和 getAnnotation(),getAnnotation() 办法返回指定类型的注解对象,在本例中就是 TestAnnotation,如果被注解的办法上没有该类型的注解,返回值就为 null。通过调用 id() 和 description() 办法来提取元素值

应用注解实现对象 – 数据库映射

当有些框架须要一些额定的信息能力与你的源代码协同工作,这种状况下注解就会变得非常有用。自定义例如对象 / 关系映射工具(Hibernate 和 MyBatis)通常都须要 XML 形容文件,而这些文件脱离于代码之外。除了定义 Java 类,程序员还必须反复的提供某些信息,而例如类名和包名等信息曾经在原始类中提供过了,常常会导致代码和形容文件的同步问题

假如你当初想提供一些根本的对象 / 关系映射性能,可能主动生成数据库表。你能够应用 XML 形容文件来指明类的名字、每个成员以及数据库映射的相干信息。然而,通过应用注解,你能够把所有信息都保留在 JavaBean 源文件中。为此你须要一些用于定义数据库表名称、数据库列以及将 SQL 类型映射到属性的注解

首先创立一个用来映射数据库表的注解,用来润饰类、接口(包含注解类型)或者 enum 申明

@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 true;
    boolean unique() default false;}
复制代码 
public @interface SQLString {int value() default 0;
    String name() default "";
    Constraints constraints() default @Constraints;}
复制代码 
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {String name() default "";
    Constraints constraints() default @Constraints;}
复制代码 

@Constraints 代表了数据库通常提供的束缚的一小部分,primaryKey(),allowNull() 和 unique() 元素都提供了默认值,大多数状况下,注解的使用者都不须要输出太多货色

另外两个 @interface 定义的是 SQL 类型。如果心愿这个框架更有价值的话,咱们应该为每个 SQL 类型都定义相应的注解。不过作为示例,两个元素足够了。这些 SQL 类型具备 name() 元素和 constraints() 元素。后者利用了嵌套注解的性能,将数据库列的类型束缚信息嵌入其中。留神 constraints() 元素的默认值是 @Constraints,没有在括号中指明 @Constraints 元素的值,因而,constraints() 的默认值为所有元素都为默认值。如果要使得嵌入的 @Constraints 注解中的 unique() 元素为 true,并作为 constraints() 元素的默认值,你能够像如下定义:

public @interface Uniqueness {Constraints constraints() default @Constraints(unique = true);
}
复制代码 

上面是一个简略的,应用了如上注解的类

@DBTable(name = "MEMBER")
public class Member {@SQLString(30)
    String firstName;
    @SQLString(50)
    String lastName;
    @SQLInteger
    Integer age;
    @SQLString(value = 30, constraints = @Constraints(primaryKey = true))
    String reference;
    static int memberCount;
    public String getReference() { return reference;}
    public String getFirstName() { return firstName;}
    public String getLastName() { return lastName;}
    @Override
    public String toString() { return reference;}
    public Integer getAge() { return age;}
}
复制代码 

类注解 @DBTable 注解给定了元素值 MEMBER,它将会作为表的名字。类的属性 firstName 和 lastName 都被注解为 @SQLString 类型并且给了默认元素值别离为 30 和 50,并在嵌入的 @Constraint 注解中设定 primaryKey 元素的值

上面是一个注解处理器的例子,它将读取一个类文件,查看下面的数据库注解,并生成用于创立数据库的 SQL 命令:

public class TableCreator {public static void generateSql(String[] classnames) throws Exception {for (String className : classnames) {Class<?> cl = Class.forName(className);
            DBTable dbTable = cl.getAnnotation(DBTable.class);
            String tableName = dbTable.name();
            // 如果表名为空字符串,则应用类名
            if (tableName.length() < 1) {tableName = cl.getName().toUpperCase();}
            List<String> columnDefs = new ArrayList<>();
            for (Field field : cl.getDeclaredFields()) {
                String columnName = null;
                Annotation[] anns = field.getDeclaredAnnotations();
                // 该属性不是列
                if (anns.length < 1) {continue;}
                // 解决整数类型
                if (anns[0] instanceof SQLInteger) {SQLInteger sInt = (SQLInteger) anns[0];
                    // 如果列名为空字符串,则应用属性名
                    if (sInt.name().length() < 1) {columnName = field.getName().toUpperCase();} else {columnName = sInt.name();
                    }
                    columnDefs.add(columnName + "INT" + getConstraints(sInt.constraints()));
                }
                // 解决字符串类型
                if (anns[0] instanceof SQLString) {SQLString sString = (SQLString) anns[0];
                    if (sString.name().length() < 1) {columnName = field.getName().toUpperCase();} else {columnName = sString.name();
                    }
                    columnDefs.add(columnName + "VARCHAR(" + sString.value() + ")" +
                            getConstraints(sString.constraints()));
                }
                // 结构并输入 sql 字符串
                StringBuilder createCommand = new StringBuilder("CREATE TABLE" + tableName + "(");
                for (String columnDef : columnDefs) {createCommand.append("n" + columnDef + ",");
                }
                String tableCreate = createCommand.substring(0, createCommand.length() - 1) + ");";
                System.out.println("Table Creation SQL for" + className + "is:n" + tableCreate);
            }
        }
    }

    private static String getConstraints(Constraints con) {
        String constraints = "";
        if (!con.allowNull())
            constraints += "NOT NULL";
        if (con.primaryKey())
            constraints += "PRIMARY KEY";
        if (con.unique())
            constraints += "UNIQUE";
        return constraints;
    }
}
复制代码 

编译时注解解决

当 @Retention 的 RetentionPolicy 参数被标注为 SOURCE 或 CLASS,此时你无奈通过反射去获取注解信息,因为注解在运行期是不存在的。应用 javac 能够创立编译时注解处理器,在编译时扫描和解决注解。你能够自定义注解,并注册到对应的注解处理器。注解处理器能够生成 Java 代码,这些生成的 Java 代码会组成新的 Java 源文件,但不能批改曾经存在的 Java 类,例如向已有的类中增加办法。如果你的注解处理器创立了新的源文件,在新一轮解决中注解处理器会查看源文件自身,在检测一轮之后继续循环,直到不再有新的源文件产生,而后编译所有的源文件

咱们来编写一个简略的注解处理器,如下是注解的定义

@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.METHOD,
        ElementType.CONSTRUCTOR,
        ElementType.ANNOTATION_TYPE,
        ElementType.PACKAGE, ElementType.FIELD,
        ElementType.LOCAL_VARIABLE})
public @interface Simple {String value() default "-default-";
}
复制代码 

@Retention 的参数为 SOURCE,这意味着注解不会存留在编译后的 class 文件,因为这对应编译时解决注解是没有必要的,在这里,javac 是惟一有机会解决注解的形式

package annotations.simplest;
@Simple
public class SimpleTest {
    @Simple
    int i;
    @Simple
    public SimpleTest() {}
    @Simple
    public void foo() {System.out.println("SimpleTest.foo()");
    }
    @Simple
    public void bar(String s, int i, float f) {System.out.println("SimpleTest.bar()");
    }
    @Simple
    public static void main(String[] args) {
        @Simple
        SimpleTest st = new SimpleTest();
        st.foo();}
}

复制代码 

运行 main 办法,程序就会开始编译,如下是一个简略的处理器,作用就是把注解相干的信息打印进去

package annotations.simplest;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import java.util.*;
@SupportedAnnotationTypes("annotations.simplest.Simple")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class SimpleProcessor extends AbstractProcessor {
    
    @Override
    public boolean process(Set<? extends TypeElement> annotations,
        RoundEnvironment env) {for(TypeElement t : annotations) {System.out.println(t);
        }
        for(Element el : env.getElementsAnnotatedWith(Simple.class)) {display(el);
        }
        return false;
    }
    
    private void display(Element el) {System.out.println("====" + el + "====");
        System.out.println(el.getKind() +    // 返回此元素的品种,字段,办法,或是类
                ":" + el.getModifiers() +    // 返回此元素的修饰符
                ":" + el.getSimpleName() +    // 返回此元素的简略名称
                ":" + el.asType());    // 返回此元素定义的类型
        // 如果元素为 CLASS 类型,动静向下转型为更具体的元素类型,并打印相干信息
        if(el.getKind().equals(ElementKind.CLASS)) {TypeElement te = (TypeElement)el;
            System.out.println(te.getQualifiedName());
            System.out.println(te.getSuperclass());
            System.out.println(te.getEnclosedElements());
        }
        // 如果元素为 METHOD 类型,动静向下转型为更具体的元素类型,并打印相干信息
        if(el.getKind().equals(ElementKind.METHOD)) {ExecutableElement ex = (ExecutableElement)el;
            System.out.print(ex.getReturnType() + " ");
            System.out.print(ex.getSimpleName() + "(");
            System.out.println(ex.getParameters() + ")");
        }
    }
}
复制代码 

应用 @SupportedAnnotationTypes 和 @SupportedSourceVersion 注解来确定反对哪些注解以及反对的 Java 版本

注解处理器须要继承抽象类 javax.annotation.processing.AbstractProcessor,惟一须要实现的办法就是 process(),这里是所有行为产生的中央。第一个参数获取到此注解处理器所要解决的注解汇合,第二个参数保留了残余信息,这里咱们所做的事件只是打印了注解(只存在一个)。process() 中实现的第二个操作是循环所有被 @Simple 注解的元素,并且针对每一个元素调用 display() 办法。展现所有 Element 本身的根本信息

原文链接:https://juejin.cn/post/689930…

正文完
 0