注解的一些用法

注解是一种用于提供数据关于程序元素(如类、方法、变量等)的元数据的方法,它可以在编译时或运行时被读取。注解可以用来影响程序的行为,提供更多的信息,甚至进行错误检查。它们通常放在元素的声明前,用“@”符号开始,如:@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 接口。下面给出一个简单案例:

  1. 自定义一个注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Immutable {}
  1. 编写处理器:
@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文件了

Java中的自定义注解是用于定义自己的注解类型,它们可以用于声明在类、方法、变量等代码元素上。自定义注解的基本使用方法包括以下几个步骤: 1. 定义注解类型:使用 `@interface` 关键字定义一个新的注解接口。注解接口中可以定义成员变量,这些成员变量的默认值可以通过 `default` 关键字指定。 ```java public @interface MyAnnotation { String value() default "Default Value"; int number() default 0; } ``` 2. 应用注解:将自定义的注解应用到类、方法或字段等代码元素上。 ```java @MyAnnotation(value = "Example", number = 10) public class MyClass { @MyAnnotation(value = "Field", number = 20) private String myField; @MyAnnotation(value = "Method", number = 30) public void myMethod() { } } ``` 3. 处理注解注解本身不会对代码的行为产生影响,除非通过反射机制来读取这些注解。可以使用 `java.lang.reflect` 包中的类,如 `AnnotatedElement` 接口的实现类(`Class`、`Method`、`Field` 等),结合 `getAnnotation` 或 `getAnnotations` 方法来获取注解实例。 ```java import java.lang.reflect.Field; import java.lang.reflect.Method; public class AnnotationProcessor { public static void processAnnotations(Class<?> clazz) { // 检查类注解 MyAnnotation classAnnotation = (MyAnnotation) clazz.getAnnotation(MyAnnotation.class); if (classAnnotation != null) { System.out.println("Class annotation value: " + classAnnotation.value()); } // 遍历类中的字段 for (Field field : clazz.getDeclaredFields()) { MyAnnotation fieldAnnotation = field.getAnnotation(MyAnnotation.class); if (fieldAnnotation != null) { System.out.println("Field annotation value: " + fieldAnnotation.value()); } } // 遍历类中的方法 for (Method method : clazz.getDeclaredMethods()) { MyAnnotation methodAnnotation = method.getAnnotation(MyAnnotation.class); if (methodAnnotation != null) { System.out.println("Method annotation value: " + methodAnnotation.value()); } } } } ``` 4. 元注解:在定义注解时,可以使用一些预定义的注解来提供额外的元数据信息。例如 `@Retention` 指定注解的保留策略,`@Target` 指定注解可以应用的元素类型等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值