Java注解詳解相嵌,自定義注解腿时,利用反射解析注解

概要

這篇文章將會帶領(lǐng)你了解Java注解,注解的使用,注解的解析饭宾,利用反射解析運行時注解批糟,相信有一定Java基礎(chǔ)的小伙伴一定會接觸大量的注解,Spring , Hibernate , MyBatis等著名的框架也有很多關(guān)于注解方面的應(yīng)用捏雌,對于注解的使用小伙伴們應(yīng)該一點都不陌生跃赚,那么如何自定義注解呢?學(xué)會自定義注解有什么好處呢性湿?
下面就隨筆者進(jìn)入注解的世界

注解的作用

很多小伙伴在學(xué)習(xí)注解之前纬傲,都不知道學(xué)習(xí)注解到底可以用來干什么,可以給自身帶來什么好處肤频,那么在這里叹括,筆者描述學(xué)習(xí)注解的幾點好處

  • 用過Hibernate的小伙伴,應(yīng)該使用過Hibernate的配置文件來描述ORM(數(shù)據(jù)庫關(guān)系映射)宵荒,Hibernate實現(xiàn)此功能除了寫配置文件之外汁雷,當(dāng)然還包含注解,那么第一個好處就是报咳,注解可以替代配置文件完成對某些功能的描述侠讯,減少程序配置
  • 在沒有配置文件的情況下,我們?nèi)ビ^察代碼暑刃,并不需要同時打開兩個文件來觀察這個字段到底對應(yīng)數(shù)據(jù)庫的哪個列厢漩,減少了程序繁瑣性,使得代碼更加清晰易懂
  • 目前市面上流行的框架基本上都包含了注解配置岩臣,那么針對于開源項目溜嗜,我們在閱讀項目代碼時,不懂注解如何實現(xiàn)架谎,真的是舉步難堅炸宵,所以,學(xué)習(xí)注解也可以加強我們對開源項目源碼的解讀
  • 最重要的一點谷扣,會使用注解和會自定義注解完全是兩碼事土全,我的意思是,讓別人可以高看你一眼(zhuang bi)

了解注解

注解是Java1.5会涎,JDK5.0引用的技術(shù)涯曲,與類,接口在塔,枚舉處于同一層次 幻件。它可以聲明在包、類蛔溃、字段绰沥、方法篱蝇、局部變量、方法參數(shù)等的前面徽曲,用來對這些元素進(jìn)行說明零截,注釋 。

在Java中秃臣,自帶了三種注解涧衙,這三種注解存在于java.lang包中,首先我們講一講這些注解

  • Override——它的作用是對覆蓋超類中方法的方法進(jìn)行標(biāo)記奥此,如果被標(biāo)記的類并沒有實際覆蓋超類弧哎,則編譯器會發(fā)出錯誤警告。
    很常見的一個注解稚虎,了解JavaOOP的小伙伴這個注解應(yīng)該較為常用撤嫩,告訴編譯器,我這個方法是重寫了父類方法蠢终,當(dāng)然如果你的方法并沒有實際重寫父類方法時序攘,那么編譯器就會顯示警告信息
  • Deprecated——它的作用是對不應(yīng)該再使用的方法添加注解,當(dāng)編程人員使用這些方法時寻拂,將會在編譯時顯示提示信息
    當(dāng)一個方法名或者類名上面此注解之后程奠,編譯器會認(rèn)為這個方法屬于過期方法,明顯的區(qū)別在于類名或者方法名上會畫一道刪除線祭钉,標(biāo)識過期方法不影響方法的繼續(xù)使用
  • SuppressWarnings——這個僅僅是告訴編譯器忽略特定的警告信息瞄沙,例如在泛型中使用原生數(shù)據(jù)類型
    例如我們在使用一些以Deprecated注解的方法時,編譯器會提出黃線警告朴皆,那么只要在使用的地方加上@SuppressWarnings(“deprecation”)就可以使編譯器忽略這個警告
    此注釋常用的參數(shù)值有 : deprecation(忽略使用過時類或者方法),unchecked(忽略執(zhí)行了未檢查裝換時警告) 泛粹, fallthrough(忽略switch直接指向到下一個case塊沒有break警告)遂铡,path(忽略類路徑,源文件路徑中有不存在路徑時警告)晶姊,serial(忽略可序列化類中沒有serialVersionUID時的警告)扒接,finally(任何finally不能正常執(zhí)行時的警告),all(以上所有)

自定義注解須知

首先们衙,自定義注解我們必須了解四個元注解钾怔,什么是元注解?元注解指作用于注解之上的元數(shù)據(jù)或者元信息蒙挑,簡單通俗的講宗侦,元注解就是注解的注解 .

  • Documented——指明擁有這個注解的元素可以被javadoc此類的工具文檔化。這種類型應(yīng)該用于注解那些影響客戶使用帶注釋的元素聲明的類型忆蚀。如果一種聲明使用Documented進(jìn)行注解矾利,這種類型的注解被作為被標(biāo)注的程序成員的公共API 姑裂。
  • Inherited——指明該注解類型被自動繼承。如果用戶在當(dāng)前類中查詢這個元注解類型并且當(dāng)前類的聲明中不包含這個元注解類型男旗,那么也將自動查詢當(dāng)前類的父類是否存在Inherited元注解舶斧,這個動作將被重復(fù)執(zhí)行知道這個標(biāo)注類型被找到,或者是查詢到頂層的父類察皇。
  • Retention——指明在什么級別顯示此注解
  • Target——指明該類型的注解可以注解的程序元素的范圍

