From Java annotation introduction to mastery, this article is enough

Keywords: Java Attribute Spring xml

Reading this article suggests looking at the table of contents first to grasp the overall context. It's better to have used or customized annotations. Even if you haven't used annotations, the examples in this article must be typed by yourself. This article will take you a step further to uncover the true face of annotations. If you don't understand, you can beat me<

1, Why learn annotation?

In daily development, annotations defined by others or various frameworks are basically used, such as some commonly used annotations in Spring framework: @ Controller, @ Service, @ RequestMapping, to realize some functions, but we don't know how to implement them. So if we want to learn the implementation principle of these frameworks, annotation is a point we must know. Secondly, annotations can be used to define some implementations. For example, adding a user-defined annotation to a method can automatically record and print the method log, which can also show enough constraints. So if you want to get to the top of your life, make better use of the framework, or you want to be a bit more aggressive and stand out from the team, then learning annotation is the premise.

2, What is an annotation?

​ In Java, annotation is actually a label written on an interface, class, property, or method, or a special form of annotation. Unlike ordinary / / or / * * / annotation, ordinary annotation is just a comment, while annotation can be read and operated by reflection when the code is running. If reflection or other checks are not used, annotation does not Any real function will not affect the normal operation of the program.

​ For example, @ Override is an annotation. Its purpose is to tell readers (developers and compilers) that this method rewrites the method of the parent class, which is just a flag for developers, while the compiler will do more things. If the compiler finds that the annotation of the method is marked, it will check whether the method actually overwrites the method of the parent class, if not That is to cheat his feelings. Don't talk nonsense. When compiling, I will directly report a mistake to you, which is merciless. If you don't add @ Override annotation, the program can run normally, but there is no static check. Originally, you wanted to overwrite the hello method of the parent class, but you wrote the he110 method, which would be awkward.

Annotations annotated in the spring framework will affect the operation of the program, because the corresponding annotations are operated by reflection within spring.

The above statement is for the convenience of understanding, so here's a little more formal: annotation provides a way to set metadata for program elements, which is still the same. Program elements refer to interfaces, classes, properties and methods, which are all program elements. What is metadata? data about data), for example, there is a sm.png file on the system, which is the data we really need. The attributes of this file can be called sm.png metadata, which is used to describe the creation time, modification time, resolution and other information of the PNG file. Whether there is such information or not, it does not affect its nature as a picture Open with the picture software.

  • Metadata is additional information added to program elements such as methods, fields, classes, and packages. Annotation is a carrier form
  • Annotation can not directly interfere with the operation of program code

3, Why use annotations?

Take Spring as an example. The previous version of Spring configured the whole framework in the form of XML files. A reduced version of the configuration file is as follows

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans">
    <!-- Configure things Manager -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- Configuration annotation driven things Management -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

In the xml file, you can define Spring managed beans, things aspects, etc., which is very popular in that year. The advantage is that the configuration information of the whole project is centralized in one file, which is convenient for management and centralized configuration. The disadvantage is also obvious. When there is a lot of configuration information, the configuration file will become larger and larger, and it is not easy to view and manage, especially when multiple people cooperate to develop, it will cause certain mutual interference.

Now it is advocated to decouple, lightweight or miniaturize, so annotation complies with this demand. Each package or module can use annotation on internal methods or classes to achieve specified functions, and it is often easy to use and easy to understand. The disadvantage is that it is not convenient for unified management. If you need to modify a certain type of function, you need to search as a whole and modify one by one. It is a decentralized existence in every corner.

Here's an extension. Spring annotation replaces the previous spinning xml file. Is spring's xml also a kind of metadata? Yes, spring's configuration file xml is also a representation of metadata. However, xml is centralized metadata and does not need to be bound with code. Annotation is a decentralized metadata setting method.

4, The role of annotations

As Java development has almost used some frameworks, I believe everyone has some experience of the role of annotations. Here are a few more words to deepen the impression. A comment is basically a comment label. From the perspective of developers, we can interpret the function of this class / method / attribute and how to use it. From the perspective of framework, we can parse the annotation itself and its attributes to realize various functions. From the perspective of compiler, we can perform some pre checks (@ Override) and suppress warnings (@ SuppressWarnings).

  • Used as a specific tag to tell the compiler something
  • Dynamic processing at compile time, such as dynamic code generation
  • Dynamic processing at runtime, as the carrier of additional information, such as obtaining annotation information

