Spring series 20: annotation explanation and spring annotation enhancement (basic internal skill)

Some small partners reported that annotations are widely used in annotation based Spring, which is a little uncomfortable because they are not familiar with Java annotations. It is suggested to summarize the basic knowledge of a Java annotation, then it comes!

Content of this article

  1. What is annotation?
  2. How to define annotations
  3. How to use annotations
  4. How to get annotation information
  5. What enhancements have been made to annotations in Spring?

What is annotation?

What are comments written in code? That's for developers to see, but there is no annotation information in the compiled bytecode file, that is to say, the annotation is meaningless for the java compiler and JVM and can't be seen!

Analogy annotation is for people to see, while annotation is some identification for java compiler and JVM. The compiler and virtual machine can obtain annotation information to do some processing during operation.

How to define annotations

The syntax of annotation definition is as follows:

public	@interface Annotation class name{
    Parameter type parameter name 1() [default Parameter defaults];
    Parameter type parameter name 2() [default Parameter defaults];
}

Parameter names can be blank or multiple. The definition details are as follows:

  • Parameter types can only be basic type, String, Class, enumeration type, annotation type and corresponding one-dimensional array
  • If there is only one annotation parameter, it is recommended to define the name as value, which is the default parameter name for convenience
  • Default can specify the default value. If there is no default value, the parameter value must be given when using annotation

There are two main issues to consider when defining annotations:

  • Where can annotations be used, that is, the scope of use?
  • To what stage, the source phase, or the runtime phase, do annotations remain?

java provides some meta annotations to solve the above problems.

@Target specifies the scope of use of the annotation

Let's look at the source code, which mainly specifies the array parameters of the element types to which annotation types can be applied.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    // Returns an array of element types to which annotation types can be applied
    ElementType[] value();
}
/*Scope of use of annotations*/
public enum ElementType {
    /*Class, interface, enumeration, annotation*/
    TYPE,
    /*Field*/
    FIELD,
    /*Methodologically*/
    METHOD,
    /*Method*/
    PARAMETER,
    /*Constructor*/
    CONSTRUCTOR,
    /*On local variables*/
    LOCAL_VARIABLE,
    /*Annotation*/
    ANNOTATION_TYPE,
    /*Pack*/
    PACKAGE,
    /*After 1.8 on type parameter*/
    TYPE_PARAMETER,
    /*After 1.8 on type name*/
    TYPE_USE
}

@Retention specifies the retention policy for annotations

Indicates how long you want to keep comments with comment types. If there is no reserved comment in the comment type declaration, the retention policy defaults to retentionpolicy CLASS

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
  
    RetentionPolicy value();
}
public enum RetentionPolicy {
    // In the source code phase, annotations are discarded by the compiler.
    SOURCE,

    // Comments are recorded in the class file by the compiler, but do not need to be retained by the VM at run time. This is the default behavior.
    CLASS,

    // Comments are recorded in class files by the compiler and retained by the VM at run time, so they can be read reflexively.
    RUNTIME
}

Based on the above two annotations, customize an annotation that is only used in methods that is retained until the run time, as follows.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DemoAnnotation {
    String name() default "";
    Class targetClazz();
}

How to use annotations

Use syntax

@Annotation (Parameter 1=Value 1,Parameter 2=Value 2,Parameter 3=Value 3,parameter n=value n)
Target object

Use the notes in the previous section to a simple case

public class MyBean {
    @DemoAnnotation(name = "xxx", targetClazz = MyBean.class)
    public void m() {

    }
}

For a comprehensive case, the annotation positions include classes, methods, constructors, method parameters, fields, local variables, generic type parameters and type names.

/**
 * Comprehensive case
 * @author zfd
 * @version v1.0
 * @date 2022/1/24 13:31
 * @For me, please pay attention to the official account of crab Java notes for more technical series.
 */
@StrongAnnotation(value = "Used on classes", elementType = ElementType.TYPE)
public class UseStrongAnnotation<@StrongAnnotation(value = "Used on type parameters T0", elementType = ElementType.TYPE_PARAMETER) T0,
        @StrongAnnotation(value = "Used on type names T1", elementType = ElementType.TYPE_USE) T1> {
    @StrongAnnotation(value = "Used on fields", elementType = ElementType.FIELD)
    private String field;

    @StrongAnnotation(value = "Construction method", elementType = ElementType.CONSTRUCTOR)
    public UseStrongAnnotation(@StrongAnnotation(value = "Used on method parameters", elementType = ElementType.PARAMETER) String field) {
        this.field = field;
    }

    @StrongAnnotation(value = "Used in common methods", elementType = ElementType.METHOD)
    public void m(@StrongAnnotation(value = "Method parameters", elementType = ElementType.PARAMETER) String name) {
        @StrongAnnotation(value = "Used on local variables", elementType = ElementType.LOCAL_VARIABLE)
        String prefix = "hello ";
        System.out.println(prefix + name);
    }

    public <@StrongAnnotation(value = "Type parameter of method T2 upper", elementType = ElementType.TYPE_PARAMETER) T2> void m1() {

    }
    public <@StrongAnnotation(value = "Type name of the method T3 upper", elementType = ElementType.TYPE_USE) T3> void m2() {

    }

    private Map<@StrongAnnotation(value = "Map The angle brackets that follow are also type names", elementType = ElementType.TYPE_USE) String ,
    @StrongAnnotation(value = "Map The angle brackets that follow are also type names", elementType = ElementType.TYPE_PARAMETER)Object> map;
}

