Java Annotation詳解 理解和使用Annotation


系統中用到了java注解:

查了一下如何使用注解,到底注解是什么;

(1)創建方法:MsgTrace Java Class==>

在Create New Class中:

name:輸入MsgTrace;

Kind:Annotation;

就可以了;

 
public @interface MsgTrace {
    String traceId() default "";
}

 

 這個traceId是屬性,認值是空,這樣,如果使用這個注解的話,就不需要顯式指定traceId,

如果用戶在類上面添加了這個注解,那么就會走代理類;

代理類:

package com.sankuai.qcs.regulation.aop;

import com.dianping.cat.util.MetricHelper;
import com.meituan.mtrace.Tracer;
import lombok.Data;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * 消息鏈路跟蹤,主要記錄過程時間
 * http請求是時間
 * 接口http請求次數
 *
 * @Author:chenzying
 * @Description:
 * @Date: Created in 上午11:14 2019/1/25
 * @Modified By:
 **/
@Aspect
@Component
public class MsgTraceAspect {

    private static final Integer MAX_TRACE_TIME = 1000;
    private static Map<String, MsgTraceAspect.TraceTime> timeMap = new HashMap<String, MsgTraceAspect.TraceTime>();
    private Logger logger = LoggerFactory.getLogger(MsgTraceAspect.class);

    @Pointcut("@annotation( com.sankuai.qcs.regulation.annotation.MsgTrace)")
    public void point() {
    }


    @Around("point()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        String traceId = "";

        try {
            traceId = Tracer.getServerSpan().getTraceId();
        } catch (Exception e) {

        }

        String functionName =
                String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(),
                        proceedingJoinPoint.getSignature().getName());


        long startTime = System.currentTimeMillis();


        Object o = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());

        long endTime = System.currentTimeMillis();

        long useTime = endTime - startTime;

        Object result = proceedingJoinPoint.proceed();

        cal(functionName, useTime, traceId);


        try {

            MetricHelper.build().name("Core_Function_Time").tag("function", functionName).duration(useTime);

            MetricHelper.build().name("Core_Function_QPS").tag("function", functionName).count();

            MetricHelper.build().name("Core_Function_QPS").tag("function", "ALL").count();


        } catch (Exception e) {

        }
        return o;
    }

    private void cal(String functionName, long useTime, String traceId) {
        if (timeMap.containsKey(functionName)) {
            TraceTime tracetime = timeMap.get(functionName);
            tracetime.setMax(tracetime.getMax() > useTime ? tracetime.getMax() : useTime);
            tracetime.setMin(tracetime.getMin() < useTime ? tracetime.getMin() : useTime);
            tracetime.setAvg(
                    (tracetime.getAvg() * tracetime.getUse() + useTime) / (tracetime.getUse() + 1));
            tracetime.setUse(tracetime.getUse() + 1);

            if (tracetime.getUse() > MAX_TRACE_TIME) {
                tracetime.setUse(MAX_TRACE_TIME);
            }


            logger.debug("{} trace: {} 用時 {} ms,max-{} ,min-{} ,avg-{} "
                    , functionName, traceId, useTime, tracetime.getMax()
                    , tracetime.getMin(), tracetime.getAvg());


        } else {
            TraceTime traceTime = new TraceTime();
            traceTime.setMin(useTime);
            traceTime.setMax(useTime);
            traceTime.setAvg(useTime);
            traceTime.setUse(1);
            timeMap.put(functionName, traceTime);

            logger.debug("{} trace: {} 用時 {} ms", functionName, traceId, useTime);
        }
    }


    @Data
    class TraceTime {
        private long min;
        private long max;
        private long avg;
        private long use;
    }
}

上面就是代理類,

代理類 最開始有個:@Aspect 代表了切面編程;

注意:

    @Pointcut("@annotation( com.sankuai.qcs.regulation.annotation.MsgTrace)")
    public void point() {
    }

這就是說 切點在MsgTrace,如果你在任何方法上面添加了MsgTrace就會走point()這個方法;

因為這僅僅是個個類似於XML的配置,所以point為空了;