5, Classification of notes

Generally, annotations fall into three categories

  • Meta annotation - a java built-in annotation that indicates the scope of use, life cycle, and so on.
  • Standard annotation – the basic annotation provided by Java to indicate that the expired element / method / indication suppression warning is to copy the parent method.
  • Custom annotations - third party defined annotations, meaning and functions are defined and implemented by third parties.

5.1 notes

Annotation used to define annotation, usually used in the definition of annotation, indicating the scope of use, effective range, etc. Meta XX represents the most basic and primitive thing. Therefore, meta annotation is the most basic and non decomposable annotation. We can't change it but use it to define custom annotation. There are five meta annotations: @ Retention, @ Target, @ Documented, @ Inherited, @ Repeatable. The most commonly used are @ Retention and @ Target.

@Retention ★★★★★

Chinese translation for reserved meaning, indicating the life cycle of custom annotation

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

The main cycle from Java code writing to running is SOURCE file → CLASS file → run-time data, @ Retention marks the stage to which the information of user-defined annotation should be kept, and the corresponding value values are SOURCE → CLASS → RUNTIME.

  • SOURCE source code java file, there is no such information in the generated class file
  • Annotations will be kept in the class file, but not when the jvm loads the runtime
  • When RUNTIME is running, if you want to use reflection to get annotation information, you need to use RUNTIME. Reflection is reflected during RUNTIME

Example: when the RentionPolicy value is SOURCE, the annotation information will not be retained in the class file, while when the value is class, the annotation information is retained in the class decompilation file

Purpose of each life cycle:

  1. Source: one of the simplest uses is to customize an annotation, such as @ ThreadSafe, to identify a class with thread safety, just like the annotation, but it's more eye-catching.
  2. Class: what's the use of this? I think it's mainly for marking, but I haven't done any experiments yet. For example, marking an @ Proxy will generate the corresponding Proxy class when the JVM loads.
  3. Runtime: if the reflection is executed in the runtime, then only the runtime's life cycle will remain in the runtime, and then it can be read by reflection, which is also the most commonly used one.

@Target ★★★★★

It describes the usage scope of user-defined annotation and the Java elements (class, method, attribute, local attribute, parameter, etc.) on which user-defined annotation is allowed )

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

Value is an array that can have multiple values, indicating that the same annotation can be used to annotate different elements at the same time. The value is as follows

value Explain
TYPE Class, interface, annotation, enumeration
FIELD attribute
MEHOD Method
PARAMETER Method parameter
CONSTRUCTOR Constructor
LOCAL_VARIABLE Local variables (such as loop variables, catch parameters)
ANNOTATION_TYPE annotation
PACKAGE package
TYPE_PARAMETER Generic parameter jdk1.8
TYPE_USE Any element jdk1.8

Example: to customize an annotation @ MyAnnotation1 to be used on a class or method, you can define it as follows

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAnnotation {
    String description() default "";
}


@MyAnnotation
public class AnnotationTest {

    // @If MyAnnotation is used on properties, an error will be reported
    public String name;

    @MyAnnotation
    public void test(){}

}

@Inherited ★★

Whether it can be Inherited by the subclass of the annotation class. The annotation modified by @ Inherited is Inherited. When a custom annotation is annotated to a class, the subclass of the class will inherit the custom annotation. It should be noted that only when the child class inherits the parent class can the annotation be Inherited. The class implementation interface, or the interface inheritance interface, cannot obtain the annotation Declaration on the parent interface. The correct example is as follows (get annotation by reflection)

@Repeatable ★★

Whether dimensions can be repeated. This annotation is actually a syntactic sugar. Before jdk1.8, there was a way to annotate repeatedly, that is, to use array attributes (as will be mentioned in the user-defined annotation). Here's an example. Although we annotate multiple @ MyAnnotations, we will actually return one @ MyAnnotations to us. Java helps us put duplicate annotations into an array attribute, so it's just a syntax sugar.

@Documented ★

