聽說你只會用注解,不會自己寫注解?那有點危險了
Java猿的命根子!
自Java EE框架步入Spring Boot時代之后,注解簡直是Java程序員的命根子啊,面向注解編程成了日常操作!
換句話的意思就是說:如果沒有注解,我們啥也干不了哇(滑稽)。
這豈不是很危險!
所以本文來嘮一嘮關(guān)于注解的相關(guān)操作,并自己動手來寫一個注解感受一下原理。原理性的東西掌握了,心里自然就不慌了。
注解的基本原理
首先必須要說的是,注解它也不是什么高深的玩意兒,沒必要畏懼它!
意如其名,其本來的意思就是用來做標注用:可以在類、字段變量、方法、接口等位置進行一個特殊的標記,為后續(xù)做一些諸如:代碼生成、數(shù)據(jù)校驗、資源整合等工作做鋪墊。
對嘛,就做標記用的嘛!
注解一旦對代碼標注完成,后續(xù)我們就可以結(jié)合Java強大的反射機制,在運行時動態(tài)地獲取到注解的標注信息,從而可以執(zhí)行很多其他邏輯,完成我們想要的自動化工作。
所以,反射必須要學(xué)好!
來!動手造一個注解
在我的前文《聽說你還在手寫復(fù)雜的參數(shù)校驗?》里曾經(jīng)講過, Spring自身提供了非常多好用的注解可以用來方便地幫我們做數(shù)據(jù)校驗的工作。
比如,在沒有注解加持時,我們想要校驗 Student類:
- public class Student {
- private Long id; // 學(xué)號
- private String name; // 姓名
- private String mobile; // 手機號碼(11位)
- }
我們只能通過手寫 if判斷來進行校驗:
- @PostMapping("/add")
- public String addStudent( @RequestBody Student student ) {
- if( student == null )
- return "傳入的Student對象為null,請傳值";
- if( student.getName()==null || "".equals(student.getName()) )
- return "傳入的學(xué)生姓名為空,請傳值";
- if( student.getScore()==null )
- return "傳入的學(xué)生成績?yōu)閚ull,請傳值";
- if( (student.getScore()<0) || (student.getScore()>100) )
- return "傳入的學(xué)生成績有誤,分數(shù)應(yīng)該在0~100之間";
- if( student.getMobile()==null || "".equals(student.getMobile()) )
- return "傳入的學(xué)生電話號碼為空,請傳值";
- if( student.getMobile().length()!=11 )
- return "傳入的學(xué)生電話號碼長度有誤,應(yīng)為11位";
- studentService.addStudent( student ); // 將student對象存入MySQL數(shù)據(jù)庫
- return "SUCCESS";
- }
這樣非常繁瑣!
但是借助于 Spring提供的注解,數(shù)據(jù)校驗工作可以變得非常優(yōu)雅,就像這樣:
- public class Student {
- @NotNull(message = "傳入的姓名為null,請傳值")
- @NotEmpty(message = "傳入的姓名為空字符串,請傳值")
- private String name; // 姓名
- @NotNull(message = "傳入的分數(shù)為null,請傳值")
- @Min(value = 0,message = "傳入的學(xué)生成績有誤,分數(shù)應(yīng)該在0~100之間")
- @Max(value = 100,message = "傳入的學(xué)生成績有誤,分數(shù)應(yīng)該在0~100之間")
- private Integer score; // 分數(shù)
- @NotNull(message = "傳入的電話為null,請傳值")
- @NotEmpty(message = "傳入的電話為空字符串,請傳值")
- @Length(min = 11, max = 11, message = "傳入的電話號碼長度有誤,必須為11位")
- private String mobile; // 電話號碼
- }
于是很多人就表示疑問,這些注解到底如何實現(xiàn)功能的呢?
今天本文則以上文的 @Length注解為例,自己動手實現(xiàn)一遍,這個學(xué)會了,其他注解實現(xiàn)原理也是類似。
總共分三大步實現(xiàn)。
第一步:首先定義注解:@Length
- @Target({ElementType.FIELD})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface Length {
- int min(); // 允許字符串長度的最小值
- int max(); // 允許字符串長度的最大值
- String errorMsg(); // 自定義的錯誤提示信息
- }
下面做幾點說明:
1、注解的定義有點像定義接口 interface,但唯一不同的是前面需要加一個 @符號
2、注解的成員變量只能使用基本類型、 String或者 enum枚舉,比如 int可以,但 Integer這種包裝類型就不行,需注意
3、像上面 @Target、 @Retention這種加在注解定義上面的注解,我們稱為 “元注解”,元注解就是專門用于給注解添加注解的注解,哈哈,很拗口,簡單理解,元注解就是天生就有的注解,可直接用于注解的定義上
4、 @Target(xxx) 用來說明該自定義注解可以用在什么位置,比如:
- ElementType.FIELD:說明自定義的注解可以用于類的變量
- ElementType.METHOD:說明自定義的注解可以用于類的方法
- ElementType.TYPE:說明自定義的注解可以用于類本身、接口或 enum類型
- 等等... 還有很多,如果記不住,建議現(xiàn)用現(xiàn)查
5、 @Retention(xxx) 用來說明你自定義注解的生命周期,比如:
- @Retention(RetentionPolicy.RUNTIME):表示注解可以一直保留到運行時,因此可以通過反射獲取注解信息
- @Retention(RetentionPolicy.CLASS):表示注解被編譯器編譯進 class文件,但運行時會忽略
- @Retention(RetentionPolicy.SOURCE):表示注解僅在源文件中有效,編譯時就會被忽略
所以聲明周期從長到短分別為:RUNTIME > CLASS > SOURCE ,一般來說,如果需要在運行時去動態(tài)獲取注解的信息,還是得用RUNTIME,就像本文所用。
第二步:獲取注解并對其進行驗證
在運行時想獲取注解所代包含的信息,該怎么辦?那當然得用 Java的反射相關(guān)知識!
下面寫了一個驗證函數(shù) validate(),代碼中會逐行用注釋去解釋想要達到的目的,認真看一下每一行的注釋:
- public static String validate( Object object ) throws IllegalAccessException {
- // 首先通過反射獲取object對象的類有哪些字段
- // 對本文來說就可以獲取到Student類的id、name、mobile三個字段
- Field[] fields = object.getClass().getDeclaredFields();
- // for循環(huán)逐個字段校驗,看哪個字段上標了注解
- for( Field field : fields ) {
- // if判斷:檢查該字段上有沒有標注了@Length注解
- if( field.isAnnotationPresent(Length.class) ) {
- // 通過反射獲取到該字段上標注的@Length注解的詳細信息
- Length length = field.getAnnotation( Length.class );
- field.setAccessible( true ); // 讓我們在反射時能訪問到私有變量
- // 用過反射獲取字段的實際值
- int value =( (String)field.get(object) ).length();
- // 將字段的實際值和注解上做標示的值進行比對
- if( value<length.min() || value>length.max() ) {
- return length.errorMsg();
- }
- }
- }
- return null;
- }
可見,學(xué)好Java的反射知識是多么的重要!
第三步:使用注解
這一步比較輕松,使用注解的過程往往都是很愉悅的
- public class Student {
- private Long id; // 學(xué)號
- private String name; // 姓名
- @Length(min = 11, max = 11, errorMsg = "電話號碼的長度必須為11位")
- private String mobile; // 手機號碼(11位)
- }
怎么樣,其實一點也不復(fù)雜吧,主要就是反射相關(guān)的知識!