How to get annotation information

java. lang.reflect. The annotatedelement interface represents the annotation element of the program currently running in this VM. This interface allows annotations to be read in a reflective manner. All annotations returned by methods in this interface are immutable and serializable. The array returned by the method of this interface can be modified by the caller without affecting the array returned to other callers. The main methods of obtaining annotations are as follows, see the meaning of the name.

The main implementation classes or interfaces are shown below

The meaning of the corresponding implementation is also clear:

  • Package: used to represent package information
  • Class: used to represent class information
  • Constructor: used to represent construction method information
  • Field: used to represent the attribute information in the class
  • Method: used to represent method information
  • Parameter: used to represent method parameter information
  • TypeVariable: used to represent type variable information, such as generic type variables defined on classes and generic type variables defined on methods

Comprehensive case

Take a comprehensive case to analyze the annotation in the previous section, and use UseStrongAnnotation. The test cases and results are as follows. It is recommended to knock the code in practice.

package com.crab.spring.ioc.demo17;

import com.sun.xml.internal.bind.v2.model.core.ID;
import org.junit.Test;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.Arrays;

import static org.junit.Assert.*;

/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/24 13:52
 * @For me, please pay attention to the official account of crab Java notes for more technical series.
 */
public class UseStrongAnnotationTest {
    @Test
    public void test_annotated_class() {
        System.out.println("Annotation on parsing class:");
        Arrays.stream(UseStrongAnnotation.class.getAnnotations())
                .forEach(System.out::println);
    }
    // Annotation on parsing class:
    // @com.crab.spring.ioc.demo17.StrongAnnotation(value = used on class, elementType=TYPE)

    @Test
    public void test_annotated_class_type_parameter() {
        TypeVariable<Class<UseStrongAnnotation>>[] typeParameters = UseStrongAnnotation.class.getTypeParameters();
        for (TypeVariable<Class<UseStrongAnnotation>> typeParameter : typeParameters) {
            System.out.println(typeParameter.getName() + " 1.8 Annotation information of variable parameter or variable name:");
            Annotation[] annotations = typeParameter.getAnnotations();
            Arrays.stream(annotations).forEach(System.out::println);
        }
    }
    // T0 1.8 annotation information of variable parameter or variable name:
    // @com.crab.spring.ioc.demo17.StrongAnnotation(value = used on type parameter T0, elementType=TYPE_PARAMETER)
    // T1 1.8 annotation information of variable parameter or variable name:
    // @com.crab.spring.ioc.demo17.StrongAnnotation(value = used on type name T1, elementType=TYPE_USE)

    @Test
    public void test_annotated_field() throws NoSuchFieldException {
        Field field = UseStrongAnnotation.class.getDeclaredField("field");
        Arrays.stream(field.getAnnotations()).forEach(System.out::println);
    }
    // @com.crab.spring.ioc.demo17.StrongAnnotation(value = used on field, elementType=FIELD)
    

    @Test
    public void test_annotated_constructor() {
        Constructor<?> constructor = UseStrongAnnotation.class.getDeclaredConstructors()[0];
        for (Annotation annotation : constructor.getAnnotations()) {
            System.out.println(annotation);
        }
    }
    // @com.crab.spring.ioc.demo17.StrongAnnotation(value = construction method, elementType=CONSTRUCTO)

    @Test
    public void test_annotated_normal_method() throws NoSuchMethodException {
        Method method = UseStrongAnnotation.class.getDeclaredMethod("m", String.class);
        System.out.println("Method notes:");
        for (Annotation annotation : method.getAnnotations()) {
            System.out.println(annotation);
        }
        System.out.println("Method Parameter annotation:");
        Parameter[] parameters = method.getParameters();
        for (Parameter parameter : parameters) {
            System.out.println(parameter.getName() + " Parameter notes:");
            for (Annotation annotation : parameter.getAnnotations()) {
                System.out.println(annotation);
            }
        }
    }
    // Method notes:
    // @com.crab.spring.ioc.demo17.StrongAnnotation(value = for common methods, elementType=METHOD)
    // Method Parameter annotation:
    // name Parameter annotation:
    // @com.crab.spring.ioc.demo17.StrongAnnotation(value = method parameter, elementType=PARAMETER)