Documented與Inherited是典型的標(biāo)識性注解茴厉,也就是說在注解內(nèi)部并沒有成員變量,沒有成員變量的注解稱為標(biāo)識注解
Target主要的參數(shù)類型包括以下幾種

  • ElementType.TYPE 用于類什荣,接口矾缓,枚舉但不能是注解
  • ElementType.FIELD 作用于字段,包含枚舉值
  • ElementType.METHOD 作用于方法溃睹,不包含構(gòu)造方法
  • ElementType.PARAMETER 作用于方法的參數(shù)
  • ElementType.CONSTRUCTOR 作用于構(gòu)造方法
  • ElementType.LOCAL_VERIABLE 作用于本地變量或者catch語句
  • ElementType.ANNOTATION_TYPE 作用于注解
  • ElementType.PACKAGE 作用于包

Retention主要的參數(shù)類型包括以下幾種

  • RetentionPolicy.SOURCE 注解存在于源代碼中而账,編譯時會被拋棄
  • RetentionPolicy.CLASS 注解會被編譯到class文件中,但是JVM會忽略
  • RetentionPolicy.RUNTIME JVM會讀取注解因篇,同時會保存到class文件中

自定義注解

首先泞辐,我們來看一段自定義注解實現(xiàn)的代碼

@Documented
@Inherited
//該注解可以作用于方法,類與接口
@Target({ElementType.METHOD,ElementType.TYPE})
//JVM會讀取注解,所以利用反射可以獲得注解
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    //定義成員變量
    //成員變量可以通過default指定默認(rèn)值
    //如果成員變量不指定默認(rèn)值的情況下
    //我們在引用接口時則必須給沒有默認(rèn)值的成員變量賦值
    String name() ; 
    int age() default 18 ;
}

@interface 用于定義注解接口,接口中只能定義成員變量竞滓,且定義的成員變量必須以()結(jié)尾咐吼,可以使用default關(guān)鍵字為成員變量指定默認(rèn)值,如果不為成員變量指定默認(rèn)值的情況商佑,則必須在引用注解時锯茄,對沒有默認(rèn)值的成員變量進(jìn)行賦值操作

注解的使用規(guī)則:

//@注解名(變量1=變量1值,變量2=變量2值,...)
//如果注解中擁有數(shù)組類型,假設(shè)是String類型,那么賦值方式可以如下
//@注解名(String數(shù)組名稱={"tset1","test2","test3"})

@TestAnnotation(name="Taro")

//因為我們注解中的age()是擁有默認(rèn)值的,所以這邊可以不為age()賦值
//如果我們的注解中只有一個成員變量,且成員變量的名稱為value()
//那么可以使用如下賦值方式
//@注解名(屬性值)
//如果我們的注解中沒有成員變量,那么此時的注解被稱為標(biāo)識注解

注解中可以定義的數(shù)據(jù)類型是受到限制的,除了基本類型之外茶没,String肌幽,Enums,Annotation,Class還有這些類型的數(shù)組
如何使用我們剛剛定義的注解呢?剛剛的注解我們聲明了是針對方法和類或者接口生效璃弄,那么我們來看看使用方法

@TestAnnotation(name="I'm class annotation")
public class Test {

    @TestAnnotation(name="I'm method annotation")
    public static void showAnnotation(){

    }

}

怎么樣畔濒,是不是很easy呢?

解析注解

主要使用Java的反射原理實現(xiàn)對注解的解析,不太懂反射的小伙伴通過筆者的注釋看起來也不會很難

PS :下一篇博文是對Java反射的詳解

public static void main(String[] args) {
    //解析注解
    //獲得我們需要解析注解的類
    Class<Test> clz = Test.class;

    //解析Class
    //由于我們的注解是可以給類使用的,所以首先判斷類上面有沒有我們的注解
    //判斷類上面是否有注解
    boolean clzHasAnnotation = clz.isAnnotationPresent(TestAnnotation.class);
    if(clzHasAnnotation){
        //類存在我們定義的注解
        //獲得注解
        TestAnnotation clzAnnotation = clz.getAnnotation(TestAnnotation.class);
        //輸出注解在類上的屬性
        System.out.println("name="+clzAnnotation.name()+"\tage="+clzAnnotation.age());
    }

    //解析Method
    //兩種解析方法上的注解方式
    //獲得類中所有方法
    Method[] methods = clz.getMethods();
    //第一種解析方法
    for(Method m : methods){
        //獲得方法中是否含有我們的注解
        boolean methodHasAnnotation = m.isAnnotationPresent(TestAnnotation.class);
        if(methodHasAnnotation){
            //注解存在
            //獲得注解
            TestAnnotation methodAnnotation = m.getAnnotation(TestAnnotation.class);

            System.out.println("name="+methodAnnotation.name()+"\tage="+methodAnnotation.age());
        }
    }
    //第二種解析方式
    for(Method m : methods){
        //獲得方法上所有注解
        Annotation[] annotations = m.getAnnotations();
        //循環(huán)注解
        for(Annotation a : annotations){
            //如果是我們自定義的注解
            if(a instanceof TestAnnotation){
                //輸出屬性,需要強制裝換類型
                System.out.println("name="+((TestAnnotation)a).name()+"\tage="+((TestAnnotation)a).age());
            }
        }
    }

}

