注解是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使用,略