天天看點

java自定義注解的使用和基本原理「建議收藏」一、定義二、元注解三、自定義注解的使用四、web開發中的運用五、java内置的注解六、注解的原理

大家好,又見面了,我是你們的朋友全棧君。

java自定義注解的使用和基本原理

  • 一、定義
  • 二、元注解
  • 三、自定義注解的使用
  • 四、web開發中的運用
  • 五、java内置的注解
  • 六、注解的原理

一、定義

  1. Java檔案叫做Annotation,用@interface表示。
  2. java中提供了四種元注解,用于建立新的注解,分别是:@Retention、@Target、@Document、@Inherited

二、元注解

  1. @Retention 從源代碼中可以看出,主要用于提示注解要保留多長時間
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();
}           

複制

有三種取值:

  1. RetentionPolicy.SOURCE 将會被編譯器抛棄
  2. RetentionPolicy.CLASS 注解會被編輯器保留在類檔案中,但是會被vm抛棄
  3. RetentionPolicy.RUNTIME 注解會被編輯器保留在類檔案中,也會被vm保留,是以可以通過反射讀取。
package java.lang.annotation;

/**
 * Annotation retention policy.  The constants of this enumerated type
 * describe the various policies for retaining annotations.  They are used
 * in conjunction with the {@link Retention} meta-annotation type to specify
 * how long annotations are to be retained.
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

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

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}           

複制

  1. @Target 用于提示該注解使用的地方
@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();
}           

複制

其中ElementType的取值有下面十種:

  1. ElementType.TYPE 用于類,接口(包括注解)或者枚舉類型
  2. ElementType.FIELD 用于屬性字段包括枚舉常量
  3. ElementType.METHOD 用于方法級别
  4. ElementType.PARAMETER 用于參數聲明
  5. ElementType.CONSTRUCTOR 用于構造函數聲明
  6. ElementType.LOCAL_VARIABLE 用于局部變量聲明
  7. ElementType.ANNOTATION_TYPE 用于注解類型聲明
  8. ElementType.PACKAGE 用于包聲明
  9. ElementType.TYPE_PARAMETER 用于泛型聲明
  10. ElementType.TYPE_USE 用于任意類型聲明
public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}           

複制

  1. @Documented 将注解包含在Javadoc中
package java.lang.annotation;

/**
 * Indicates that annotations with a type are to be documented by javadoc
 * and similar tools by default.  This type should be used to annotate the
 * declarations of types whose annotations affect the use of annotated
 * elements by their clients.  If a type declaration is annotated with
 * Documented, its annotations become part of the public API
 * of the annotated elements.
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}           

複制

  1. @Inherited 允許子類繼承父類
package java.lang.annotation;

/**
 * Indicates that an annotation type is automatically inherited.  If
 * an Inherited meta-annotation is present on an annotation type
 * declaration, and the user queries the annotation type on a class
 * declaration, and the class declaration has no annotation for this type,
 * then the class's superclass will automatically be queried for the
 * annotation type.  This process will be repeated until an annotation for this
 * type is found, or the top of the class hierarchy (Object)
 * is reached.  If no superclass has an annotation for this type, then
 * the query will indicate that the class in question has no such annotation.
 *
 * <p>Note that this meta-annotation type has no effect if the annotated
 * type is used to annotate anything other than a class.  Note also
 * that this meta-annotation only causes annotations to be inherited
 * from superclasses; annotations on implemented interfaces have no
 * effect.
 *
 * @author  Joshua Bloch
 * @since 1.5
 * @jls 9.6.3.3 @Inherited
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}           

複制

三、自定義注解的使用

  1. 建立一個自定義注解
import java.lang.annotation.*;

/**
 * Created Date: 2019/3/1
 * 建立自定義注解
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation {
    
    String value();
}           

複制

  1. 通過反射擷取注解
public class Test {

	@TestAnnotation(value = "測試方法")
    public static void main(String args[]){
        try {
            Class c=Test.class;
            Method[] methods=c.getDeclaredMethods();
            for(Method method:methods){
                Annotation[] annotations=method.getDeclaredAnnotations();
                for(Annotation annotation:annotations){
                    TestAnnotation testAnnotation= (TestAnnotation) annotation;
                    System.out.println(testAnnotation.value());
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}           

複制

四、web開發中的運用

在web開發中,權限控制非常重要,是以有些接口會限制必須登入之後才能通路,但是個别接口并沒有這種限制。一種方式是把需要過濾的接口或者方法配置在檔案中,每次請求時在攔截器中根據請求的路徑與配置檔案中的對比過濾。其實還有另外一種方式就是通過注解方式。

  1. 定義一個注解NoLogin
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NoLogin {
}           

複制

  1. 标注在方法上
  2. 在攔截器中判斷方法上是否有NoLogin注解
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {

        	//支援兩種方式過濾  1、注解方式 添加@NoLogin注解
        	HandlerMethod handlerMethod= (HandlerMethod) o;
        	NoLogin noLogin=handlerMethod.getMethod().getDeclaredAnnotation(NoLogin.class);
        	if(null!=noLogin){
            	return true;
       		 }
       	}           

複制

五、java内置的注解

除了上述的四個元注解,java還内置了另外三個注解:

  1. @Override 它沒有任何的屬性,不能存儲任何其他資訊。它隻能作用于方法之上,編譯結束後将被丢棄。在java編譯器編譯成位元組碼的時候,一旦發現某個方法被這個注解辨別過,就會比對父類中是否存在同一方法,如果不存在就回編譯失敗。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}           

複制

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

複制

  1. @SuppressWarnings 壓制警告,比如某段代碼中存在過時的方法,那麼在編譯過程中,會有warn警告,如果不想出現類似的警告,可在方法上添加這個注解。這個注解有一個value的值,這個value表示需要壓制的警告類型。
@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();
}           

複制

六、注解的原理

1、java.lang.annotation.Annotation中有這麼一句話:The common interface extended by all annotation types 所有的注解都繼承于這個接口。怎麼了解呢?其實剛才上面例子中的注解可以了解為:

public @interface TestAnnotation extends Annotation{

}           

複制

注解的本質就是一個繼承了 Annotation 接口的接口

為了友善了解和掌握注解,還是以剛才的TestAnnotation注解為例。在idea中配置啟動參數,友善檢視代理産生的類,參數如下:

Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

main方法運作結束後,會在/com/sun/proxy目錄下生成一個代理類,反編譯之後是這樣的:

代理類proxy1重寫了TestAnnotation的所有方法,包括value()和從Annotation繼承來的equals()、hashCode()等方法。

package com.sun.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import org.fy.annotation.TestAnnotation;

public final class $Proxy1 extends Proxy implements TestAnnotation {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m0;
    private static Method m3;

    public $Proxy1(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

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

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

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

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

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("org.fy.annotation.TestAnnotation").getMethod("annotationType");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("org.fy.annotation.TestAnnotation").getMethod("value");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}           

複制

在看proxy1構造函數中有一個接口InvocationHandler,這個接口的執行個體化對象又是誰?

java中有一個專門用于注解類型的代理對象AnnotationInvocationHandler,位于sun.reflect.annotation包中。同樣是invoke方法用于處理具體的業務。

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;
                }
            }
        }
    }           

複制

var2是具體被調用的方法執行個體。var4擷取方法的名稱,然後判斷是否equals方法,接着用swtich判斷是否來自于annotation中的其他三個方法,toString、hashCode、annotationType。如果是這三個方法,則給var7附上特定的值,并且AnnotationInvocationHandler 執行個體中已經預定義好了這些方法的實作,直接調用即可。

假如var7沒有比對到上面的那四個方法,則會走下面的邏輯:這裡有一個 memberValues,它是一個 Map 鍵值對,鍵是我們注解屬性名稱,值就是該屬性當初被賦上的值。

如下圖所示,在編譯時候會對memberValues指派,把我們定義好的value字段作為key值放入map中。

java自定義注解的使用和基本原理「建議收藏」一、定義二、元注解三、自定義注解的使用四、web開發中的運用五、java内置的注解六、注解的原理

在具體執行時,會重新對memberValues指派,如圖所示:

java自定義注解的使用和基本原理「建議收藏」一、定義二、元注解三、自定義注解的使用四、web開發中的運用五、java内置的注解六、注解的原理

這樣就能從注解map中擷取我們需要的值。

最後總結一下注解的工作原理:

首先,我們通過鍵值對的形式可以為注解屬性指派,像這樣:@TestAnnotation(value = “測試方法”)

接着,你用注解修飾某個方法或者屬性、類等,編譯器将在編譯期掃描每個注解,會檢查注解是否允許作用在目前位置,如果允許,最後會将注解資訊寫入元素的屬性表。

然後在進行反射的時候,jvm會把所有生命周期在 RUNTIME 的注解取出來放到一個 map 中,并建立一個 AnnotationInvocationHandler 執行個體,把這個 map 傳遞給它。虛拟機通過JDK 動态代理機制生成一個目标注解的代理類,并初始化好處理器。

那麼這樣,一個注解的執行個體就建立出來了,它本質上就是一個代理類。歸納後就是通過方法名傳回注解屬性值。

釋出者:全棧程式員棧長,轉載請注明出處:https://javaforall.cn/129135.html原文連結:https://javaforall.cn