【進階之路】自定義註解介紹與實戰
阿新 • • 發佈:2021-02-24
在使用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