《On Java进阶卷》- 笔记-4-注解

51 阅读6分钟

注解是java5的语言更新,可以用来保存和程序有关的额外信息,编译器会校验格式的正确性。

  • 可以生成描述文件
  • 生成新的类定义

java5内建注解

  • @Override 方法重载声明
  • @Deprecated 废弃声明
  • @SuppressWarnings 关闭编译警告

java7、8新增注解

  • @SafeVarargs 泛型作为可变参数的方法时,关闭调用者警告
  • @FunctionInterface 函数式接口声明

4.1 基本语法

4.1.1 定义注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}
  • @Target、@Retention 称为元注解,注解的定义必须有元注解
  • @Target表示当前注解可以用于那些位置
    • TYPE 类、接口上
    • FIELD 字段上
    • METHOD 方法上
    • PARAMETER 方法参数上
    • CONSTRUCTOR 构造函数上
    • LOCAL_VARIABLE 局部变量上
    • ANNOTATION_TYPE 注解上
    • PACKAGE 包上
    • TYPE_PARAMETER 任何声明类型的地方
    • TYPE_USE 任意使用类型的地方
    • MODULE 模块上
    • RECORD_COMPONENT record上
  • @Retention表示当前注解存在周期
    • SOURCE 只存在源代码中,编译后消失
    • CLASS 存在到类文件中,运行时消失
    • RUNTIME 直到运行时,就是一直都存在
  • 没有任何元素的注解,比如上面的@Test,称为标记注解

使用示例

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface UseCase {
    int id();

    String description() default "no description";
}

public class PasswordUtils {
    @UseCase(id = 47, description = "Passwords must contain at least one numeric")
    public boolean validatePassword(String password) {
        return true;
    }

    @UseCase(id = 48)
    public String encryptPassword(String password) {
        return "encrypted";
    }
}

4.1.2 元注解

@Target、@Retention、@Documented、@Inherited、@Repeatable

  • @Documented javadoc中使用
  • @Inherited 允许子类继承父注解
  • @Repeatable 可以多次应用同一个声明(java8)

4.2 编写注解处理器

使用注解处理器

  • getAnnotation 在x上获取指定的注解声明,没有就返回null
public class UseCaseTracker {
    public static void trackUseCases(List<Integer> useCases, Class<?> cl) {
        for (Method m : cl.getDeclaredMethods()) {
            UseCase uc = m.getAnnotation(UseCase.class);
            if (uc != null) {
                System.out.println("Found Use Case:" + uc.id() + " " + uc.description());
                useCases.remove(Integer.valueOf(uc.id()));
            }
        }
        useCases.forEach(i -> System.out.println("Warning: Missing use case-" + i));
    }

    public static void main(String[] args) {
        List<Integer> useCases = IntStream.range(47, 51).boxed().collect(Collectors.toList());
        trackUseCases(useCases, PasswordUtils.class);
        /*
        Found Use Case:47 Passwords must contain at least one numeric
        Found Use Case:48 no description
        Warning: Missing use case-49
        Warning: Missing use case-50
        */
    }
}

4.2.1 注解元素

注解中声明的方法,只允许下面的类型

  • 基本类型(int、float、boolean等)
  • String
  • Class
  • enum 枚举
  • Annotation
  • 上面类型组合的数组

4.2.2 默认值的限制

注解的元素需要值,要么声明有默认值,要么使用时有值

  • 注解中定义默认值时,非基本元素类型不能赋值为null
  • 技巧:可以使用一些特殊值来表示
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SimulatingNull {
    int id() default -1;

    String description() default "";
}

4.2.3 生成外部文件

使用注解描述SQL表

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

    boolean allowNull() default true;

    boolean unique() default false;
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface SQLString {
    int value() default 0;

    String name() default "";

    Constraints constraints() default @Constraints;
}

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

    Constraints constraints() default @Constraints;
}

//嵌套注解
@interface Uniqueness {
    Constraints constraints() default @Constraints(unique = true);
}

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

