注解
定义注解¶
-
注解是一种结构化接受检查的向代码中添加元数据的方法,可以帮助面出部署描述文件或其它生成文件的编写工作。是对代码的一种特殊标记,这些标记可以在编译,类加载和运行时被读取,并执行相应的处理
-
注解通常包含一些可以设定值的元素,程序或工具在处理注解时可以使用参数,没有任何元素的注解为标记注解
-
使用@interface 定义注解定义注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface name{ int id(); String description() default "no description";//默认值 } @UseCase(id = 47, description = "xxx") public void...//绑定注解的方法
-
注解允许的元素类型:基本数据类型、String、Class、enum、Annotation(注解)、及以上类型的数组
- 元素要么有默认值,要么在使用时必须给出值
-
非基本类型不允许赋值为null
-
标准注解
- @Override
- @Deprecated:如果元素被使用了,则编译器会发出警告
- @SuppressWarnings:关闭不当的编译警告
- @SafeVarargs:在使用泛型作为可变参数的方法或构造器中关闭警告
-
@FunctionalInterface:声明函数式接口
-
元注解
- 对注解进行注解
- @Target:该注解可用的地方
- ElementType.CONSTRUCTOR:构造器声明
- ElementType.FIELD:字段声明(包括枚举常量)
- ElementType.LOCAL_VARIABLE:本地变量声明
- ElementType.METHOD:方法声明
- ElementType.PACKAGE:包声明
- ElementType.PARAMETRE:参数声明
- ElementType.TYPE:类、接口(含注解)枚举的声明
- @Retention:注解信息可以保存多久
- RetentionPolicy.SOURCE:注解会被编译器丢弃
- RetentionPolicy.CLASS:可以被编译器使用但是会被虚拟机丢弃
- RetentionPolicy.RUNTIME:运行时被虚拟机保留,可以通过反射读取注解信息
- @Documented:在javadoc中引入注解
- @Inherited:允许子类继承父类注解
-
@Repeatable:可以多次应用于同一个声明
-
注解不支持继承,不能对@interface使用extends,但是可以通过内嵌注解实现类似的功能
-
嵌套注解
- 嵌套注解通常用于当一个注解的属性值本身就是一个注解
- 这就是一个嵌套注解
public @interface Uniqueness { Constraints constraints() default @Constraints(unique = true); }
注解处理器¶
-
例:读取被注解的类,通过反射查找注解标签,通过注解的id查找出已经注册的测试案例
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() + "\n " + uc.description()); useCases.remove(Integer.valueOf(uc.id())); } } }
-
示例
@Target(ElementType.TYPE) // Applies to classes only @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; } @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface SQLInteger { String name() default ""; Constraints constraints() default @Constraints; } @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface SQLString { int value() default 0; String name() default ""; Constraints constraints() default @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; } }
-
这套自定义注解的意义在于提供一种声明式的方法来描述一个Java类如何映射到数据库的表和字段。通过这些注解,你可以将类和字段的元数据(例如表名、字段名、字段类型、约束等)直接嵌入到Java代码中。这种方式的好处是代码更清晰、更直观,并且可以由框架或工具在运行时动态解析,从而自动生成SQL语句或执行其他数据库相关操作。
-
根据注解读取文件创建 SQL 数据库
public class TableCreator { public static void main(String [] args) throws Exception { if(args.length < 1) { System.out.println( "arguments: annotated classes"); System.exit(0); } //参数标识已经标注好需要进行自动构建的类 for(String className : args) { Class <?> cl = Class.forName(className); //获取注解 DBTable dbTable = cl.getAnnotation(DBTable.class); if(dbTable == null) { System.out.println( "No DBTable annotations in class " + className); continue; } String tableName = dbTable.name(); // If the name is empty, use the Class 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; // Not a db table column //根据类型自动执行 SQL 命令进行构建 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]; // Use field name if name not specified. if(sString.name().length() < 1) 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 + ","); // Remove trailing comma 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; }
用javac处理-编译时注解*¶
- 通过javac可以创建编译时注解处理器,将注解用于java源文件而不是编译之后的文件。
- 不能通过注解来修改源代码,唯一能影响结果的方法是创建新文件
- 如果注解创建了新文件,新一轮处理中会检查该文件自身的注解。工具会一轮一轮的持续处理,直到没有新的源文件被创建
- 需要创建一个注解处理器并绑定到编译器
@SupportedAnnotationTypes("com.class")
指定注解处理器支持的注解类型- 这意味着注解处理器会处理所有被
com.class
注解标记的元素。 - 使用这个注解能够提高处理器的查找效率,并且使得处理器的意图更加明确。
- 这意味着注解处理器会处理所有被
-
@SupportedSourceVersion(SourceVersion.RELEASE_8)
注解用于指定注解处理器支持的源代码版本。在这个例子中,它表明处理器支持Java 8的源代码版本。- 避免在与处理器不兼容的Java版本上运行时可能出现的问题。
- 默认情况下,注解处理器被假定为支持最新的源码版本。
-
Element
是用于表示和操作元素在源代码中的结构和信息的基础。 - PackageElement:表示一个包。提供对包名、包内成员等的访问。
- TypeElement:表示一个类或接口。提供对类名、父类、接口、方法和字段等的访问。
- ExecutableElement:表示某些可执行的元素,如方法、构造函数或初始化器(静态或实例)。
- VariableElement:表示一个字段、枚举常量、方法或构造函数的参数、局部变量或异常参数。
getKind()
:返回此元素的类型(如类、方法、字段等)。getModifiers()
:返回此元素的修饰符(如public
、private
、static
等)。getSimpleName()
:返回此元素的简单(不带包名的)名称。getEnclosingElement()
:返回包含此元素的最近的上层元素。-
getAnnotation(Class<A> annotationType)
:返回此元素上特定类型的注解。 -
示例
//注解定义
@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-";
}
//使用注解
@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();
}
}
//处理器(仅仅打印注解的信息)
@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());
//不同类型不同显示
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());
}
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() + ")");
}
}
}
-
正常编译是不会使用注解处理器,要加上参数
-processor com.注解类
-
在编译时注解处理器中不能使用反射,因为操作的是源代码而不是编译后的类,使用镜子查看源代码中的方法字段类型等信息
@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 arg) { return arg * 10; } public static void main(String [] args) { Multiplier m = new Multiplier(); System.out.println( " 11 * 16 = " + m.multiply(11, 16)); } } //处理器,提取出方法创建新接口的源代码文件 @SupportedAnnotationTypes( "annotations.ifx.ExtractInterface") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class IfaceExtractorProcessor extends AbstractProcessor { //存储符合条件的方法字段 private ArrayList <Element> interfaceMethods = new ArrayList <>(); Elements elementUtils; private ProcessingEnvironment processingEnv; @Override public 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) { 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 + ")"; } }
-
得到的文件
//IMultiplier.java package annotations.ifx; public interface IMultiplier{ public int multiply(int x, int y); public int fortySeven(); public double timesTen(double arg); }