    @Test
    public void test_annotated_map_type() throws NoSuchFieldException {
        Field field = UseStrongAnnotation.class.getDeclaredField("map");
        // Returns a Type object that represents the declared Type of the Field represented by this Field object.
        // If Type is a parameterized Type, the returned Type object must accurately reflect the actual Type parameters used in the source code.
        Type genericType = field.getGenericType();
        // Gets or returns an array of Type objects representing actual Type parameters of this Type
        Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();

        // Returns an AnnotatedType object that represents using a type to specify the declaration type of the Field represented by this Field.
        AnnotatedType annotatedType = field.getAnnotatedType();
        // Gets an array of possible annotated actual type parameters for this parameterized type
        AnnotatedType[] annotatedActualTypeArguments =
                ((AnnotatedParameterizedType) annotatedType).getAnnotatedActualTypeArguments();
        int index = 0;
        for (AnnotatedType annotatedActualTypeArgument : annotatedActualTypeArguments) {
            Type actualTypeArgument = actualTypeArguments[index++];
            System.out.println(annotatedActualTypeArgument.getType());
            System.out.println(actualTypeArgument.getTypeName() + " Notes on type:");
            for (Annotation annotation : annotatedActualTypeArgument.getAnnotations()) {
                System.out.println(annotation);
            }
        }

    }
    // T0 1.8 annotation information of variable parameter or variable name:
    // @com.crab.spring.ioc.demo17.StrongAnnotation(value = used on type parameter T0, elementType=TYPE_PARAMETER)
    // T1 1.8 annotation information of variable parameter or variable name:
    // @com.crab.spring.ioc.demo17.StrongAnnotation(value = used on type name T1, elementType=TYPE_USE)


}

@The Inherited implementation subclass inherits the annotation of the parent class

@Inherited indicates that the annotation type is automatically inherited. Note that the annotation for the parent class of is invalid

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

Let's take a look at a case. There are inheritable annotations on the parent class and interface. Observe the annotations on the subclasses.

/**
 * Test the inheritance of parent annotation
 * Note: it is a class, not an interface, and the interface is invalid
 * @author zfd
 * @version v1.0
 * @date 2022/1/24 17:15
 * @For me, please pay attention to the official account of crab Java notes for more technical series.
 */
public class TestInherited {

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @interface Annotation1{}

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @interface Annotation2{}


    @Annotation1
    interface Interface1{}
    @Annotation2
    static class SupperClass{}

    // Inherit supplerclass to implement Interface1 and observe its annotation inheritance
    static class SubClass extends SupperClass implements Interface1{}

    public static void main(String[] args) {
        for (Annotation annotation : SubClass.class.getAnnotations()) {
            System.out.println(annotation);
        }
    }
    // output
    // @com.crab.spring.ioc.demo17.TestInherited$Annotation2()
    // Only the parent class annotation is inherited, and the annotation on the interface cannot be inherited
}

@Repeatable repeat annotation

Under normal circumstances, multiple duplicate tags with the same annotation cannot be used on the same target. If the user-defined annotation needs to implement Repeatable annotation, the annotation type declared by @ Repeatable can be Repeatable when defining.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    // Specifies the container annotation type
    Class<? extends Annotation> value();
}

Simulate @ ComponentScan @ComponentScans to provide a case.

/**
 * Test @ Repeatable annotation reuse
 * @author zfd
 * @version v1.0
 * @date 2022/1/24 17:30
 * @For me, please pay attention to the official account of crab Java notes for more technical series.
 */
public class TestRepeatable {
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Repeatable(ComponentScans.class)
    @interface  ComponentScan{}

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface ComponentScans{
        // Note: the value parameter must be defined, and its type is the array type of child repeated annotation
        ComponentScan[] value();
    }

    // Repeat annotation method 1
    @ComponentScan
    @ComponentScan
    static class MyComponent{}

    // Repeat annotation method 2
    @ComponentScans({@ComponentScan, @ComponentScan})
    static class MyComponentB{}

    public static void main(String[] args) {
        for (Annotation annotation : MyComponent.class.getAnnotations()) {
            System.out.println(annotation);
        }
        for (Annotation annotation : MyComponentB.class.getAnnotations()) {
            System.out.println(annotation);
        }
    }
    // output
    // @com.crab.spring.ioc.demo17.TestRepeatable$ComponentScans(value=[@com.crab.spring.ioc.demo17
    // .TestRepeatable$ComponentScan(), @com.crab.spring.ioc.demo17.TestRepeatable$ComponentScan()])
    // @com.crab.spring.ioc.demo17.TestRepeatable$ComponentScans(value=[@com.crab.spring.ioc.demo17
    // .TestRepeatable$ComponentScan(), @com.crab.spring.ioc.demo17.TestRepeatable$ComponentScan()])

}