Whether or not it is reflected in the generated Javadoc document. After the annotation is marked, the generated Javadoc will contain the annotation. This will not be demonstrated here.

5.2 standard notes

There are three standard notes

  • @Override marks a method that overrides the parent method
  • @Deprecated marks an element as expired. Avoid using

The supported element types are: constructor, field, local "variable, method, package, parameter, type

  • @SuppressWarnings does not output the corresponding compilation warnings

It is common and fixed. Here is a simple example

@SuppressWarnings(value = {"unused", "rawtypes"})
public class StandardDemo extends Parent{

    @Override
    public void sayHello() {
        // unused declared list but not used
        // rawtypes creates generic classes without specifying element types
        List list = new ArrayList();
    }

    @Deprecated
    public void walk() {

    }

}

5.3 user defined notes

Annotation definition format

public @interface annotation name{
  Modifier return value property name () default value;
  Modifier return value property name () default value;
}

First of all, the modifier of annotation is generally public. Defining annotation is generally used by three parties. What's the meaning of not public? The defined type uses @ interface. It can be guessed that there are some unclear relations with the interface. In fact, the annotation is an interface. When the program is running, the JVM will generate the corresponding proxy class for it.

Then the internal definition, which is a bit different, is a method. It also has a default value. It's a property. It's also followed by a bracket. Personally, I prefer to call it the interface method with default return value. We will further recognize its true face through later learning. The internal modifier can only be public, even if it is not written, it will be public by default, because it is essentially an interface, and the default access rights of interface methods are public.

Annotation cannot inherit or implement other classes or interfaces. It is a metadata in itself. It is really unnecessary.

The supported types of return values are as follows

  • Basic type int float Boolean byte double char log short
  • String
  • Class
  • Enum
  • Annotation
  • Array types of all the above types

Define a simple interface example

// Keep until run time
@Retention(RetentionPolicy.RUNTIME)
// Can be added to methods or classes
@Target(value = {ElementType.TYPE,ElementType.METHOD})
public @interface RequestMapping {
    public String method() default "GET";
    public String path();
    public boolean required();
}

Now let's see if it's an interface

First compile the annotation javac RequestMapping.java to generate the corresponding RequestMapping.class file, and then decompile it to javap-v requestmapping. The output is as follows

// ...public interface RequestMapping extends java.lang.annotation.Annotation
  //...public abstract java.lang.String method();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_ABSTRACT
    AnnotationDefault:
      ③default_value: s#7
  public abstract java.lang.String path();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_ABSTRACT

  public abstract boolean required();
    descriptor: ()Z
    flags: ACC_PUBLIC, ACC_ABSTRACT
}
//...

① From here, we can see that annotation is an interface in essence and inherits java.lang.annotation.Annotation

② ③ it is verified here that the internal definition is actually a method with default value

6, Use reflection operation annotation

Reflection of the relevant knowledge will not be introduced much, and students who don't know much can read another blog of mine Java reflection learning summary . Reflection can obtain Class objects, and then obtain instances such as Constructor, Field, and Method. Click the source code structure to find that Class, Constructor, Field, and Method all implement AnnotatedElement interface. The Method of AnnotatedElement interface is as follows

// Determines whether the element contains the specified annotation, and returns true if it does
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
// Return the corresponding annotation on the element, if not null
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
// Returns all comments on the element, or an empty array if there are no comments
Annotation[] getAnnotations();
// Returns the annotation of the specified type, if no empty array is returned
T[] getAnnotationsByType(Class<T> annotationClass)
// Returns the annotation of the specified type. If no empty array is returned, only the annotation of direct annotation is included, and the annotation of inherited is not included
T getDeclaredAnnotation(Class<T> annotationClass)
// Returns the annotation of the specified type. If no empty array is returned, only the annotation of direct annotation is included, and the annotation of inherited is not included
T[] getDeclaredAnnotationsByType
// Returns all comments on the element. If there are no comments, an empty array will be returned, containing only the comments directly annotated, not inherited
Annotation[] getDeclaredAnnotations();

This means that all the above elements can obtain the annotation annotated on the element through reflection.

show you the code

// package-info.java
@AnyAnnotation(order = 0, desc = "package")
package demo.annotation.reflect;