使用注解定义类文件

@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;
    }
}

4.2.5 实现处理器

使用注解处理器来处理Member类,生成一个创建表的SQL语句

public class TableCreator {
    public static void main(String[] args) {
        Class<?> cl = Member.class;

        DBTable dbTable = cl.getAnnotation(DBTable.class);
        if (dbTable != null) {
            String tableName = dbTable.name();
            if (tableName.isEmpty()) {
                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().isEmpty()) {
                        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().isEmpty()) {
                        columnName = field.getName().toUpperCase();
                    } else {
                        columnName = sString.name();
                    }
                    columnDefs.add(columnName + " VARCHAR(" + sString.value() + ")" + getConstraints(sString.constraints()));
                }
            }

            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 " + cl.getSimpleName() + " is :\n" + tableCreate);
        }
        /*
        Table Creation SQL for Member is :
        CREATE TABLE MEMBER (
            FIRSTNAME VARCHAR(30),
            LASTNAME VARCHAR(50),
            AGE INT,
            REFERENCE VARCHAR(30) PRIMARY KEY);
       */
    }

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

输出结果为

CREATE TABLE MEMBER (
    FIRSTNAME VARCHAR(30),
    LASTNAME VARCHAR(50),
    AGE INT,
    REFERENCE VARCHAR(30) PRIMARY KEY);

4.3 用javac处理注解

通过javac可以创建编译时注解处理器,并将注解应用于java源文件,而不是类文件。

  • 限制是,注解处理器不能修改源代码,可以创建新的文件
  • 如果创建了新的文件,则在新一轮处理中会检查文件自身的注解,然后继续一轮一轮的持续处理,直到没有新文件,最近编译所有源文件

4.3.1 最简单的处理器

继承AbstractProcessor并实现process方法可以处理包含注解的文件

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

处理注解的处理器代码

package com.edfeff.ch03;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import java.util.Set;

@SupportedAnnotationTypes(value = "com.edfeff.ch03.Simple")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class SimpleProcessor extends AbstractProcessor {
    @Override
    public boolean process(
            /*处理的文件有那些注解*/
            Set<? extends TypeElement> annotations,
            /*环境信息*/
            RoundEnvironment roundEnv) {
        for (TypeElement t : annotations) {
            System.out.println(t);
        }
        /*处理所有Simple注解的元素*/
        for (Element el : roundEnv.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());
        if (el.getKind().equals(ElementKind.CLASS)) {
            TypeElement te = (TypeElement) el;
            System.out.println("QualifiedName: " + te.getQualifiedName());
            System.out.println("Superclass: " + te.getSuperclass());
            System.out.println("EnclosingElement: " + te.getEnclosingElement());
        }
        if (el.getKind().equals(ElementKind.METHOD)) {
            ExecutableElement ex = (ExecutableElement) el;
            System.out.println("ReturnType: " + ex.getReturnType());
            System.out.println("SimpleName: " + ex.getSimpleName());
            System.out.println("Parameters: " + ex.getParameters());
        }
    }
}

运行脚本

"jdk-22\bin\javac" -cp target/classes -processor com.edfeff.ch03.SimpleProcessor src/main/java/com/edfeff/ch03/SimpleTest.java

运行结果

com.edfeff.ch03.Simple
======com.edfeff.ch03.SimpleTest=======
CLASS [public] SimpleTest com.edfeff.ch03.SimpleTest
QualifiedName: com.edfeff.ch03.SimpleTest
Superclass: java.lang.Object
EnclosingElement: com.edfeff.ch03
======i=======
FIELD [] i int
======SimpleTest()=======
CONSTRUCTOR [public] <init> ()void
======foo()=======
METHOD [public] foo ()void
ReturnType: void
SimpleName: foo
Parameters:
======bar()=======
METHOD [public] bar ()void
ReturnType: void
SimpleName: bar
Parameters:
======main(java.lang.String[])=======
METHOD [public, static] main (java.lang.String[])void
ReturnType: void
SimpleName: main
Parameters: args
警告: 来自批注处理程序 'com.edfeff.ch03.SimpleProcessor' 的受支持 source 版本 'RELEASE_8' 低于 -source '22'
1 个警告

