Java: an implementation principle summary and use example of reading annotations

Keywords: Java Android Spring RESTful

Write before:

In the past, "XML" was the favorite of all major frameworks. It completed almost all the configurations in the framework in a loose coupling way. However, as the project became larger and larger, the content of "XML" became more and more complex and the maintenance cost became higher.

Therefore, someone proposed a marked high coupling configuration method, "Annotation". You can annotate methods, classes, and field properties. You can annotate almost all the places that need to be configured anyway.

The two different configuration modes of annotation and XML have been debated for many years. Each has its own advantages and disadvantages. Annotation can provide greater convenience and easy maintenance and modification, but it has a high degree of coupling, while XML is the opposite to annotation.

The pursuit of low coupling means abandoning high efficiency, and the pursuit of efficiency will inevitably encounter coupling. The purpose of this paper is no longer to distinguish between the two, but to introduce the basic contents related to annotation in the simplest language.

The essence of annotation

There is a sentence in the "java.lang.annotation.Annotation" interface to describe the "Annotation".

The common interface extended by all annotation types

All Annotation types inherit from this common interface (Annotation)

This sentence is a little abstract, but it speaks the essence of annotation. Let's look at the definition of a JDK built-in annotation:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {

}

This is the definition of annotation @ Override. In fact, it is essentially:

public interface Override extends Annotation{
    
}

Yes, the essence of Annotation is an interface that inherits the Annotation interface. In this regard, you can decompile any Annotation class, and you will get the result.

In a precise sense, an annotation is just a special annotation. If its code is not parsed, it may not even be as good as an annotation.

Annotations for parsing a class or method often take two forms: direct scanning at compile time and reflection at run time. We will talk about reflection later, and compiler scanning refers to that the compiler will detect that a class or method is modified by some annotations during the process of compiling bytecode of java code, and then it will deal with these annotations.

The typical annotation is @ Override. Once the compiler detects that a method is decorated with the @ Override annotation, the compiler will check whether the method signature of the current method really overrides a method of the parent class, that is, compare whether the parent class has the same method signature.

This situation only applies to those annotation classes that the compiler is already familiar with, such as several built-in annotations in JDK. For your customized annotations, the compiler does not know the role of your annotation and, of course, how to deal with it. It often just chooses whether to compile into bytecode files according to the scope of the annotation, that's all.

Meta annotation

"Meta Annotation" is an annotation used to modify annotation, which is usually used in the definition of annotation, for example:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {

}

This is the definition of our @ Override annotation. You can see the @ Target and @ Retention annotations, which are called meta annotations. Meta annotations are generally used to specify the life cycle and action Target of an annotation.

There are the following meta annotations in JAVA:

  • @Target: the target of the annotation
  • @Retention: the lifecycle of an annotation
  • @Documented: should annotations be included in JavaDoc documents
  • @Inherited: whether subclasses are allowed to inherit the annotation

Among them, @ Target is used to indicate the final Target of the modified annotation, that is, to indicate that your annotation is used to modify the method? Decorated class? It is also used to modify field properties.

@Target is defined as follows:

package java.lang.annotation;