// AnyAnnotation.java
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.PACKAGE, ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD,
        ElementType.LOCAL_VARIABLE, ElementType.PARAMETER})
public @interface AnyAnnotation {
    int order() default 0;
    String desc() default "";
}

// ReflectAnnotationDemo.java
@AnyAnnotation(order = 1, desc = "I'm a comment on the class")
public class ReflectAnnotationDemo {

    @AnyAnnotation(order = 2, desc = "I am a member property")
    private String name;

    @AnyAnnotation(order = 3, desc = "I'm a builder")
    public ReflectAnnotationDemo(@AnyAnnotation(order = 4, desc = "I'm the constructor parameter") String name) {
        this.name = name;
    }

    @AnyAnnotation(order = 45, desc = "I am the method.")
    public void method(@AnyAnnotation(order = 6, desc = "I am the method parameter") String msg) {
        @AnyAnnotation(order = 7, desc = "I'm a method internal variable") String prefix = "I am ";
        System.out.println(prefix + msg);
    }

    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
        Class<ReflectAnnotationDemo> clazz = ReflectAnnotationDemo.class;
        // Get the annotation on the package and declare it in the package-info.java file
        Package packagee = Package.getPackage("demo.annotation.reflect");
        printAnnotation(packagee.getAnnotations());
        // Get annotation on class
        Annotation[] annotations = clazz.getAnnotations();
        printAnnotation(annotations);
        // Get member property annotation
        Field name = clazz.getDeclaredField("name");
        Annotation[] annotations1 = name.getAnnotations();
        printAnnotation(annotations1);
        //Get comments on the constructor
        Constructor<ReflectAnnotationDemo> constructor = clazz.getConstructor(String.class);
        AnyAnnotation[] annotationsByType = constructor.getAnnotationsByType(AnyAnnotation.class);
        printAnnotation(annotationsByType);
        // Get comments on constructor parameters
        Parameter[] parameters = constructor.getParameters();
        for (Parameter parameter : parameters) {
            Annotation[] annotations2 = parameter.getAnnotations();
            printAnnotation(annotations2);
        }
        // Get annotation on method
        Method method = clazz.getMethod("method", String.class);
        AnyAnnotation annotation = method.getAnnotation(AnyAnnotation.class);
        printAnnotation(annotation);
        // Get comments on method parameters
        Parameter[] parameters1 = method.getParameters();
        for (Parameter parameter : parameters1) {
            printAnnotation(parameter.getAnnotations());
        }
        // Get comments on local variables
        /**
         * After checking some data, the annotation of local variables cannot be obtained, and the annotation of local variables is only kept in the Class file, which is not available at runtime.
         * This is more for bytecode tools. For example, lombok can embed the compilation process and detect that there is a corresponding annotation converted into the corresponding code,
         * Reflection cannot be manipulated. Of course, you can also use asm and other tools in the compiler to complete what you have to do
         */
    }

    public static void printAnnotation(Annotation... annotations) {
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
    }
}

Interested students can change the life cycle of @ AnyAnnotation's Retention to SOURCE/CLASS, and then they won't get any annotation information

7, The underlying implementation of annotation - Dynamic Proxy

First, prepare the test code as follows

@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.TYPE)
public @interface Learn {
    String name() default "default";
    int age();
}

@Learn(age = 12)
public class LearnAnnotationReflect {

    public static void main(String[] args) {
        Class<LearnAnnotationReflect> clazz = LearnAnnotationReflect.class;
 				// Judge whether there is corresponding annotation for class element
        if (!clazz.isAnnotationPresent(Learn.class)) {
            return;
        }
        // Get the corresponding annotation and print the properties
        Learn learn = clazz.getAnnotation(Learn.class);
        System.out.println(learn.name());
        System.out.println(learn.age());
    }
}

Make a breakpoint in System.out.println(learn.name()); run in Debug mode to see what the learn object is

