系統中用到了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) ;
}
}