/**
 * Indicates the contexts in which an annotation type is applicable. The
 * declaration contexts and type contexts in which an annotation type may be
 * applicable are specified in JLS 9.6.4.1, and denoted in source code by enum
 * constants of {@link ElementType java.lang.annotation.ElementType}.
 *
 * <p>If an {@code @Target} meta-annotation is not present on an annotation type
 * {@code T} , then an annotation of type {@code T} may be written as a
 * modifier for any declaration except a type parameter declaration.
 *
 * <p>If an {@code @Target} meta-annotation is present, the compiler will enforce
 * the usage restrictions indicated by {@code ElementType}
 * enum constants, in line with JLS 9.7.4.
 *
 * <p>For example, this {@code @Target} meta-annotation indicates that the
 * declared type is itself a meta-annotation type.  It can only be used on
 * annotation type declarations:
 * <pre>
 *    &#064;Target(ElementType.ANNOTATION_TYPE)
 *    public &#064;interface MetaAnnotationType {
 *        ...
 *    }
 * </pre>
 *
 * <p>This {@code @Target} meta-annotation indicates that the declared type is
 * intended solely for use as a member type in complex annotation type
 * declarations.  It cannot be used to annotate anything directly:
 * <pre>
 *    &#064;Target({})
 *    public &#064;interface MemberType {
 *        ...
 *    }
 * </pre>
 *
 * <p>It is a compile-time error for a single {@code ElementType} constant to
 * appear more than once in an {@code @Target} annotation.  For example, the
 * following {@code @Target} meta-annotation is illegal:
 * <pre>
 *    &#064;Target({ElementType.FIELD, ElementType.METHOD, ElementType.FIELD})
 *    public &#064;interface Bogus {
 *        ...
 *    }
 * </pre>
 *
 * @since 1.5
 * @jls 9.6.4.1 @Target
 * @jls 9.7.4 Where Annotations May Appear
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

We can pass values for this value in the following ways:

@Target(value = {ElementType.FIELD})

The annotation modified by this @ Target annotation can only be used on member fields and cannot be used to modify methods or classes. ElementType is an enumeration type with the following values:

  • ElementType.TYPE: allows modified annotations to act on classes, interfaces, and enumerations
  • ElementType.FIELD: allowed to act on attribute fields
  • ElementType.METHOD: allowed to act on Methods
  • ElementType.PARAMETER: allowed to act on method parameters
  • ElementType.CONSTRUCTOR: allows action on constructors
  • ElementType.LOCAL_VARIABLE: allows action on local variables
  • ElementType.ANNOTATION_TYPE: allowed on annotation
  • ElementType.PACKAGE: allow to act on packages

@Retention is used to indicate the life cycle of the current annotation. Its basic definitions are as follows:

package java.lang.annotation;

/**
 * Indicates how long annotations with the annotated type are to
 * be retained.  If no Retention annotation is present on
 * an annotation type declaration, the retention policy defaults to
 * {@code RetentionPolicy.CLASS}.
 *
 * <p>A Retention meta-annotation has effect only if the
 * meta-annotated type is used directly for annotation.  It has no
 * effect if the meta-annotated type is used as a member type in
 * another annotation type.
 *
 * @author  Joshua Bloch
 * @since 1.5
 * @jls 9.6.3.2 @Retention
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

Similarly, it also has a value attribute:

@Retention(value = RetentionPolicy.RUNTIME)

The RetentionPolicy here is still an enumeration type. It has the following enumeration values:

  • RetentionPolicy.SOURCE: the current annotation is visible during compilation and will not be written to the class file
  • RetentionPolicy.CLASS: the class is discarded in the class loading phase and written to the class file
  • RetentionPolicy.RUNTIME: it is permanently saved and can be obtained by reflection

@The Retention annotation specifies the life cycle of the modified annotation. One is visible only during the compilation period and will be discarded after compilation. The other is compiled into the class file by the compiler. Whether it is a class, method, or even field, they all have attribute tables. JAVA virtual machine also defines several annotation attribute tables to store annotation information, However, this visibility cannot be brought to the method area, and will be discarded when the class is loaded. The last one is permanent visibility.

The remaining two types of annotations are not used much and are relatively simple. They are not introduced in detail here. You only need to know their respective functions.

@The annotation modified by the Documented annotation will be saved into the doc document when we package the JavaDoc document, otherwise it will be discarded during packaging.

@The annotation modified by Inherited annotation is inheritable, that is, our annotation modifies a class, and the subclass of this class will automatically inherit the annotation of the parent class.

Three built-in annotations of JAVA

In addition to the above four meta annotations, JDK also predefined three other annotations for us, which are:

  • @Override
  • @Deprecated
  • @SuppressWarnings

@The Override annotation must be familiar to everyone. Its definition is as follows:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

It doesn't have any properties, so it can't store any other information. It can only act on methods and will be discarded after compilation.

So you see, it is a typical "markup Annotation". Only the compiler can know that once the compiler detects that the annotation is modified on a method in the process of compiling java files into bytecode, it will match whether a function with the same method signature in the parent class. If not, it will not be compiled.

@The basic definition of Deprecated is as follows:

package java.lang;

import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;

/**
 * A program element annotated &#64;Deprecated is one that programmers
 * are discouraged from using, typically because it is dangerous,
 * or because a better alternative exists.  Compilers warn when a
 * deprecated program element is used or overridden in non-deprecated code.
 *
 * @author  Neal Gafter
 * @since 1.5
 * @jls 9.6.3.6 @Deprecated
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

It is still a kind of "markup Annotation", which exists permanently and can modify all types. Its purpose is to mark that the current class, method or field is no longer recommended. It may be deleted in the next JDK version.

Of course, the compiler does not force you to do anything, but tells you that the JDK no longer recommends using the current method or class, and recommends that you use a substitute.

@SuppressWarnings is mainly used to suppress java warnings. Its basic definition is as follows:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    /**
     * The set of warnings that are to be suppressed by the compiler in the
     * annotated element.  Duplicate names are permitted.  The second and
     * successive occurrences of a name are ignored.  The presence of
     * unrecognized warning names is <i>not</i> an error: Compilers must
     * ignore any warning names they do not recognize.  They are, however,
     * free to emit a warning if an annotation contains an unrecognized
     * warning name.
     *
     * <p> The string {@code "unchecked"} is used to suppress
     * unchecked warnings. Compiler vendors should document the
     * additional warning names they support in conjunction with this
     * annotation type. They are encouraged to cooperate to ensure
     * that the same names work across multiple compilers.
     * @return the set of warnings to be suppressed
     */
    String[] value();
}

