1. 程式人生 > >【進階之路】自定義註解介紹與實戰

【進階之路】自定義註解介紹與實戰

在使用spring框架的時候,我們經常會感嘆註解式程式設計真是大大簡化了開發的時間,幾個小小的註解,就能解決一系列的配置問題,讓寫程式碼像寫詩一樣輕鬆明快。 我們都知道,在spring框架的前期,大多使用XML配置進行開發。XML配置起來有時候冗長,如實體類的對映,使用XML進行開發會顯得十分複雜。同時註解在處理一些不變的元資料時有時候比XML方便的多,比如spring 宣告式事務管理,如果用XML寫的程式碼會多的多。**註解與Java Bean緊密結合,既大大減少了配置檔案的體積,又增加了Java Bean的可讀性與內聚性**。 當然,不管使用註解還是XML,滿足需求的前提下,採用最簡單的方法才是最合適的。 今天我就以一個簡單的例子來給大家講解,如何進行自定義註解,幫助我們使用註解開發專案。 ## 一、元註解 首先,我們定義一個類需要用到元註解。 ``` @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface NotifyMonitor { String value() default ""; } ``` ### 1、@Target @Target註解,是專門用來限定某個自定義註解能夠被應用在哪些Java元素上面的。它使用一個列舉型別定義如下: ``` public enum ElementType { /** Class, interface (including annotation type), or enum declaration */ /** 類,介面(包括註解型別)或列舉的宣告 */ TYPE, /** Field declaration (includes enum constants) */ /** 屬性的宣告 */ FIELD, /** Method declaration */ /** 方法的宣告 */ METHOD, /** Formal parameter declaration */ /** 方法形式引數宣告 */ PARAMETER, /** Constructor declaration */ /** 構造方法的宣告 */ CONSTRUCTOR, /** Local variable declaration */ /** 區域性變數宣告 */ LOCAL_VARIABLE, /** Annotation type declaration */ /** 註解型別宣告 */ ANNOTATION_TYPE, /** Package declaration */ /** 包的宣告 */ PACKAGE, } ``` 就像我們之前定義的,@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD,ElementType.TYPE})就是可以運用在註解、方法和類上。 ### 2、@Retention @Retention註解,翻譯為持久力、保持力。即用來修飾自定義註解的生命力。 註解的生命週期有三個階段: - 1、Java原始檔階段; - 2、編譯到.class檔案階段; - 3、執行期階段。 ``` public enum RetentionPolicy { /** * Annotations are to be discarded by the compiler. (註解將被編譯器忽略掉) */ SOURCE, /** * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time. This is the default * behavior. (註解將被編譯器記錄在class檔案中,但在執行時不會被虛擬機器保留,這是一個預設的行為) */ CLASS, /** * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. *(註解將被編譯器記錄在.class檔案中,而且在執行時會被虛擬機器保留,因此它們能通過反射被讀取到) * @see java.lang.reflect.AnnotatedElement */ RUNTIME } ``` 我們使用的@Retention(RetentionPolicy.RUNTIME) 是讓註解將被編譯器記錄在.class檔案中,而且在執行時會被虛擬機器保留,所以它能通過反射被讀取到。 ### 3、@Inherited 在註解上使用@Inherited 表示該註解會被子類繼承,注意,僅針對類,成員屬性、方法並不受此註釋的影響。 對於類來說,子類要繼承父類的註解需要該註解被 @Inherited 標識。 對於成員屬性和方法來說,非重寫的都會保持和父類一樣的註解,而被實現的抽象方法,被重寫的方法都不會有父類的註解。 當@NotifyMonitor註解加在某個類A上時,假如類B繼承了A,則B也會帶上該註解。 我們可以看到,在springboot,很多類也加上了這個註解。 ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6271de25894e40a0b9b60be405978d25~tplv-k3u1fbpfcp-watermark.image) ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/035d75b6ee504e15af1f599d2a6f3ca6~tplv-k3u1fbpfcp-watermark.image) ### 4、@Documented 除了我們在註解類上應用到的之外,@Documented註解的作用是在使用 javadoc 工具為類生成幫助文件時保留其註解資訊。 如果去掉了這個註解,那麼在生成的工具文件上就不會出現這個註解,對於一些內部工具類註解來說可有無可。 ## 二、利用AOP實現自定義註解 我們來實現下面這個場景,執行一個任務,如果任務報錯,我們就通過釘釘通知指定的人員讓他進行處理。 要實現這個功能,我們可能會想到try-catch方式。當然,沒有什麼不對,但是如果要在一百個不同的方法中加入這個邏輯,豈不是要實現100次?於是乎,使用自定義註解的方式或許是不錯的主意。 我寫了一個類來實現上訴方法: ``` @Slf4j @Aspect @Component public class NotifyMonitorAspect { @Autowired private DingDingOpe dingDingOpe; @Autowired private StringRedisTemplate stringRedisTemplate; //相比大家對aop都不會陌生 @Pointcut("@annotation(com.nanju.aop.NotifyMonitor)") private void monitor() {} /** * 處理任務 point.proceed()是用來執行原來的任務 dingDingOpe.sendRobotMsg 是自定了一個方法用來通知釘釘 * * @param point */ @Around("monitor()") public Object doAround(ProceedingJoinPoint point) { String jobName = getJobName(point); Object object = null; try { object = point.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); String url = getUrl(jobName); dingDingOpe.sendRobotMsg(url, "任務處理失敗:"+"{"+throwable.getMessage()+"}", false); } return object; } /** * 獲取Job名稱,這個方法就是利用了NotifyMonitor中的value值,根據不同的方法使用不同的通知 * @param point 切點 */ private String getJobName(ProceedingJoinPoint point) { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); NotifyMonitor jobs = method.getAnnotation(NotifyMonitor.class); if ("".equals(jobs.value())){ return null; } return jobs.value(); } /** * 根據Job名稱獲取通知地址,使用了stringRedisTemplate,提前將輸入埋入redis,也可以放在資料庫裡,配置通知地址 * @param notifyMonitor * */ private String getUrl(String notifyMonitor) { if (Objects.isNull(notifyMonitor)){ return TextUtils.dealNull(stringRedisTemplate.opsForValue().get("warn:dingdingUrl:")); }else{ return TextUtils.dealNull(stringRedisTemplate.opsForValue().get("warn:dingdingUrl:"+notifyMonitor)); } } } ``` 我們測試一下 1、在方法上加上註解@NotifyMonitor ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/218af06567ef4af7a0e89e92602c8796~tplv-k3u1fbpfcp-watermark.image) 2、呼叫方法 ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ffbf4dacf2f34b64bd67e5cc917b8ea4~tplv-k3u1fbpfcp-watermark.image) 3、執行成功 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cf5549358eae4674a9f7cf52fe6c7e08~tplv-k3u1fbpfcp-watermark.image) 我們還可以嘗試一下,在@NotifyMonitor加上value(因為只有一個屬性,所以value="xxx" 與 "xxx" 等價) ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2c10c6f7743047c2bc834679089fb0ae~tplv-k3u1fbpfcp-watermark.image) ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d1d013d1e77a49b0b411511530bcba7b~tplv-k3u1fbpfcp-watermark.image) 4、執行結果 ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/53c5e26c28a94f6198aaa14f1fa0a311~tplv-k3u1fbpfcp-watermark.image) 這樣,一個註解式的任務處理、通知功能就完成了。自定義註解不僅能夠在方法執行前後進行擴充套件、獲取到實現註解的方法、所在類等資訊、修改引數和返回值,還能夠實現包括執行緒池、分散式鎖、類資料校驗等等你能想到的大部分操作,我在工作中也實現了其中一些功能,減少了大量的重複程式碼,也讓程式碼的可讀性提高了。 瞭解到這裡,不妨你也自己動手來寫一個自定義註解來簡化我們的專案吧。 > 大家好,我是練習java兩年半時間的南橘,下面是我的微信,需要之前的導圖或者想互相交流經驗的小夥伴可以一起互相交流哦。 ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4afe4b91459c4a55a1a222f1a903711c~tplv-k3u1fbpfcp-watermark