注解是一种用于提供数据关于程序元素(如类、方法、变量等)的元数据的方法,它可以在编译时或运行时被读取。注解可以用来影响程序的行为,提供更多的信息,甚至进行错误检查。它们通常放在元素的声明前,用“@”符号开始,如:@Override, @Entity等。在Java中,注解被广泛用于各种框架,如Spring、Hibernate等。此外,它们也被用于创建API文档,测试代码,等等。
如何自定义一个注解
@interface 就可以表示这是一个注解。
public @interface Immutable {}
指定注解的保留策略
用元注解**@Retention** 其参数含义如下:
RetentionPolicy.SOURCE | 表示注解仅在源代码级别保留,编译器会丢弃这些注解,并不包含在编译后的类文件中。这意味着在编译后,这些注解将不再存在。 |
---|---|
RetentionPolicy.CLASS | 表示注解在编译时保留,它们会包含在编译后的类文件中,但在运行时不会加载到 JVM 中。这意味着在运行时无法通过反射来读取和处理这些注解,但可以在编译期间通过一些工具进行处理。 |
RetentionPolicy.RUNTIME | 表示注解在运行时保留,它们会包含在编译后的类文件中,并且可以通过反射在运行时读取和处理。这种保留策略允许在运行时检测和使用注解。 |
指定注解适用范围
用元注解 @Target
其参数一共有8种,如下:
ElementType | 描述 |
---|---|
ElementType.TYPE | 可以应用于类、接口、枚举(包括注解类型)的声明。 |
ElementType.FIELD | 可以应用于字段(包括枚举常量)的声明。 |
ElementType.METHOD | 可以应用于方法的声明。 |
ElementType.PARAMETER | 可以应用于方法形参的声明。 |
ElementType.CONSTRUCTOR | 可以应用于构造函数的声明。 |
ElementType.LOCAL_VARIABLE | 可以应用于局部变量的声明。 |
ElementType.ANNOTATION_TYPE | 可以应用于注解类型的声明。 |
ElementType.PACKAGE | 可以应用于包的声明。 |
用反射对指定注解的操作
我们在日志打印序列化的时候有些字段属性是希望做额外操作的,比如密码掩盖,敏感信息脱敏等等,是需要在序列化的时候作出额外的操作。可以用自定义注解的方式来实现这个需求。
举个简单的例子:我们需要实现一个对象在创建之后,让有注解修饰的属性设置值为1;先创建一个注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface DefaultValueOne {}
实体类:
@Data
class MyClass {
@DefaultValueOne
private int value1;
private int value2;
}
用反射处理这个注解:
public void setDefaultValues(MyClass mc) throws IllegalAccessException {
Class<?> clazz = mc.getClass();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(DefaultValueOne.class)) {
field.setAccessible(true);
if (field.getType() == int.class) {
field.setInt(obj, 1);
}
}
}
}
其处理的核心点就在于isAnnotationPresent
这个方法,判断属性是否是某个注解修饰的。
自定义注解处理器生成新的.java文件
我们用过的lombok可以在编译的时候将get方法和set方法补齐,以及mapstruct可以自动生成接口的实现类。这些功能是可以通过继承AbstractProcessor
类或者实现javax.annotation.processing.Processor
接口。下面给出一个简单案例:
- 自定义一个注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Immutable {}
- 编写处理器:
@SupportedAnnotationTypes("com.bluea.annotation.Immutable")
@SupportedSourceVersion(SourceVersion.RELEASE_11)
public class ImmutableProcessor extends AbstractProcessor {
private Elements elementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUtils = processingEnv.getElementUtils();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(
Immutable.class)) {
if (annotatedElement.getKind() == ElementKind.CLASS) {
TypeElement typeElement = (TypeElement) annotatedElement;
try {
generateImmutableClass(typeElement);
} catch (IOException e) {
e.printStackTrace();
}
}
}
return true;
}
private void generateImmutableClass(TypeElement typeElement) throws IOException {
String packageName = elementUtils.getPackageOf(typeElement).getQualifiedName().toString();
String className = typeElement.getSimpleName().toString() + "Immutable";
String fullName = packageName + "." + className;
JavaFileObject fileObject = processingEnv.getFiler().createSourceFile(fullName);
try (PrintWriter writer = new PrintWriter(fileObject.openWriter())) {
writer.println("package " + packageName + ";");
writer.println();
writer.println("public final class " + className + " {");
for (Element element : typeElement.getEnclosedElements()) {
if (element.getKind() == ElementKind.FIELD) {
String fieldName = element.getSimpleName().toString();
String fieldType = getLastTypeName(element.asType().toString());
writer.println(" private " + fieldType + " " + fieldName + ";");
}
}
writer.println();
writer.println(
" public " + className + "(" + typeElement.getSimpleName() + " " +
lowercase(typeElement.getSimpleName().toString()) + ") {");
for (Element element : typeElement.getEnclosedElements()) {
if (element.getKind() == ElementKind.FIELD) {
String fieldName = element.getSimpleName().toString();
writer.println(" this." + fieldName + " = " +
lowercase(typeElement.getSimpleName().toString()) + "." + fieldName +
";");
}
}
writer.println(" }");
writer.println();
for (Element element : typeElement.getEnclosedElements()) {
if (element.getKind() == ElementKind.FIELD) {
String fieldName = element.getSimpleName().toString();
String fieldType = getLastTypeName(element.asType().toString());
writer.println(
" public " + fieldType + " get" + capitalize(fieldName) + "() {");
writer.println(" return " + fieldName + ";");
writer.println(" }");
writer.println();
writer.println(
" public void set" + capitalize(fieldName) + "(" + fieldType + " " +
fieldName + ") {");
writer.println(" this." + fieldName + " = " + fieldName + ";");
writer.println(" }");
writer.println();
}
}
writer.println("}");
}
}
private String capitalize(String s) {
return Character.toUpperCase(s.charAt(0)) + s.substring(1);
}
private String lowercase(String s) {
return Character.toLowerCase(s.charAt(0)) + s.substring(1);
}
private String getLastTypeName(String s) {
String[] split = s.split("/.");
return split[split.length - 1];
}
}
当我们在TestVo这个类上加上Immutable注解时,就可以实现生成一个final修饰的类TestVoImmutable,并且将TestVo里的公有属性转为私有并加上get和set方法。
如何引进自定义的注解并让处理器在项目中生效
如果仅仅只是写了上述代码,其实是不能够生效的。需要借助Java的SPI来自动扫到这个处理器。
所以注解的处理器需要新建起一个maven项目,在META-INF/services/javax.annotation.processing.Processor
文件里写你自定义的处理器的全限定类名。记得将这个项目编译后发布(install)
发布之后其他项目就可以引入你的这个自定义的依赖包,dependency里加上,以及在plugin里要加上annotationProcessorPaths
所以完整的插件配置如下:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
<annotationProcessorPaths>
<path>
<groupId>com.bluea</groupId>
<artifactId>custom-annotation</artifactId>
<version>1.0-SNAPSHOT</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
完成上述操作之后,编译项目时就会自动生成一个新的java文件了