It has a value attribute that requires you to actively pass the value. What does this value mean? This value represents the warning type that needs to be suppressed. For example:

public static void main(String[] args) {
    Date date = new Date(2018, 7, 11);
}

For such a piece of code, the compiler will report a warning when the program starts.

Warning: (8, 21) Java: Date(int,int,int) in java.util.date is out of date

If we don't want the compiler to check the outdated methods in the code when the program starts, we can use the @ SuppressWarnings annotation and pass a parameter value to its value attribute to suppress the compiler's check.

@SuppressWarning(value = "deprecated")
public static void main(String[] args) {
    Date date = new Date(2018, 7, 11);
}

In this way, you will find that the compiler no longer checks whether there are outdated method calls under the main method, which suppresses the compiler's check for such warnings.

Of course, there are many warning types in JAVA. They all correspond to a string. You can suppress the inspection of this kind of warning type by setting the value of the value attribute.

The related content of user-defined annotation will not be repeated. It is relatively simple. You can customize an annotation through a syntax similar to the following.

public @interface InnotationName{
    
}

Of course, when customizing annotations, you can also selectively use meta annotations for modification, so that you can more specifically specify the life cycle, scope and other information of your annotations.

Annotation and reflection

In the above content, we introduced the details of Annotation use, and also briefly mentioned that "the essence of Annotation is an interface that inherits the Annotation interface". Now let's see what the essence of Annotation is from the virtual machine level.

First, we customize an annotation type:

Here we specify that the Hello annotation can only modify fields and methods, and the annotation will survive forever so that we can get it by reflection.

As we said before, the virtual machine specification defines a series of attribute tables related to annotations, that is, if fields, methods or classes themselves are modified by annotations, they can be written into bytecode files. There are several types of attribute tables:

  • Runtime visible annotations: annotations visible at runtime
  • Runtime invisibleannotations: annotations that are not visible at runtime
  • RuntimeVisibleParameterAnnotations: method parameter annotations visible at run time
  • RuntimeInVisibleParameterAnnotations: method parameter annotations that are not visible at run time
  • AnnotationDefault: the default value of the annotation class element

The purpose of showing you the attribute tables related to these annotations of the virtual machine is to make you build a basic impression on how annotations are stored in bytecode files as a whole.

