【進階之路】自定義註解介紹與實戰
在使用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,很多類也加上了這個註解。
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
2、調用方法
3、執行成功
我們還可以嘗試一下,在@NotifyMonitor加上value(因為只有一個屬性,所以value=”xxx” 與 “xxx” 等價)
4、執行結果
這樣,一個註解式的任務處理、通知功能就完成了。自定義註解不僅能夠在方法執行前後進行擴展、獲取到實現註解的方法、所在類等資訊、修改參數和返回值,還能夠實現包括執行緒池、分散式鎖、類數據校驗等等你能想到的大部分操作,我在工作中也實現了其中一些功能,減少了大量的重複程式碼,也讓程式碼的可讀性提高了。
了解到這裡,不妨你也自己動手來寫一個自定義註解來簡化我們的項目吧。
大家好,我是練習java兩年半時間的南橘,下面是我的微信,需要之前的導圖或者想互相交流經驗的小夥伴可以一起互相交流哦。