Appearance
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
.