Therefore, for a Class or interface, the Class class provides the following methods for reflecting annotations.

  • getAnnotation: returns the specified annotation
  • isAnnotationPresent: determines whether the current element is modified by the specified annotation
  • getAnnotations: returns all annotations
  • Getdeclaraedannotation: returns the specified annotation of this element
  • Getdeclaraedannotations: returns all annotations of this element, excluding those inherited from the parent class

Methods and related reflection annotations in fields are basically similar. We won't repeat them here. Let's see a complete example below.

First, set a virtual machine startup parameter to capture the JDK dynamic proxy class.

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

Then the main function:

As we said, Annotation essentially inherits the interface of the Annotation interface. When you obtain an Annotation class instance through reflection, that is, our getAnnotation method here, JDK actually generates a proxy class that implements our Annotation (Interface) through the dynamic proxy mechanism.

After running the program, we will see such a proxy class in the output directory. After decompilation, it is as follows:

The proxy class implements the interface Hello and overrides all its methods, including the value method and the method inherited from the Annotation interface.

Who is the key InvocationHandler instance?

AnnotationInvocationHandler is a JAVA Handler for handling annotations. The design of this class is also very interesting. Here is a memberValues, which is a Map key value pair. The key is the name of our annotation attribute, and the value is the value to which the attribute was originally assigned.

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package sun.reflect.annotation;

