注解(Annotation)是Java语言中一种特殊的修饰符,它可以用于类、方法、参数、变量、构造器以及包声明中,用于为Java代码提供元数据。相对于其他修饰符如public
、final
等,注解并不直接影响代码的语义,但却能被某些工具软件(如编译器、框架)所读取和利用。
注解的声明类似于接口的声明,但是前面多了一个@
符号:
public @interface MyAnnotation {
String value() default ""; //定义value属性
}
使用注解:
@MyAnnotation(value="This is my custom annotation")
public class MyClass {
// class body
}
Java提供了一些预定义的注解,如:
@Override
: 限定重写父类方法。@Deprecated
: 表示某个程序元素(如方法)已经过时。@SuppressWarnings
: 告诉编译器忽略指定的警告。用于注解其他注解的注解称为元注解。Java提供了以下几种元注解:
@Target
: 表明该注解可以被应用于什么地方(如方法、类、字段等)。@Retention
: 表明该注解的生命周期(仅源代码、编译期、运行期)。@Documented
: 表示使用该注解的元素应被Javadoc或其他工具文档化。@Inherited
: 表示该注解可以被子类继承。注解为Java提供了一种元级编程的方式,它允许开发者在不修改代码逻辑的前提下,向源码中添加一些额外的信息,这些信息可以被编译器或其他工具所读取并使用。
由编译器使用的注解
.class
文件,在编译后它们就被编译器丢弃了。@Override
:此注解让编译器检查该方法是否正确地实现了覆写。@SuppressWarnings
:此注解告诉编译器忽略此处代码产生的警告。由工具处理 .class
文件使用的注解
.class
文件,但在加载完成后并不会存在于内存中。这类注解主要被一些底层库使用,一般我们不需要自己处理。lombok
。在程序运行期能够读取的注解
@PostConstruct
的方法会在调用构造方法后自动被调用。这是 Java 代码通过读取该注解实现的功能,JVM 本身并不会识别该注解。Java 的注解为代码添加了额外的元数据,这些元数据在编译或运行时都可以被访问。为了定义注解的属性和行为,Java 提供了以下一系列的元注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation { }
@Target(ElementType.METHOD)
public @interface MyMethodAnnotation { }
@Inherited
public @interface Inheritable { }
@Documented
public @interface DocumentedAnnotation { }
public @interface RepeatedValues {
Value[] value();
}
@Repeatable(RepeatedValues.class)
public @interface Value {
String info() default "";
}
在 java.lang
包中,JDK 提供了五个基本注解:
deprecation
:使用已过时的类或方法。unchecked
:未经检查的类型转换。fallthrough
:switch
语句缺少 break
。path
:类或源文件路径不存在。serial
:可序列化类缺少 serialVersionUID
。finally
:finally
语句未正常完成。all
:上述所有情况。default
或 static
方法)。value 特权:如果注解的 value
成员变量是唯一需要赋值的,可以省略 name=value
的格式,直接在注解的括号中指定值。
推荐使用 @Override:在意图重写父类方法的每个方法上使用 @Override
,帮助编译器及时捕获可能的错误。
在 Java 中,注解是一种为代码添加元数据的方式。当我们使用 @interface
关键字定义新的注解时,背后的一些细节会由编译器自动完成,例如自动继承 java.lang.annotation.Annotation
接口。
注解的定义规则:
@interface
关键字用于声明一个新的注解。default
关键字为参数设定默认值。注解参数的支持类型:
int
, float
, boolean
, byte
, double
, char
, long
, short
String
Class
Enum
)Annotation
)package annotation.custom;
// 导入必要的注解处理类
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 表明这个注解是可以被子类继承的
@Inherited
// 指明该注解的生命周期,此处指定为RUNTIME,表示注解在运行时仍然存在,并可以通过反射机制读取
@Retention(RetentionPolicy.RUNTIME)
// 指明该注解可以被应用于方法和类型(类、接口、枚举、注解)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Tag {
// 定义一个名为name的元素,它的默认值为"undefined"
String name() default "undefined";
// 定义一个名为description的元素,使用该注解时,这个元素是必须要设置的,因为它没有默认值
String description();
}
// 代表所有的注解类型
public interface Annotation {
// 用于比较两个注解是否相等。注意:自定义的注解会有编译器生成的实现,所以使用者不需要手动实现此方法。
boolean equals(Object obj);
// 返回该注解的哈希码。同样,这个方法会由编译器自动生成其实现。
int hashCode();
// 返回该注解的字符串表示形式。这也是由编译器自动生成的方法。
String toString();
// 返回表示此注解的注解类型的Class对象。它可以用于在运行时获取注解的信息。
Class<? extends Annotation> annotationType();
}
注意: Java 使用 Annotation 接口来代表程序元素前面的注解,该接口是所有 Annotation 类型的父接口。 自定义的注解继承了 Annotation 这个接口,因此自定义注解中包含了 Annotation 接口中所有的方法
Java在java.lang.reflect
包内提供了强大的反射机制,其中AnnotatedElement
接口扮演了关键角色,代表那些可以携带注解的程序元素。
定义:此接口表示可以接受注解的程序元素。
实现类:
AccessibleObject
:是Field
、Method
和Constructor
对象的基类。Executable
:是Method
和Constructor
对象的共有基类。Method
:表示类或接口中的某个方法。Constructor
:提供某个类的构造方法的信息。Field
:表示类或接口中的某个字段。Class
:代表应用程序中的类和接口。Package
:提供有关Java包的版本信息。Parameter
:提供关于方法参数的信息,Java 8 新增。功能:通过这些类,Java提供了一套丰富的API用于获取运行时的注解信息。
只有当一个注解被定义为运行时注解(即使用@Retention(RetentionPolicy.RUNTIME)
标注)时,它才会在运行时被JVM读取。这意味着,通过反射,我们可以读取类、方法或字段上的这些注解,并据此执行某些操作。例如,通过反射获得某个类的Method
对象后,可以调用该对象的getAnnotations()
方法来获取所有的注解。
isAnnotationPresent(Class extends Annotation> annotationType):
getAnnotations():
getDeclaredAnnotations():
getAnnotation(Class annotationClass):
getDeclaredAnnotation(Class annotationClass):
getAnnotationsByType(Class annotationClass):
getDeclaredAnnotationsByType(Class annotationClass):
注意: 只有当定义 Annotation 时使用了 @Retention(RetentionPolicy.RUNTIME) 修饰, JVM
才会在装载 class 文件时提取保存在 class 文件中的 Annotation, 该 Annotation 才会在运行时可见。否则class 文件的注解信息在执行过程中将不可用, 从而也就不能从中得到任何和注解有关的数据。
apt
(Annotation Processor Tool)和com.sun.mirror
包中的Mirror API来处理注解。apt
工具被集成到javac
编译工具中。Java 6提供了一个通用功能的抽象类javax.annotation.processing.AbstractProcessor
和javax.lang.model
包来支持注解处理。javac
会扫描每个源文件中的注解。javac
会查询是否有已注册的注解处理器可以处理它。javac
将调用该处理器来处理注解。处理器可以分析注解、读取注解的值,并根据这些值生成额外的源代码或其他文件。类属性自动赋值:
@Autowired
和@Value
。验证对象属性完整性:
@NotNull
, @Size
, @Min
, @Max
等)进行对象属性验证。代替配置文件功能:
@Component
, @Service
, @Repository
, @Controller
等。生成文档:
编译时检查:
@Override
用于告诉编译器子类中的某个方法是否正确地重写了父类中的方法。代码生成:
运行时处理:
APT(Annotation Processing Tool):
AbstractProcessor
是一个提供便捷实现注解处理器功能的抽象类,实现了 Processor
接口,都位于 javax.annotation.processing
包中。
自定义注解处理器的主要步骤:
实现 Processor 接口: 通过继承 AbstractProcessor
类来实现自定义的注解处理器。主要的工作在于实现 process
方法,进行你想要完成的注解处理功能。
在 init()
方法中,你可以获得:
在注解处理过程中,会扫描所有的 Java 源文件。每个部分都是一个特定类型的 Element
,代表程序的元素,例如包、类或者方法。主要的子类有:
注册注解处理器: 有两种方法:
resources/META-INF/services
目录下新建文件 javax.annotation.processing.Processor
,内容是处理器的全称。com.google.auto.service:auto-service
依赖,并在处理器上添加 @AutoService(Processor.class)
注解。这会自动在 META-INF
目录下生成配置信息文件。注意:在 Java 6 及以上版本中,可以使用
@SupportedAnnotationTypes
注解和@SupportedSourceVersion
注解来分别替代getSupportedAnnotationTypes()
方法和getSupportedSourceVersion()
方法。
自定义注解处理器都需要继承于 AbstractProcessor,如下所示:
/**
* 自定义注解处理器
*/
public class CustomProcessor extends AbstractProcessor {
private Filer mFiler;
private Messager mMessager;
private Elements mElementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFiler = processingEnvironment.getFiler();
mMessager = processingEnvironment.getMessager();
mElementUtils = processingEnvironment.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new LinkedHashSet<String>();
annotataions.add(Tag.class.getCanonicalName());
return annotataions;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
Set<? extends Element> tagElements = roundEnvironment.getElementsAnnotatedWith(Tag.class);
for (Element element : tagElements) {
// 1.获取包名
PackageElement packageElement = mElementUtils.getPackageOf(element);
String pkName = packageElement.getQualifiedName().toString();
printMessage(String.format("package = %s", pkName));
// 2.获取包装类类型
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
String enclosingName = enclosingElement.getQualifiedName().toString();
printMessage(String.format("enclosindClass = %s", enclosingName));
// 3.获取注解的成员变量名
String tagFiledName = element.getSimpleName().toString();
// 4.获取注解的成员变量类型
String tagFiledClassType = element.asType().toString();
// 5.获取注解元数据
Tag tag = element.getAnnotation(Tag.class);
String name = tag.name();
printMessage(String.format("%s %s = %s", tagFiledClassType, tagFiledName, name));
// 6.生成文件
createFile(enclosingElement, tagFiledClassType, tagFiledName, name);
return true;
}
return false;
}
private void createFile(TypeElement enclosingElement, String tagFiledClassType, String tagFiledName, String name) {
String pkName = mElementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
try {
JavaFileObject javaFileObject = mFiler.createSourceFile(pkName + ".Tag");
Writer writer = javaFileObject.openWriter();
writer.write(generateCode(pkName, tagFiledClassType, tagFiledName, name));
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private void printMessage(String msg) {
mMessager.printMessage(Diagnostic.Kind.NOTE, msg);
}
private String generateCode(String pkName, String tagFiledClassType, String tagFiledName, String name) {
StringBuilder builder = new StringBuilder();
builder.append("package " + pkName + ";\n\n");
builder.append("//Auto generated by apt,do not modify!!\n\n");
builder.append("public class Tag { \n\n");
builder.append("public static void main(String[] args){ \n");
String info = String.format("%s %s = %s", tagFiledClassType, tagFiledName, name);
builder.append("System.out.println(\"" + info + "\");\n");
builder.append("}\n");
builder.append("}");
return builder.toString();
}
}