作為一個 JAVA 開發者,對注解這一概念一定是不陌生的。像我們平時常用的就有 @Controller, @Service,@Test,@Override 等等好多個,正確的使用注解確實可以方便我們的開發,以@Controller 為例,加上該注解后,框架層面為我們節省了一大堆需要在 Servlet 層面寫的通用代碼,大大減少了實際開發時的重復代碼量。
除了使用這些框架提供的注解外,我們也可以為我們的應用自定義注解方便自己的開發,下面我們來看一下如何自定義和使用注解,並在提供一個實際使用自定義注解的例子。
注解的概念
從JDK 1.5開始, Java增加了對元數據(MetaData)的支持,也就是 Annotation(注解)。 注解其實就是代碼里的特殊標記,它用於替代配置文件:傳統方式通過配置文件告訴類如何運行,有了注解技術后,開發人員可以通過注解告訴類如何運行。在Java技術里注解的典型應用是:可以通過反射技術去得到類里面的注解,以決定怎么去運行類。
對於注解,官方的說法是:注解是一種能被添加到java代碼中的元數據,類、方法、變量、參數和包都可以用注解來修飾。注解對於它所修飾的代碼並沒有直接的影響。
注解實際上就是一種數據結構,為我們的類、屬性、方法等添加附加信息,我們可以利用這些附加信息對宿主進行一些邏輯判斷。
注解的生命周期有三個階段:1、Java源文件階段;2、編譯到class文件階段;3、運行期階段。
我們可以通過元注解來配置我們自定義注解的生命周期,在實際生產中,用的最多的是在運行期階段使用注解,所以一般情況下我們會將自定義注解的生命周期配置為運行期生效。
元注解
上面說過,我們可以通過元注解來配置注解的生命周期,同樣的我們可以使用元注解配置注解的作用對象等等基本屬性。元注解是一種用於修飾注解的注解。
在JDK 1.5中提供了4個標准的用來對注解類型進行注解的注解類,我們稱之為 meta-annotation(元注解),他們分別是:
@Target、@Retention、@Documented、@Inherited
我們可以使用這4個元注解來對我們自定義的注解類型進行注解,接下來,我們挨個對這4個元注解的作用進行介紹。
1、Target注解:該注解的作用是:描述注解的使用范圍(即:被修飾的注解可以用在什么地方) 。Target注解用來說明那些被它所注解的注解類可修飾的對象范圍:注解可以用於修飾 packages、types(類、接口、枚舉、注解類)、類成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數),在定義注解類時使用了@Target 能夠更加清晰的知道它能夠被用來修飾哪些對象,它的取值范圍定義在ElementType 枚舉中。
2、Reteniton注解:該注解的作用是:描述注解保留的時間范圍(即:被描述的注解在它所修飾的類中可以被保留到何時) 。Reteniton注解用來限定那些被它所注解的注解類在注解到其他類上以后,可被保留到何時,一共有三種策略,定義在RetentionPolicy枚舉中。
3、Documented注解:該注解的作用是:描述在使用 javadoc 工具為類生成幫助文檔時是否要保留其注解信息。
4、Inherited注解:該注解的作用是:使被它修飾的注解具有繼承性(如果某個類使用了被@Inherited修飾的注解,則其子類將自動具有該注解)。
其中 Target 和 Reteniton 是我們最常用的。
例子
注解的基礎知識比較少,我們直接上手一個實際生產的例子。
在生產中,遇到了一個用於描述評分信息的類,類的屬性是評分時的各個評分項。評分項非常多(getter/setter 全部略過節約篇幅):
public class SysMark extends BaseEntity { private String markReasons; private String fj1ReasonSelf; private Double fj1MarkSelf; private String fj2ReasonSelf; private Double fj2MarkSelf; private String fj3ReasonSelf; private Double fj3MarkSelf; private String fj4ReasonSelf; private Double fj4MarkSelf; private String fj5ReasonSelf; private Double fj5MarkSelf; private String jf1ReasonSelf; private Double jf1MarkSelf; private String jf2ReasonSelf; private Double jf2MarkSelf; private String jf3ReasonSelf; private Double jf3MarkSelf; private String jf4ReasonSelf; private Double jf4MarkSelf; private String jf5ReasonSelf; private Double jf5MarkSelf; private String jf6ReasonSelf; private Double jf6MarkSelf; private String llxz1ReasonSelf; private Double llxz1MarkSelf; private String llxz2ReasonSelf; private Double llxz2MarkSelf; private String llxz3ReasonSelf; private Double llxz3MarkSelf; private String llxz4ReasonSelf; private Double llxz4MarkSelf; private String zygw1ReasonSelf; private Double zygw1MarkSelf; private String zygw2ReasonSelf; private Double zygw2MarkSelf; private String zygw3ReasonSelf; private Double zygw3MarkSelf; private String jsyw1ReasonSelf; private Double jsyw1MarkSelf; private String jsyw2ReasonSelf; private Double jsyw2MarkSelf; private String jsyw3ReasonSelf; private Double jsyw3MarkSelf; private String jsyw4ReasonSelf; private Double jsyw4MarkSelf; private String zgsx1ReasonSelf; private Double zgsx1MarkSelf; private String zgsx2ReasonSelf; private Double zgsx2MarkSelf; private String zgsx3ReasonSelf; private Double zgsx3MarkSelf; private String zgsx4ReasonSelf;private Double zgsx4MarkSelf; private java.sql.Timestamp markTime; private java.sql.Timestamp updateTime; private String startTime; private String endTime; private String markId; private Double markMark; private String markMonth; private String markYear; private String markJiDu; private long uid; private String deptId;
}
目前接到的需求是,將評分項匯總,組成一個 “評分項1:得分1;評分項2:得分2...” 的字符串。其中每個評分項得分在實際語義中包含三種情況:加分項,減分項,否決項(總分直接判0)。
在通常情況下,我們一般便會通過 if - else 逐個判斷每個屬性是否有打分,判斷該評分項的類型來決定得分是加分、減分還是直接判 0 。
但這樣做會帶來很多不方便,幾十個 if 判斷不說,萬一我們要新增或減少一個評分項或者配錯/漏配了一個評分項,我們需要那 if 的代碼逐行的與類中的屬性比對,非常的繁瑣。這時候我們可以考慮自定義一個注解用於修飾這些屬性,在定義屬性時便為其附加 評分項名稱 以及加減分/否決的屬性,然后通過反射對所有屬性統一進行處理。
我們來定義出這個注解:
/** * @Author Nxy * @Date 2020/2/15 13:17 * @Description 自定義評分原因注解 */ @Retention(RetentionPolicy.RUNTIME) @Target(value = ElementType.FIELD) public @interface MarkReason { //評分項目名稱
public String reasonName(); //是否減分項
public boolean isSubtraction() default true; //是否否決項
public boolean isFouJue() default false; }
其中兩個元注解的含義便是:@Retention(RetentionPolicy.RUNTIME):該注解在運行期生效;@Target(value = ElementType.FIELD):該注解作用於屬性。
我們為注解定義了三個屬性 resonName、isSubtraction、isFouJue,被 MarkReason 注解修飾的屬性可以擁有這三個屬性。
我們用該注解對上述評分類進行修飾后評分類變成這樣:
public class SysMark extends BaseEntity { private String markReasons; private String fj1ReasonSelf; @MarkReason(reasonName = "責任區黨員群眾發生違法行為", isFouJue = true) private Double fj1MarkSelf; private String fj2ReasonSelf; @MarkReason(reasonName = "責任區黨員群眾發生撞“紅線”及以上問題", isFouJue = true) private Double fj2MarkSelf; private String fj3ReasonSelf; @MarkReason(reasonName = "責任區黨員群眾參與群體上訪、越級上訪", isFouJue = true) private Double fj3MarkSelf; private String fj4ReasonSelf; @MarkReason(reasonName = "責任區發生打架斗毆等不良行為", isFouJue = true) private Double fj4MarkSelf; private String fj5ReasonSelf; @MarkReason(reasonName = "經研究其他否決項目的問題", isFouJue = true) private Double fj5MarkSelf; private String jf1ReasonSelf; @MarkReason(reasonName = "責任區黨員群眾發現安全重大隱患、防止安全事故、受到段級及以上表揚表彰或通報嘉獎", isSubtraction = false) private Double jf1MarkSelf; private String jf2ReasonSelf; @MarkReason(reasonName = "責任區黨員群眾參加技術比武獲得名次", isSubtraction = false) private Double jf2MarkSelf; private String jf3ReasonSelf; @MarkReason(reasonName = "積極組織責任區黨員群眾 圍繞安全、運輸和技術難題立項攻關取得實效,受到總公司、 集團公司、段表彰", isSubtraction = false) private Double jf3MarkSelf; private String jf4ReasonSelf; @MarkReason(reasonName = "責任區黨員群眾完成急難險重任務成績突出", isSubtraction = false) private Double jf4MarkSelf; private String jf5ReasonSelf; @MarkReason(reasonName = "責任區黨員群眾做好人好事、見義勇為事跡受到表彰獎勵或媒體表揚", isSubtraction = false) private Double jf5MarkSelf; private String jf6ReasonSelf; @MarkReason(reasonName = "其他受到集團公司及以上表彰獎勵", isSubtraction = false) private Double jf6MarkSelf; private String llxz1ReasonSelf; @MarkReason(reasonName = "責任區黨員群眾無故不參加上級組織的集體活動") private Double llxz1MarkSelf; private String llxz2ReasonSelf; @MarkReason(reasonName = "責任區內環境衛生差、備品擺放不整齊") private Double llxz2MarkSelf; private String llxz3ReasonSelf; @MarkReason(reasonName = "班組標准化驗收不達標") private Double llxz3MarkSelf; private String llxz4ReasonSelf; @MarkReason(reasonName = "班組未完成生產任務,運輸組織工作,旅客、貨主等服務工作受到上級批評") private Double llxz4MarkSelf; private String zygw1ReasonSelf; @MarkReason(reasonName = "責任區黨員群眾發生遲到、早退") private Double zygw1MarkSelf; private String zygw2ReasonSelf; @MarkReason(reasonName = "責任區黨員群眾發生嚴重“兩違”問題") private Double zygw2MarkSelf; private String zygw3ReasonSelf; @MarkReason(reasonName = "作業提醒不到位") private Double zygw3MarkSelf; private String jsyw1ReasonSelf; @MarkReason(reasonName = "不參加月度業務考試、模擬演練") private Double jsyw1MarkSelf; private String jsyw2ReasonSelf; @MarkReason(reasonName = "責任區黨員群眾月度考試或抽考成績不達標") private Double jsyw2MarkSelf; private String jsyw3ReasonSelf; @MarkReason(reasonName = "應知應會考試、專業技能考核不達標") private Double jsyw3MarkSelf; private String jsyw4ReasonSelf; @MarkReason(reasonName = "責任區黨員群眾技術業務幫帶效果不明顯") private Double jsyw4MarkSelf; private String zgsx1ReasonSelf; @MarkReason(reasonName = "對責任區內職工思想動態不掌握、不熟悉、不了解,不能及時做思想工作") private Double zgsx1MarkSelf; private String zgsx2ReasonSelf; @MarkReason(reasonName = "未及時與發生“兩違”問題的黨員群眾談心談") private Double zgsx2MarkSelf; private String zgsx3ReasonSelf; @MarkReason(reasonName = "未及時與困難黨員群眾談心談話") private Double zgsx3MarkSelf; private String zgsx4ReasonSelf; @MarkReason(reasonName = "未及時化解矛盾造成不良影響") private Double zgsx4MarkSelf; private java.sql.Timestamp markTime; private java.sql.Timestamp updateTime; private String startTime; private String endTime; private String markId; private Double markMark; private String markMonth; private String markYear; private String markJiDu; private long uid; private String deptId;
}
這樣一來,在每個屬性被定義時,它的語義便被一同寫進了其注解中。我們通過反射獲取每個屬性的注解,對所有屬性進行統一的處理:
/** * @Author Nxy * @Date 2020/2/15 14:14 * @Description 匯總加減分原因 */
public static void setMarkReasons(BaseEntity markBean) throws IllegalAccessException, NoSuchFieldException { Class beanClass = markBean.getClass(); Field[] fields = beanClass.getDeclaredFields(); if (fields == null || fields.length == 0) { throw new RuntimeException(markBean + " has no field"); } Field targetField = beanClass.getDeclaredField("markReasons"); StringBuilder reasonsSb = new StringBuilder(); //遍歷屬性
for (Field field : fields) {
//判斷該屬性是否被 MarkReason 注解修飾 if (field.isAnnotationPresent(MarkReason.class)) { //允許私有屬性訪問
field.setAccessible(true); String isSubtraction = "-"; MarkReason reasonAnno = field.getAnnotation(MarkReason.class);
Object markMark = field.get(markBean);
//判斷該項是否已評分
if(markMark==null){
continue;
} //判斷是否是減分項
if (!reasonAnno.isSubtraction()) { isSubtraction = "+"; } String project=""; //判斷是否否決項
if(reasonAnno.isFouJue()){ project = "-100"; }else { project = isSubtraction + (double) markMark; } //拼裝加減分原因
reasonsSb.append(reasonAnno.reasonName() + ":" + project + ";"); } }
//匯總后將結果寫入對象 targetField.setAccessible(true); targetField.set(markBean, reasonsSb.toString()); }
通過上面的方法,我們只需要在定義一個新的評分項時將其用 @MarkReason 注解修飾即可,匯總評分原因的代碼不需要做任何改變。而使用傳統的 if 判斷的方法時,新增/刪除/改變一個評分項,相應的 if 節點的代碼都需要做出對應的改變。並且相較 if 判斷,該方法的代碼量也減少了非常多。