import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.io.ObjectInputStream.GetField;
import java.lang.annotation.Annotation;
import java.lang.annotation.AnnotationFormatError;
import java.lang.annotation.IncompleteAnnotationException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import sun.misc.Unsafe;

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
    private static final long serialVersionUID = 6182022883658399397L;
    private final Class<? extends Annotation> type;
    private final Map<String, Object> memberValues;
    private transient volatile Method[] memberMethods = null;

    AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
        Class[] var3 = var1.getInterfaces();
        if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
            this.type = var1;
            this.memberValues = var2;
        } else {
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        }
    }

    public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            byte var7 = -1;
            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;
                }
            }

            switch(var7) {
            case 0:
                return this.toStringImpl();
            case 1:
                return this.hashCodeImpl();
            case 2:
                return this.type;
            default:
                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;
                }
            }
        }
    }

    private Object cloneArray(Object var1) {
        Class var2 = var1.getClass();
        if (var2 == byte[].class) {
            byte[] var6 = (byte[])((byte[])var1);
            return var6.clone();
        } else if (var2 == char[].class) {
            char[] var5 = (char[])((char[])var1);
            return var5.clone();
        } else if (var2 == double[].class) {
            double[] var4 = (double[])((double[])var1);
            return var4.clone();
        } else if (var2 == float[].class) {
            float[] var11 = (float[])((float[])var1);
            return var11.clone();
        } else if (var2 == int[].class) {
            int[] var10 = (int[])((int[])var1);
            return var10.clone();
        } else if (var2 == long[].class) {
            long[] var9 = (long[])((long[])var1);
            return var9.clone();
        } else if (var2 == short[].class) {
            short[] var8 = (short[])((short[])var1);
            return var8.clone();
        } else if (var2 == boolean[].class) {
            boolean[] var7 = (boolean[])((boolean[])var1);
            return var7.clone();
        } else {
            Object[] var3 = (Object[])((Object[])var1);
            return var3.clone();
        }
    }

    private String toStringImpl() {
        StringBuilder var1 = new StringBuilder(128);
        var1.append('@');
        var1.append(this.type.getName());
        var1.append('(');
        boolean var2 = true;
        Iterator var3 = this.memberValues.entrySet().iterator();

        while(var3.hasNext()) {
            Entry var4 = (Entry)var3.next();
            if (var2) {
                var2 = false;
            } else {
                var1.append(", ");
            }

            var1.append((String)var4.getKey());
            var1.append('=');
            var1.append(memberValueToString(var4.getValue()));
        }

        var1.append(')');
        return var1.toString();
    }

    private static String memberValueToString(Object var0) {
        Class var1 = var0.getClass();
        if (!var1.isArray()) {
            return var0.toString();
        } else if (var1 == byte[].class) {
            return Arrays.toString((byte[])((byte[])var0));
        } else if (var1 == char[].class) {
            return Arrays.toString((char[])((char[])var0));
        } else if (var1 == double[].class) {
            return Arrays.toString((double[])((double[])var0));
        } else if (var1 == float[].class) {
            return Arrays.toString((float[])((float[])var0));
        } else if (var1 == int[].class) {
            return Arrays.toString((int[])((int[])var0));
        } else if (var1 == long[].class) {
            return Arrays.toString((long[])((long[])var0));
        } else if (var1 == short[].class) {
            return Arrays.toString((short[])((short[])var0));
        } else {
            return var1 == boolean[].class ? Arrays.toString((boolean[])((boolean[])var0)) : Arrays.toString((Object[])((Object[])var0));
        }
    }

    private Boolean equalsImpl(Object var1) {
        if (var1 == this) {
            return true;
        } else if (!this.type.isInstance(var1)) {
            return false;
        } else {
            Method[] var2 = this.getMemberMethods();
            int var3 = var2.length;

            for(int var4 = 0; var4 < var3; ++var4) {
                Method var5 = var2[var4];
                String var6 = var5.getName();
                Object var7 = this.memberValues.get(var6);
                Object var8 = null;
                AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
                if (var9 != null) {
                    var8 = var9.memberValues.get(var6);
                } else {
                    try {
                        var8 = var5.invoke(var1);
                    } catch (InvocationTargetException var11) {
                        return false;
                    } catch (IllegalAccessException var12) {
                        throw new AssertionError(var12);
                    }
                }

                if (!memberValueEquals(var7, var8)) {
                    return false;
                }
            }

            return true;
        }
    }

    private AnnotationInvocationHandler asOneOfUs(Object var1) {
        if (Proxy.isProxyClass(var1.getClass())) {
            InvocationHandler var2 = Proxy.getInvocationHandler(var1);
            if (var2 instanceof AnnotationInvocationHandler) {
                return (AnnotationInvocationHandler)var2;
            }
        }

        return null;
    }

    private static boolean memberValueEquals(Object var0, Object var1) {
        Class var2 = var0.getClass();
        if (!var2.isArray()) {
            return var0.equals(var1);
        } else if (var0 instanceof Object[] && var1 instanceof Object[]) {
            return Arrays.equals((Object[])((Object[])var0), (Object[])((Object[])var1));
        } else if (var1.getClass() != var2) {
            return false;
        } else if (var2 == byte[].class) {
            return Arrays.equals((byte[])((byte[])var0), (byte[])((byte[])var1));
        } else if (var2 == char[].class) {
            return Arrays.equals((char[])((char[])var0), (char[])((char[])var1));
        } else if (var2 == double[].class) {
            return Arrays.equals((double[])((double[])var0), (double[])((double[])var1));
        } else if (var2 == float[].class) {
            return Arrays.equals((float[])((float[])var0), (float[])((float[])var1));
        } else if (var2 == int[].class) {
            return Arrays.equals((int[])((int[])var0), (int[])((int[])var1));
        } else if (var2 == long[].class) {
            return Arrays.equals((long[])((long[])var0), (long[])((long[])var1));
        } else if (var2 == short[].class) {
            return Arrays.equals((short[])((short[])var0), (short[])((short[])var1));
        } else {
            assert var2 == boolean[].class;

            return Arrays.equals((boolean[])((boolean[])var0), (boolean[])((boolean[])var1));
        }
    }

    private Method[] getMemberMethods() {
        if (this.memberMethods == null) {
            this.memberMethods = (Method[])AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
                public Method[] run() {
                    Method[] var1 = AnnotationInvocationHandler.this.type.getDeclaredMethods();
                    AnnotationInvocationHandler.this.validateAnnotationMethods(var1);
                    AccessibleObject.setAccessible(var1, true);
                    return var1;
                }
            });
        }

        return this.memberMethods;
    }

    private void validateAnnotationMethods(Method[] var1) {
        boolean var2 = true;
        Method[] var3 = var1;
        int var4 = var1.length;
        int var5 = 0;

        while(var5 < var4) {
            Method var6 = var3[var5];
            if (var6.getModifiers() == 1025 && !var6.isDefault() && var6.getParameterCount() == 0 && var6.getExceptionTypes().length == 0) {
                Class var7 = var6.getReturnType();
                if (var7.isArray()) {
                    var7 = var7.getComponentType();
                    if (var7.isArray()) {
                        var2 = false;
                        break;
                    }
                }

                if ((!var7.isPrimitive() || var7 == Void.TYPE) && var7 != String.class && var7 != Class.class && !var7.isEnum() && !var7.isAnnotation()) {
                    var2 = false;
                    break;
                }

                String var8 = var6.getName();
                if ((!var8.equals("toString") || var7 != String.class) && (!var8.equals("hashCode") || var7 != Integer.TYPE) && (!var8.equals("annotationType") || var7 != Class.class)) {
                    ++var5;
                    continue;
                }

                var2 = false;
                break;
            }

            var2 = false;
            break;
        }

        if (!var2) {
            throw new AnnotationFormatError("Malformed method on an annotation type");
        }
    }

    private int hashCodeImpl() {
        int var1 = 0;

        Entry var3;
        for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) {
            var3 = (Entry)var2.next();
        }

        return var1;
    }

    private static int memberValueHashCode(Object var0) {
        Class var1 = var0.getClass();
        if (!var1.isArray()) {
            return var0.hashCode();
        } else if (var1 == byte[].class) {
            return Arrays.hashCode((byte[])((byte[])var0));
        } else if (var1 == char[].class) {
            return Arrays.hashCode((char[])((char[])var0));
        } else if (var1 == double[].class) {
            return Arrays.hashCode((double[])((double[])var0));
        } else if (var1 == float[].class) {
            return Arrays.hashCode((float[])((float[])var0));
        } else if (var1 == int[].class) {
            return Arrays.hashCode((int[])((int[])var0));
        } else if (var1 == long[].class) {
            return Arrays.hashCode((long[])((long[])var0));
        } else if (var1 == short[].class) {
            return Arrays.hashCode((short[])((short[])var0));
        } else {
            return var1 == boolean[].class ? Arrays.hashCode((boolean[])((boolean[])var0)) : Arrays.hashCode((Object[])((Object[])var0));
        }
    }

    private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        GetField var2 = var1.readFields();
        Class var3 = (Class)var2.get("type", (Object)null);
        Map var4 = (Map)var2.get("memberValues", (Object)null);
        AnnotationType var5 = null;

        try {
            var5 = AnnotationType.getInstance(var3);
        } catch (IllegalArgumentException var13) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var6 = var5.memberTypes();
        LinkedHashMap var7 = new LinkedHashMap();

        String var10;
        Object var11;
        for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
            Entry var9 = (Entry)var8.next();
            var10 = (String)var9.getKey();
            var11 = null;
            Class var12 = (Class)var6.get(var10);
            if (var12 != null) {
                var11 = var9.getValue();
                if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
                    var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
                }
            }
        }

        AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
        AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
    }

    private static class UnsafeAccessor {
        private static final Unsafe unsafe;
        private static final long typeOffset;
        private static final long memberValuesOffset;

        private UnsafeAccessor() {
        }

        static void setType(AnnotationInvocationHandler var0, Class<? extends Annotation> var1) {
            unsafe.putObject(var0, typeOffset, var1);
        }

        static void setMemberValues(AnnotationInvocationHandler var0, Map<String, Object> var1) {
            unsafe.putObject(var0, memberValuesOffset, var1);
        }

        static {
            try {
                unsafe = Unsafe.getUnsafe();
                typeOffset = unsafe.objectFieldOffset(AnnotationInvocationHandler.class.getDeclaredField("type"));
                memberValuesOffset = unsafe.objectFieldOffset(AnnotationInvocationHandler.class.getDeclaredField("memberValues"));
            } catch (Exception var1) {
                throw new ExceptionInInitializerError(var1);
            }
        }
    }
}