最后得出結(jié)果,因為我們使用了兩種解析Method注解的方式,所以最終會得到兩個Method上面的字符串

結(jié)果

例子:利用切面打印web日志


import java.lang.annotation.*;

/**
 * @description 環(huán)繞通知狡孔,增強controller接口響應(yīng)
 * @author jack-cooper
 * @create 2018-05-31 11:22
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ApiResult {
}

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * @desc 環(huán)繞通知,增強controller接口響應(yīng)
 * <pre>
 *          使用@Before在切入點開始處切入內(nèi)容
 *
 *          使用@After在切入點結(jié)尾處切入內(nèi)容
 *
 *          使用@AfterReturning在切入點return內(nèi)容之后切入內(nèi)容(可以用來對處理返回值做一些加工處理)
 *
 *          使用@Around在切入點前后切入內(nèi)容蜂嗽,并自己控制何時執(zhí)行切入點自身的內(nèi)容
 *
 *          使用@AfterThrowing用來處理當(dāng)切入內(nèi)容部分拋出異常之后的處理邏輯
 * </pre>
 *
 * <pre>
 *     https://blog.csdn.net/rainbow702/article/details/52185827
 * </pre>
 *
 * @author jack-cooper
 * @create 2018-05-31 11:28
 */
@Aspect
@Component
public class ApiResultAspect {

    private static final Logger logger = LoggerFactory.getLogger(ApiResultAspect.class);

    /**
     * 切入點
     */
    @Pointcut("@annotation(ApiResult)")
    public void apiResultPointCut(){}


    /**
     * 環(huán)繞通知苗膝,增強controller接口響應(yīng)
     * @param joinPoint
     * @return
     */
    @Around(value = "apiResultPointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        // 接收到請求,記錄請求內(nèi)容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 記錄下請求內(nèi)容
        logger.info("====> 請求 URL : {} , HTTP_METHOD :{} , IP : {} , CLASS_METHOD : {} , ARGS : {}" ,
                request.getRequestURL().toString(),
                request.getMethod(),
                request.getRemoteAddr(),
                joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName(),
                JSON.toJSONString(joinPoint.getArgs())
        );
        //執(zhí)行目標(biāo)方法
        final Object proceed = joinPoint.proceed();
        //所需時間
        long proceedTime =  System.currentTimeMillis() - startTime ;
        JSONObject result = new JSONObject();
        result.put("data", proceed);
        result.put("proceedTime", proceedTime);
        result.put("code", "200");
        result.put("message", "成功");
        logger.info("====> 響應(yīng)結(jié)果:{}",result.toJSONString());
        return result;
    }

}



資料:
https://blog.csdn.net/rainbow702/article/details/52185827

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末植旧,一起剝皮案震驚了整個濱河市荚醒,隨后出現(xiàn)的幾起案子芋类,更是在濱河造成了極大的恐慌,老刑警劉巖界阁,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侯繁,死亡現(xiàn)場離奇詭異,居然都是意外死亡泡躯,警方通過查閱死者的電腦和手機贮竟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來较剃,“玉大人咕别,你說我怎么就攤上這事⌒囱ǎ” “怎么了惰拱?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長啊送。 經(jīng)常有香客問我偿短,道長,這世上最難降的妖魔是什么馋没? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任昔逗,我火速辦了婚禮,結(jié)果婚禮上篷朵,老公的妹妹穿的比我還像新娘勾怒。我一直安慰自己,他們只是感情好声旺,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布笔链。 她就那樣靜靜地躺著,像睡著了一般腮猖。 火紅的嫁衣襯著肌膚如雪鉴扫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天缚够,我揣著相機與錄音幔妨,去河邊找鬼鹦赎。 笑死谍椅,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的古话。 我是一名探鬼主播雏吭,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼陪踩!你這毒婦竟也來了杖们?” 一聲冷哼從身側(cè)響起悉抵,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎摘完,沒想到半個月后姥饰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡孝治,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年列粪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谈飒。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡岂座,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出杭措,到底是詐尸還是另有隱情费什,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布手素,位于F島的核電站鸳址,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏刑桑。R本人自食惡果不足惜氯质,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望祠斧。 院中可真熱鬧闻察,春花似錦、人聲如沸琢锋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吴超。三九已至钉嘹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鲸阻,已是汗流浹背跋涣。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鸟悴,地道東北人陈辱。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像细诸,于是被迫代替她去往敵國和親沛贪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容