4.3.2 更复杂的处理器

注解处理器中不能使用java的反射功能,因此此时操作的是源代码,不是编译后的类,有各种mirror可以查看源代码中的方法、字段和类型。

利用注解处理器生成新的文件,示例,把类中public的方法提取到一个接口中

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface ExtractInterface {
    String interfaceName() default "-!!-";
}

待处理的类

@ExtractInterface(interfaceName = "IMultiplier")
public class Multiplier {
    public boolean flag = false;
    private int n = 0;

    public int multiply(int x, int y) {
        int total = 0;
        for (int i = 0; i < x; i++) {
            total = add(total, y);
        }
        return total;
    }

    public int fortySeven() {
        return 47;
    }

    private int add(int x, int y) {
        return x + y;
    }

    public double timesTen(double x) {
        return x * 10;
    }

    public static void main(String[] args) {
        Multiplier m = new Multiplier();
        System.out.println("11*16=" + m.multiply(11, 16));
    }
}

注解处理器

package com.edfeff.ch03;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.util.Elements;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@SupportedAnnotationTypes("com.edfeff.ch03.ExtractInterface")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class IfaceExtractorProcessor extends AbstractProcessor {
    //    需要处理的方法
    private ArrayList<Element> interfaceMethods = new ArrayList<>();
    Elements elementUtils;
    private ProcessingEnvironment processingEnv;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        this.processingEnv = processingEnv;
        elementUtils = processingEnv.getElementUtils();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        for (Element elem : env.getElementsAnnotatedWith(ExtractInterface.class)) {
            String interfaceName = elem.getAnnotation(ExtractInterface.class).interfaceName();
            for (Element enclosed : elem.getEnclosedElements()) {
                //public 非static方法
                if (enclosed.getKind().equals(ElementKind.METHOD)
                        && enclosed.getModifiers().contains(Modifier.PUBLIC)
                        && !enclosed.getModifiers().contains(Modifier.STATIC)) {
                    interfaceMethods.add(enclosed);
                }
            }
            if (interfaceMethods.size() > 0) {
                writeInterfaceFile(interfaceName);
            }
        }
        return false;
    }

    private void writeInterfaceFile(String interfaceName) {
        //创建新文件
        //        使用Filer,可以持续的跟踪创建的文件
        try (Writer writer = processingEnv.getFiler()
                .createSourceFile(interfaceName)
                .openWriter()) {
            String packageName = elementUtils
                    .getPackageOf(interfaceMethods.get(0)).toString();
            writer.write("package " + packageName + ";\n");
            writer.write("public interface " + interfaceName + " {\n");
            for (Element elem : interfaceMethods) {
                ExecutableElement method = (ExecutableElement) elem;
                String signature = " public ";
                signature += method.getReturnType() + " ";
                signature += method.getSimpleName();
                signature += createArgList(method.getParameters());
                System.out.println(signature);
                writer.write(signature + ";\n");
            }
            writer.write("}");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    //    方法参数
    private String createArgList(List<? extends VariableElement> parameters) {
        String args = parameters.stream().map(p -> p.asType() + " " + p.getSimpleName())
                .collect(Collectors.joining(","));
        return "(" + args + ")";
    }
}

编译命令

"jdk-22\bin\javac" -cp target/classes -processor com.edfeff.ch03.IfaceExtractorProcessor src/main/java/com/edfeff/ch03/Multiplier.java

生成的文件如下

package com.edfeff.ch03;

public interface IMultiplier {
    public int multiply(int x, int y);

    public int fortySeven();

    public double timesTen(double x);
}

4.4 基于注解的单元测试

junit使用,略