一,引言
1.1 引入註解
我們經常使用便利貼用來提醒我們在某個時間點檢查下系統的運行狀態。我們可以把便利貼貼在不同的地方用於提醒我們做一件事情。其實註解的作用是相同的,需要一個固定化的結構讓java能夠讀取。這個也屬於設計模式的註釋模式,可以極大的提高開發的效率。
1.2 文字概念
註解(Annotation)是撰寫在原始碼中的資訊,可以提供代碼以外的額外資訊,本身有其結構,你可以使用工具提取這些結構化訊息。
簡單來說註解就是寫在程序上方的結構化註釋,java會按照其結構讀取訊息,並且做對應操作。比如@Override註解標註在方法上,在編譯期間,java就會檢查當前方法是否重載了父類的方法,如果沒有就報錯。
1.3 java的註解
-
註解是一種數據類型,我們可以通過class,interface定義類和接口一樣。通過@interface可以定義一個註解。
-
註解可以有成員變量,但是沒有方法。成員變量可以有默認的值。成員變量的類型可以使java中所有的數據類型,包含註解類型本身。
[@Target]
[@Retention]
[@Documented]
[@Inherited]
public @interface [名稱] {
// 元素
String value() default "hello";
}
1.4 註解的三種分類
1.4.1 java自帶標準註解
- @Override:表示當前的方法定義將覆蓋父類中的方法
- @Deprecated:表示代碼被棄用,如果使用了被@Deprecated註解的代碼則編譯器將發出警告
- @SuppressWarnings:表示關閉編譯器警告信息
1.4.2 元註解-定義註解的註解
註解名稱 | 作用 | 可能值 |
---|---|---|
@Retention | 說明了這個註解的存活時間 | SOURCE,CLASS ,RUNTIME |
@Documented | 將註解中的元素包含到 Javadoc 中去 | 無 |
@Target | 限定註解可以標註的位置 | ANNOTATION_TYPE、CONSTRUCTOR 、FIELD 、LOCAL_VARIABLE 、METHOD 、PACKAGE 、PARAMETER 、TYPE |
@Inherited | 子類自動擁有父類的註解 | 無 |
@Repeatable |
注1:
- RetentionPolicy.SOURCE 註解只在源碼階段保留,在編譯器進行編譯時它將被丟棄忽視
- RetentionPolicy.CLASS 註解只被保留到編譯進行的時候,它並不會被加載到 JVM 中。
- RetentionPolicy.RUNTIME 註解可以保留到程序運行的時候,它會被加載進入到 JVM 中,所以在程序運行時可以獲取到它們。
1.4.3 自定義註解
利用上述註解生成自己定義的註解,就像自己定義一個類一樣,可以通過@interface創建一個自定義註解。稍後會講解註解的價值和使用。
註解的價值
- 生成文檔,通過代碼裏標識的元數據生成javadoc文檔。
- 編譯檢查,通過代碼裏標識的元數據讓編譯器在編譯期間進行檢查驗證。
- 編譯時動態處理,編譯時通過代碼裏標識的元數據動態處理,例如動態生成代碼。
- 運行時動態處理,運行時通過代碼裏標識的元數據動態處理,例如使用反射注入實例
注:前三種作用都是在編譯期間當編譯器掃描到註解時,進行操作。
最後一種運行期間動態處理是通過java的反射,當掃描到註解時通過反射獲取到類的方法和屬性上的註解及註解的屬性值。按照
自定義註解生產使用案例
很多博客都介紹瞭如何通過java反射獲取方法或者屬性的註解,但是很少有人講清楚到底如何使用這些註解,這裏我尋找了幾個生產中使用的場景,希望大家可以理解註解的如何使用。
場景1:在spring啓動時獲取標註某個註解的對相集合
@Component
public class ContextRefreshedListener implements ApplicationListener<ContextRefreshedEvent> {
public static Map<String, Object> beanMap = new HashMap<>();
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
//獲取標註有TargetBean註解的bean
beanMap = event.getApplicationContext().getBeansWithAnnotation(TargetBean.class);
System.out.println(beanMap);
}
}
https://blog.csdn.net/qq_37334435/article/details/91383537
https://my.oschina.net/mfkwfc/blog/60885
場景2: 利用註解實現對象信息統計和彙總
這個來自於網友生產案例,很有代表性的講述了註解的使用。
評分類
package annotation.demo2;
/**
* 業務邏輯:總分100分,根據各個字段規則爲每人評分,評分最高的3名學生可以得到獎學金。
* 注:這裏只給出了6個字段,實際上評分項可能有幾十個,如果通過if else判斷代碼會非常臃腫
*
* 1,如果選項是否決項,則直接分數爲0
* 2,如果選項是減分項則從總分中減去對應分值
* 3,如果選項是普通項則計入總分
*
*/
public class StudentMark {
private String name;
@MarkReason(reasonName = "積極參加學校活動")
private Double point1; //評分項1 加分項
@MarkReason(reasonName = "掛科超過2門以上",isSubtraction = true)
private Double point2; //評分項2 減分項
@MarkReason(reasonName = "考試作弊行爲",isFouJue = true)
private Double point3; //評分項3 否決項(分數直接爲0)
@MarkReason(reasonName = "成績達到優秀的課程數量超過6門")
private Double point4; //評分項4
@MarkReason(reasonName = "獲得創業大賽前三名")
private Double point5; //評分項5
@MarkReason(reasonName = "1年內已經領取過獎學金",isFouJue = true)
private Double point6; //評分項6
@MarkReason(reasonName = "曠課行爲超過2次",isSubtraction = true)
private Double point7; //評分項6
private String summary; //彙總
public StudentMark(String name,Double point1, Double point2, Double point3, Double point4, Double point5, Double point6, Double point7) {
this.name = name;
this.point1 = point1;
this.point2 = point2;
this.point3 = point3;
this.point4 = point4;
this.point5 = point5;
this.point6 = point6;
this.point7 = point7;
}
//省略getset
註解
/**
* 註解定義了三個屬性 resonName、isSubtraction、isFouJue,被 MarkReason 註解修飾的屬性可以擁有這三個屬性。
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.FIELD)
public @interface MarkReason {
//評分項目名稱
public String reasonName();
//是否減分項
public boolean isSubtraction() default false;
//是否否決項
public boolean isFouJue() default false;
}
解析註解工具類
package annotation.demo2;![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20200401124458373.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tvdXJ5b3VzaGluZQ==,size_16,color_FFFFFF,t_70)
import java.lang.reflect.Field;
public class AnnotationUtil {
public static void setMarkReasons(StudentMark bean) throws NoSuchFieldException, IllegalAccessException {
//獲取到目標對象所有的字段
Field []fields = bean.getClass().getDeclaredFields();
double score=100;
String isSubtraction="";
boolean isFouJue =false;
StringBuilder sbReason = new StringBuilder();
for(Field field:fields){
//判斷字段上是否標註了MarkReason自定義註解
if(field.isAnnotationPresent(MarkReason.class) ){
field.setAccessible(true); //允許訪問私有屬性
//獲取字段上註解類型,並且計算分值
MarkReason markReason = field.getAnnotation(MarkReason.class);
double fieldvalue = (double)field.get(bean);
if(markReason.isFouJue()){
score=0;
isFouJue=true;
}else if(markReason.isSubtraction()){
score-=fieldvalue;
isSubtraction="-";
}else {
score+=fieldvalue;
isSubtraction="+";
}
//拼接統計項
sbReason.append(markReason.reasonName()+":"+isSubtraction+fieldvalue);
}
}
score=isFouJue?0:score;
sbReason.append("總分:"+score);
Field targetField = bean.getClass().getDeclaredField("summary");
targetField.setAccessible(true);
targetField.set(bean,sbReason.toString());//設置summary字段的值
}
}
測試類
public class ClientTest {
public static void main(String[] args) {
StudentMark stu1 = new StudentMark("張三",33d,33d,33d,223d,31d,21d,32d);
StudentMark stu2 = new StudentMark("張三",33d,33d,32d,223d,31d,21d,32d);
try {
AnnotationUtil.setMarkReasons(stu2);
AnnotationUtil.setMarkReasons(stu1);
System.out.println(stu1.toString());
System.out.println(stu2.toString());
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
測試結果
{"name":"張三","point1":33.0,"point2":33.0,"point3":33.0,"point4":223.0,"point5":31.0,"point6":21.0,"point7":32.0,"summary":"積極參加學校活動:+33.0掛科超過2門以上:-33.0考試作弊行爲:-33.0成績達到優秀的課程數量超過6門:+223.0獲得創業大賽前三名:+31.01年內已經領取過獎學金:+21.0曠課行爲超過2次:-32.0總分:0.0"}
結果說明:
上述案例有很多不嚴謹的地方,但是不妨礙我們理解註解在這個案例的價值。如果字段更多註解的價值就更大,把一些字段的額外屬性保存在註解中。通過反射獲取註解和字段的屬性進行業務邏輯實現。可以節省代碼是業務邏輯更加專注。
結論
希望大家能夠通過上面兩個例子理解註解的概念,原理,和使用的場景。需要發揮我們的想象力利用註解靈活實現我們的業務。
參考
https://www.cnblogs.com/niuyourou/p/12312879.html
https://baijiahao.baidu.com/s?id=1637216017950921752&wfr=spider&for=pc