Annotation enhancement of @ AliasFor in Spring

The definition parameters of annotation cannot be inherited. For example, annotation solution A has annotation B, but it is impossible to set the parameters of A in the process of using annotation B in target class C.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationA {
    String name() default "";
    int value() default -1;
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@AnnotationA
public @interface AnnotationB {
    String name() default "";
    int value() default 1;
    String aliasForName() default "";
}

@AnnotationB(name = "xxx", value = 1) // Unable to set parameter value for AnnotiaonA
public class ClassC {
}

Spring provides @ AliasFor meta annotation, which is used to declare the alias of annotation attribute. The main usage scenarios are as follows:

  • Explicit aliases in annotations: in a single annotation, @ AliasFor can be declared on a pair of attributes to indicate that they are interchangeable aliases
  • Explicit alias of attribute in meta annotation: if the annotation attribute of @ AliasFor is set to an annotation different from the annotation declaring it, the attribute is interpreted as the alias of the attribute in the meta annotation (i.e. explicit meta annotation attribute override). This allows precise control over the attributes covered in the annotation hierarchy.
  • Implicit aliases in annotations: if one or more attributes in an annotation are overridden (directly or passed) by attributes declared as the same meta annotation attributes, these attributes are treated as a set of implicit aliases for each other, resulting in behavior similar to the explicit aliases in the annotation.

The source code is simple

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface AliasFor {

	@AliasFor("attribute")
	String value() default "";

	@AliasFor("value")
	String attribute() default "";

	// The Annotation type that declares the alias attribute. The default is Annotation, which means that the alias attribute is declared in the same Annotation as this attribute.
	Class<? extends Annotation> annotation() default Annotation.class;

}

Comprehensive case

To transform annotation B with @ AliasFor.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@AnnotationA
public @interface AnnotationB {
    // Annotation annotation B internal explicit alias
    @AliasFor(value = "aliasForName")
    String name() default "";

    int value() default 1;

    // Annotation annotation B internal explicit alias
    @AliasFor(annotation = AnnotationB.class, attribute = "name")
    String aliasForName() default "";

    // Meta annotation annotation a attribute name explicit alias
    @AliasFor(annotation = AnnotationA.class, value = "name")
    String aliasForAnnotationAName() default "";

    // Meta annotation AnnotationA property name explicit alias 2
    @AliasFor(annotation = AnnotationA.class, value = "name")
    String aliasForAnnotationAName2() default "";

    // Meta annotation annotation a attribute value explicit alias
    @AliasFor(annotation = AnnotationA.class, value = "value")
    int aliasForAnnotationAValue() default -1;
}

Use AnnotationB annotation. Note: only one of the properties that are mutually aliased can be set when setting, otherwise setting more than one will report an error.

@AnnotationB(value = 100,
        name = "xx",
        aliasForAnnotationAName = "a1",
        aliasForAnnotationAValue = -100
)
public class ClassC2 {
    public static void main(String[] args) {
        //spring provides a tool class AnnotatedElementUtils for finding annotations
        System.out.println(AnnotatedElementUtils.getMergedAnnotation(ClassC2.class, AnnotationB.class));
        System.out.println(AnnotatedElementUtils.getMergedAnnotation(ClassC2.class, AnnotationA.class));
    }
}

The output result shows that AnnotationB successfully sets the attribute in AnnotationA through the alias.

@com.crab.spring.ioc.demo17.AnnotationB(aliasForAnnotationAName=a1, aliasForAnnotationAName2=a1, aliasForAnnotationAValue=-100, aliasForName=xx, name=xx, value=100)
@com.crab.spring.ioc.demo17.AnnotationA(name=a1, value=-100)

summary

This paper explains the concept of annotation in detail, how to define annotation, use annotation and obtain annotation; It also introduces the use of meta annotations @ Target, @ Retention, @ Inherited, @ Repeatable; It focuses on the enhanced processing of setting aliases for meta annotation attributes with @ AliasFor annotation in Spring.

Source address of this article: https://github.com/kongxubihai/pdf-spring-series/tree/main/spring-series-ioc/src/main/java/com/crab/spring/ioc/demo17
Knowledge sharing, please indicate the source of reprint. There is no order in learning, and the one who reaches is the first!

Keywords: Java

Added by bouwob on Thu, 10 Mar 2022 10:13:00 +0200