Talk about java annotations

Posted by aldernon on Tue, 25 Jan 2022 16:58:51 +0100

The essence of annotation

When talking about the essence of annotation, the simplest way is to decompile. See the essence. The code is as follows:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Foo{
    String[] value();
    boolean bar();
}

Use the following command to compile and generate bytecode, and decompile it into java file. We can get the following output

 javac Foo.java

 javap -c Foo.class

I think there is no need to say more. The essence of annotation is an interface.

Compiled from "Foo.java"
public interface com.shark.wiki.interview.javaBase.annotation.Foo extends java.lang.annotation.Annotation {
public abstract java.lang.String[] value();

public abstract boolean bar();
}

What is meta annotation?

Meta annotation is a very tall concept. In fact, it is the annotation of annotation. Its function is to annotate annotation. The commonly used annotations are as follows

    @Target: Purpose of annotation
    @Retention: Annotation lifecycle
    @Documented: Should annotations be included in JavaDoc In the document
    @Inherited: Allow subclasses to inherit the annotation

For the specific content of meta annotation, please refer to the following article, which will not be introduced here
java meta annotation and its function

Three built-in annotations in java

@Override

The source code is as follows. You can see that the meta annotation has target and retention, where retention is source, which means that it will annotate are to be discarded by the compiler. Therefore, it is not difficult for us to guess its function, that is, to check whether the method rewritten by the current subclass exists in the parent class during compilation. If it exists, it will be compiled, and vice versa.

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

@Deprecated

This annotation is often used to remind developers that annotated gadgets are no longer recommended

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

@SuppressWarnings

To put it bluntly, it's to suppress the warnings of mischief

@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();
}

The relationship between annotation and reflection and simple practice

In fact, we often use annotations when using the Spring framework, such as Service("userservice"), so how does Spring get the value of this bean through annotations?
We might as well customize an annotation to experiment with this problem. First, we customize a service

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
    String value();
    String scope();
}

Then we write the following code and type this jvm command - dsun. In the vim option of idea misc. ProxyGenerator. saveGeneratedFiles=true

@Service(value = "userService", scope = "singleton")
public class Test {


    public static void main(String[] args) throws Exception{
        Service service = Test.class.getAnnotation(Service.class);
        System.out.println(service.value());
        System.out.println(service.scope());

    }
}

You can see that userService will be output, and the following file will appear in the view project file

View $proxy1 Class source code, we can see such a piece of code. We can see that these two variables are not the values defined in our annotation? Then we'll look for his call

  static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m5 = Class.forName("com.shark.wiki.interview.javaBase.annotation.Service").getMethod("annotationType");
          <font color="#c24f4a"> </font><b style=""><font color="#000000"> <font style="background-color: rgb(249, 150, 59);">m4 = Class.forName("com.shark.wiki.interview.javaBase.annotation.Service").getMethod("scope");</font></font></b>
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
           <span style="background-color: rgb(249, 150, 59);"><b> m3 = Class.forName("com.shark.wiki.interview.javaBase.annotation.Service").getMethod("value");</b></span>
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

Take scope as an example, we see such a code, it is not difficult to see the Service. that we called before. Scope () uses this method. Here is an invoke. Let's click in to see the call

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

At this time, we see an interface, but we don't see the specific implementation. It doesn't matter. The name of the source code designer is always reasonable. Therefore, we can completely find the inheritance classes related to annotation naming

At this time, the author found this

The specific implementation source code is as follows. The key author has commented in the code

public Object invoke(Object var1, Method var2, Object[] var3) {
//var2 is the scope passed in in the previous step
        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;
                }
            }
//Mark var7 according to var2, and then return the VaR value of the response. Here, the scope takes the last branch and returns the string of the scope
            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;
                }
            }
        }
    }

summary

From the above examples, we have a good understanding of the usage of annotations in the framework. Its birth is to solve the scenario that the maintenance becomes more complex due to the long-term use of xml configuration, that is, a best practice to solve the maintenance difficulties caused by low coupling through high coupling.

reference

Basic principles of java annotation

Topics: Java Back-end Interview JavaSE