The invoke method is very interesting. Please note that our proxy class proxies all the methods in the Hello interface, so the call to any method in the proxy class will be transferred here.

var2 points to the called method instance. Here, first obtain the concise name of the method with the variable var4, and then judge who the current calling method is with the switch structure. If it is the four methods in Annotation, assign var7 with a specific value.

If the current call method is toString, equals, hashCode, annotationType, the implementation of these methods has been pre defined in AnnotationInvocationHandler instances, and can be called directly.

Then, if var7 does not match these four methods, it means that the current method calls the method declared by the custom annotation byte, such as the value method of our Hello annotation. In this case, the value corresponding to this annotation attribute will be obtained from our annotation map.

In fact, the annotation design in JAVA personally feels a little anti-human. It is obviously the operation of attributes, which must be realized by methods. Of course, if you have different opinions, you are welcome to leave a message.

Finally, let's summarize the working principle of the whole reflection annotation:

First, we can assign values to annotation attributes in the form of key value pairs, such as @ Hello (value = "hello").

Next, you modify an element with annotations. The compiler will scan the annotations on each class or method during compilation, and make a basic check whether your annotation is allowed to act on the current position. Finally, the annotation information will be written to the attribute table of the element.

Then, when you reflect, the virtual machine takes out the annotations of all life cycles in RUNTIME and puts them into a map, creates an AnnotationInvocationHandler instance and passes the map to it.

