Skip to content
On this page

Handle annotations

Java annotations themselves have no impact on code logic. According to the configuration of @Retention :

  • SOURCE type annotations are discarded at compile time;
  • CLASS type annotations are only saved in class files, they will not be loaded into the JVM;
  • RUNTIME type annotations will be loaded into the JVM and can be read by the program during runtime.

How annotations are used is entirely up to the tool. SOURCE type annotations are mainly used by the compiler, so we generally only use them and do not write them. CLASS type annotations are mainly used by the underlying tool library and involve class loading. Generally, we rarely use them. Only RUNTIME type annotations must not only be used, but also often written.

Therefore, we only discuss how to read RUNTIME type annotations.

Because annotations are also a class after definition, all annotations inherit from java.lang.annotation.Annotation , Therefore, to read annotations, you need to use the reflection API.

The methods provided by Java to read Annotation using the reflection API include:

Determine whether an annotation exists in Class , Field , Method or Constructor :

  • Class.isAnnotationPresent(Class)
  • Field.isAnnotationPresent(Class)
  • Method.isAnnotationPresent(Class)
  • Constructor.isAnnotationPresent(Class)

For example:

java
Person.class.isAnnotationPresent(Report.class);

Use the reflection API to read the Annotation:

  • Class.getAnnotation(Class)
  • Field.getAnnotation(Class)
  • Method.getAnnotation(Class)
  • Constructor.getAnnotation(Class)

For example:

java
// Get the @Report annotation defined by Person:
Report report = Person.class.getAnnotation(Report.class);
int type = report.type();
String level = report.level();

There are two ways to read Annotation using the reflection API. Method one is to first determine whether Annotation exists. If it exists, read it directly:

java
Class cls = Person.class;
if (cls.isAnnotationPresent(Report.class)) {
    Report report = cls.getAnnotation(Report.class);
    ...
}

The second method is to read Annotation directly. If Annotation does not exist, null will be returned:

java
Class cls = Person.class;
Report report = cls.getAnnotation(Report.class);
if (report != null) {
   ...
}

Annotation for read methods, fields and constructors is similar to Class. However, it is more troublesome to read Annotation of the method parameters, because the method parameters themselves can be regarded as an array, and each parameter can define multiple annotations.

Therefore, to obtain all the annotations of the method parameters at one time, a two-dimensional array must be used. to express. For example, for the annotation defined by the following method:

java
public void hello(@NotNull @Range(max=5) String name, @NotNull String prefix) {
}

To read the annotations of method parameters, we first use reflection to obtain Method instance, and then read all the annotations of the method parameters:

java
// Get Method instance:
Method m = ...
// Get the Annotation of all parameters:
Annotation[][] annos = m.getParameterAnnotations();
// All Annotations for the first parameter (index 0):
Annotation[] annosOfName = annos[0];
for (Annotation anno : annosOfName) {
    if (anno instanceof Range r) { // @Range annotation
        r.max();
    }
    if (anno instanceof NotNull n) { // @NotNull annotation
        //
    }
}

Use annotations

How to use annotations is entirely up to the program itself. For example, JUnit is a testing framework that automatically runs all methods marked @Test .

Let's take a look at a @Range annotation. We hope to use it to define a rule for a String field: the field length meets the parameter definition of @Range :

java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Range {
    int min() default 0;
    int max() default 255;
}

In a JavaBean, we can use this annotation:

java
public class Person {
    @Range(min=1, max=20)
    public String name;

    @Range(max=10)
    public String city;
}

However, defining annotations itself has no impact on program logic. We have to write code ourselves to use annotations. Here, we write a checking method for Person instance, which can check whether the length of String field of Person instance meets the definition of @Range :

java
void check(Person person) throws IllegalArgumentException, ReflectiveOperationException {
    // Traverse all fields:
    for (Field field : person.getClass().getFields()) {
        // Get the @Range defined by Field:
        Range range = field.getAnnotation(Range.class);
        // If @Range exists:
        if (range != null) {
            // Get the value of Field:
            Object value = field.get(person);
            // If value is String:
            if (value instanceof String s) {
                // Determine whether the value meets the min/max of @Range:
                if (s.length() < range.min() || s.length() > range.max()) {
                    throw new IllegalArgumentException("Invalid field: " + field.getName());
                }
            }
        }
    }
}

In this way, we can complete the check of Person instances through the @Range annotation and the check() method. Note that the checking logic is entirely written by ourselves, and the JVM will not automatically add any additional logic to the annotations.

Summary

Annotations of the RUNTIME type can be read through reflection during runtime. Be careful not to miss writing. @Retention(RetentionPolicy.RUNTIME) , otherwise the annotation cannot be read during runtime.

Corresponding functions can be achieved by processing annotations programmatically:

  • Check JavaBean attribute values according to rules;
  • JUnit will automatically run the test method marked @Test .
Handle annotations has loaded