[failed to transfer the pictures in the external link. The source station may have anti-theft chain mechanism. It is recommended to save the pictures and upload them directly (img-0d2f1GLy-1586103608775)(http://images.kimzing.com/blog/debug-annotation.png))

​ From the above screenshot, we can see that JDK has generated a proxy object named $Proxy1 for Learn, and it contains an internal member AnnotationIvocationHandler. Next, it calls $Proxy1.name() to get the value of name. Then we can see what kind of object $Proxy1 is. In jdk8, we can add the JVM parameter - Dsun.misc.ProxyGenerator.saveGeneratedFiles To save the proxy class, a later version can use - Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true to save the proxy class. The setting method in Idea is as follows

Re run the program, and you will find that there are more classes in the root directory of the project, where $Proxy1 is the agent class corresponding to the Learn annotation

When we call Learn.name(), we actually call the name method of this proxy class, as follows

    public final String name() throws  {
        try {
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

The name method of the proxy class mainly calls the invoke method of h to pass in the current object and the method element m3, m3 as follows

m3 = Class.forName("demo.annotation.runtime.Learn").getMethod("name");

In the content explained in 5.3, we decompiled the class file of the annotation, knowing that when compiling the annotation, it was actually compiled into an interface, in which the methods of the attributes we wanted to do were defined.

Then we can sort out the basic process:

  1. Through reflection, we can get the annotation @ Learn on the corresponding element. As mentioned earlier, annotation is an interface in nature, that is, the proxy object of the Learn interface is obtained.
  2. The Learn proxy object provides the corresponding Method with the same name, and internally declares the corresponding Method of the original annotation, such as method3
  3. Then the invoke method is executed through the h member attribute of the parent class of the proxy object, that is, AnnotationInvocationHandler
  4. When the AnnotationInvocationHandler is initialized, it will contain a map of memberValues. The key is the Method name, and the value is the corresponding attribute value. In the invoka, get the corresponding value from the memberValues through the name of the Method and return

Next, let's look at the information about the invoke method in the AnnotationInvocationHandler, as follows

// Current annotation type
private final Class<? extends Annotation> type;
// The related attribute set of the current annotation. key is the method name and value is the corresponding value
private final Map<String, Object> memberValues;

// The jdk will pass the corresponding attribute information
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
            this.type = var1;
            this.memberValues = var2;
    }


public Object invoke(Object var1, Method var2, Object[] var3) {
        // Method name
        String var4 = var2.getName();
        // Parameter type
        Class[] var5 = var2.getParameterTypes();
        // If it is an equals method, the corresponding method is called
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        // It is not an equals method, but it has parameters. The description is wrong. The annotated method is not allowed to have parameters
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            // Define a variable var7 default value - 1
            byte var7 = -1;
            // Different methods give different values to var7
            switch(var4.hashCode()) {
            case -1776922004:
                if (var4.equals("toString")) {
                    var7 = 0;
                }
                break;
            case 147696667:
                if (var4.equals("hashCode")) {
                    var7 = 1;
                }
                break;
            case 1444986633:
                if (var4.equals("annotationType")) {
                    var7 = 2;
                }
            }

          	// Return the processing of the corresponding method
            switch(var7) {
            case 0:
                return this.toStringImpl();
            case 1:
                return this.hashCodeImpl();
            case 2:
                return this.type;
            // The default method, which is our custom attribute method
            default:
                // Get the corresponding value from the map collection
                Object var6 = this.memberValues.get(var4);
                if (var6 == null) {
                    throw new IncompleteAnnotationException(this.type, var4);
                } else if (var6 instanceof ExceptionProxy) {
                    throw ((ExceptionProxy)var6).generateException();
                } else {
                    if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                        var6 = this.cloneArray(var6);
                    }

                    return var6;
                }
            }
        }
    }

Follow the source code debug several times to understand the overall process

8, Summary: annotate workflow

  • Assign annotation attributes in the form of key value pairs
  • The compiler checks the usage scope of annotations (writes annotation information to the attribute table of the element)
  • The runtime JVM takes out all the annotation attributes of the runtime of a single Class and stores them in the map
  • Create an AnnotationInvocationHandler instance and pass in the previous map
  • The JVM uses the JDK dynamic proxy to generate proxy classes for annotations and initialize the processor
  • Call the invoke method to return the attribute value corresponding to the annotation by passing in the method name
Published 165 original articles· Praised 189· 680000 visitors+
His message board follow

Posted by bfuzze_98 on Wed, 08 Apr 2020 07:21:34 -0700