Finally, the virtual machine will use JDK dynamic proxy mechanism to generate a proxy class with target annotation and initialize the processor.

In this way, an annotation instance is created. It is essentially a proxy class. You should be more familiar with it   The implementation logic of the invoke method in the AnnotationInvocationHandler is the core. In a word, the annotation attribute value is returned through the method name.

As mentioned earlier, the function of annotation is to identify and explain. More operations need to be implemented by the corresponding processor or code. I will also briefly summarize some core knowledge of annotation below

Annotation basis:
All Annotation types inherit from this common interface.
According to the notes, it can be divided into two categories:
(1) The JDK predefined three annotations [@ Override,@Deprecated,@SuppressWarnings] that play the role of prompt identification to the compiler during compilation
(2) During compilation, you need to use the attributes of annotations and use reflection to realize specific functions. [custom annotations, Spring annotations, etc.]
Four meta annotations:
(1)Target is used to declare the action position of annotation, type (class, interface, enumeration), method (method), field (attribute), parameter, local variable, construction method, annotation and package
(2)Retention, the retention period of annotations, is divided into three types: source (only exists on the source code and will not be compiled into bytecode), Class (discarded when the Class is loaded and will be compiled into Class), runtime (permanently exists and can be called through reflection)
(3)Documented exists. When we package a JavaDoc document, it will be saved into the doc document
(4)Inherited, whether it can be inherited by annotation
How annotation works:
According to the annotation saving cycle, you can sort out the working principle, such as @ override annotation. During compilation, the compiler scans the preset annotation to check the location of the annotation and whether to rewrite the parent class methods. If not, an error will be reported (of course, this kind of annotation is not our focus. You need to know how to trigger the check)
During compilation, the compiler scans the class and finds that the annotation will be added to the annotations in the attributes of the class or method. All annotations of the current entity are stored in this annotation. Each annotation has a handle and a map. The map stores the attributes (keys) and values in the annotation. If necessary, a Proxy object will be created for this annotation, The object inherits from Proxy class Proxy and implements the annotated interface, which implements all annotated methods. At the same time, the annotated method essentially calls the invoke() method in Proxy class to return the corresponding value
Of course, Spring annotations are not just those complex operations implemented by annotations, but by annotation processor (annotation post processor)

The example will be supplemented later, as well as the definition and use of annotation processor!

Posted by chapm4 on Fri, 17 Sep 2021 20:15:34 -0700