下面:

 @Around("point()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

Around方法,就是說,如果你在你的方法上面添加了MsgTrace,那么會產生一個代理類,圍繞這個方法,執行自定義的方法;

 

 

 

以下是網絡上的解釋: 

JDK1.5之后,引入了元數據的概念,也就是Annotation(注釋),其實它是代碼里的特殊標記,這些標記可以再編譯、類加載、運行時被讀取,並執行相應的處理。

元數據的作用:

如果要對於元數據的作用進行分類,目前還沒有明確的定義,不過我們可以根據它所起的作用,大致可分為三類:

1.  編寫文檔:通過代碼里標識的元數據生成文檔。
2.  代碼分析:通過代碼里標識的元數據對代碼進行分析。
3.  編譯檢查:通過代碼里標識的元數據讓編譯器能實現基本的編譯檢查。

 

 

 

 

 

一、 系統內建的Annotation:

    @Override 覆寫的Annotation

注釋能實現編譯時檢查,你可以為你的方法添加該注釋,以聲明該方法是用於覆蓋父類中的方法。如果該方法不是覆蓋父類的方法,將會在編譯時報錯。例如我們為某類重寫toString()方法卻寫成了tostring(),並且我們為該方法添加了@Override注釋,則會提示編譯錯誤。
     @Deprecated 不贊成使用的Annotation

其作用是對不應該在使用的方法添加注釋,當編程人員使用這些方法時,將會在編譯時顯示提示信息,不推薦在使用該方法或該類。
    @SuppressWarnings 壓制安全警告的Annotation

與前兩個注釋有所不同,你需要添加一個參數才能正確使用,這些參數值都是已經定義好了的,我們選擇性的使用就好了,參數如下:
deprecation   使用了過時的類或方法時的警告
unchecked  執行了未檢查的轉換時的警告,例如當使用集合時沒有用泛型 (Generics) 來指定集合保存的類型
fallthrough   當 Switch 程序塊直接通往下一種情況而沒有 Break 時的警告
path   在類路徑、源文件路徑等中有不存在的路徑時的警告
serial 當在可序列化的類上缺少 serialVersionUID 定義時的警告
finally    任何 finally 子句不能正常完成時的警告
all 關於以上所有情況的警告

在為@SuppressWarnings設置注釋信息的時候,是以key-value的形式出現的,所以以上的@SuppressWarnings也可以直接使用,所以@SuppressWarnings可以使用”value={"unchecked","deprecation"}“的方式來設置。

@Deprecated
class Demo<T>{
private T var ;
public T getVar(){
return this.var ;
}
public void setVar(T var){
this.var = var ;
}
};
public class SuppressWarningsAnnotationDemo03{
// @SuppressWarnings(value={"unchecked","deprecation"})
public static void main(String args[]){
Demo d = new Demo() ;
d.setVar("沉緣") ;
System.out.println("內容:" + d.getVar()) ;
}
};

上面,我們將 注釋掉,編譯后,會出現警告提示:
---------- javac ----------
注: SuppressWarningsAnnotationDemo03.java使用或覆蓋了已過時的 API。
注: 有關詳細信息, 請使用 -Xlint:deprecation 重新編譯。
注: SuppressWarningsAnnotationDemo03.java使用了未經檢查或不安全的操作。
注: 有關詳細信息, 請使用 -Xlint:unchecked 重新編譯。


打開@SuppressWarnings注釋,再次編譯,發現,警告已被抑制。

 

二、 自定義Annotation

定義簡單的Annotation形式:

[public] @interface Annotation名稱{

        數據類型  變量名稱();

}

例如:


public @interface MyDefaultAnnotationNoneParam{
}
之后,就可以直接使用@Annotation名稱:

@MyDefaultAnnotationNoneParam
class Demo
{
}
此時,就表示在Demo類上使用Annotation。

還可以向Annotation中設置變量,使用變量接受參數。


public @interface MyDefaultAnnotationSingleParam{
public String value(); //接受設置的參數
}
在使用的時候,必須清楚的指定變量的名稱,變量的內容:

@MyDefaultAnnotationSingleParam("沉緣")
class Demo
{
}
或者使用明確的標記,表示內容賦給哪個參數:


@MyDefaultAnnotationSingleParam(value="沉緣")
class Demo
{
}
以上的參數,是要賦給value屬性的。既然可以設置一個參數,則也就可以同時設置多個參數。

public @interface MyDefaultAnnotationMoreParam{
public String key() ;
public String value() ; // 接收設置的內容
}
此Annotation在使用時,需要設置兩個參數,一個key,一個value。

@MyDefaultAnnotationMoreParam(key="Linkage",value="沉緣")
class Demo{
};
當然,我們可以設置數組進去,@SuppressWarnings就使用了數組。

public @interface MyDefaultAnnotationArrayParam{
public String[] value() ; // 接收設置的內容
}
接收內容本身是一個數組類型,要傳遞數組。

@MyDefaultAnnotationArrayParam(value={"沉緣","流燼"})
class Demo{
};
以上的定義Annotation都未指定屬性的默認值,必須在使用時設置。 其實,也可以直接使用default來定義默認值:

public @interface MyDefaultAnnotationValue{
public String key() default "Linkage" ; // 指定好了默認值
public String value() default "沉緣" ; // 指定好了默認值
}
在實際的操作中,對於一個Annotation而言,有時候會固定其取值范圍,只能使用固定的幾個值。那么這時候實際上就需要依靠枚舉:

public enum MyName{ // 定義枚舉類型
WUQING,WUYUAN,WULEI ;
}

public @interface MyDefaultAnnotationEnum{
public MyName name() default MyName.WUQING ; // 指定默認值
}


三、 限定注釋使用范圍Target

當我們的自定義注釋不斷的增多也比較復雜時,就會導致有些開發人員使用錯誤,主要表現在不該使用該注釋的地方使用。為此,Java提供了一個ElementType枚舉類型來控制每個注釋的使用范圍,比如說某些注釋只能用於普通方法,而不能用於構造函數等。下面是Java定義的ElementType枚舉:


package java.lang.annotation;

public enum ElementType {

TYPE, // Class, interface, or enum (but not annotation)

FIELD, // Field (including enumerated values)

METHOD, // Method (does not include constructors)

PARAMETER, // Method parameter

CONSTRUCTOR, // Constructor

LOCAL_VARIABLE, // Local variable or catch clause

ANNOTATION_TYPE, // Annotation Types (meta-annotations)

PACKAGE // Java package

}
想要使用ElementType,只需要為注釋添加@Target即可:

@Target( { ElementType.METHOD, ElementType.CONSTRUCTOR })

public @interface TargetTest {

}

正如上面代碼所展示的,我們只允許Greeting注釋標注在普通方法和構造函數上,使用在包申明、類名等時,會提示錯誤信息。

四、 Retention和RetentionPolicy,注釋保持性策略


public enum RetentionPolicy {


SOURCE,// Annotation is discarded by the compiler


CLASS,// Annotation is stored in the class file, but ignored by the VM


RUNTIME// Annotation is stored in the class file and read by the VM

}


RetentionPolicy的使用方法的簡單代碼示例如下:

@Retention(RetentionPolicy.RUNTIME)
而,在RetentionPolicy的三個范圍中,最需要注意的就是RUNTIME范圍,因為在執行的時候起作用。


import java.lang.annotation.Retention ;
import java.lang.annotation.RetentionPolicy ;
@Retention(value=RetentionPolicy.RUNTIME) // 表示此Annotation在運行時有效
public @interface MyDefaultRententionAnnotation{
public String name() default "沉緣" ;
}

我們看下內建的Annotation的RetentionPolicy:
@Override定義采用的是@Retention(value=SOURCE),只能在源文件中出現。

@Deprecated定義采用的是@Retention(value=RUNTIME),可以在執行時出現。

@SuppressWarnings定義采用的是@Retention(value=SOURCE),只能在源文件中出現。

 

五、 文檔化功能

Java提供的Documented元注釋跟Javadoc的作用是差不多的,其實它存在的好處是開發人員可以定制Javadoc不支持的文檔屬性,並在開發中應用。它的使用跟前兩個也是一樣的,簡單代碼示例如下:


import java.lang.annotation.Documented ;
@Documented
public @interface MyDocumentedAnntation{
<span style="white-space:pre"> </span>public String key() default "Linkage" ;
<span style="white-space:pre"> </span>public String value() default "沉緣" ;
}

成功后,在使用此Annotation的時候,可以增加一些信息進去:

@MyDocumentedAnntation(key="Baidu",value="www.baidu.com")
public class SimpleBeanDocumented{
/**
* 此方法在對象輸出時調用,返回對象信息
*/
@MyDocumentedAnntation(key="Xinlang",value="www.sina.com")
public String toString(){
return "Hello World!!!" ;
}
};

之后,在生成jdk文檔的時候,使用@Document修飾的方法將被注釋下來。
六、 標注繼承

繼承應該是Java提供的最復雜的一個元注釋了,它的作用是控制注釋是否會影響到子類(一個Annotation是否可以被繼承下來),簡單代碼示例如下:


package com.test.inheriteddemo ;
import java.lang.annotation.Retention ;
import java.lang.annotation.RetentionPolicy ;
import java.lang.annotation.Documented ;
import java.lang.annotation.Inherited ;
@Documented
@Inherited
@Retention(value=RetentionPolicy.RUNTIME)
public @interface MyInheritedAnnotation{
public String name() ;

}
使用該注釋,標注一個父類Person:

package com.test.inheriteddemo ;
@MyInheritedAnnotation(name="沉緣")
public class Person{
};

按照所解釋的,使用Inherited聲明的Annotation是可以被子類繼承下來的:
import java.lang.annotation.Annotation ;
import org.lxh.demo16.inheriteddemo.MyInheritedAnnotation ;
public class ReflectInheritedDemo{
public static void main(String args[]) throws Exception{
Class<?> c = null ;
c = Class.forName("com.test.inheriteddemo.Student") ;
Annotation ann[] = c.getAnnotations() ; // 取得全部的Annotation
for(Annotation a:ann){ // 輸出
System.out.println(a) ;
}
// 繼續取得此Annotation設置的內容
if(c.isAnnotationPresent(MyInheritedAnnotation.class)){
MyInheritedAnnotation mda = null ;
mda = c.getAnnotation(MyInheritedAnnotation.class) ;
String name = mda.name() ; // 取出name的內容
System.out.println("name = " + name) ;
}
}

 

 

參考:Java Annotation詳解(一): 理解和使用Annotation


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM