詳解Java注解機(jī)制

上篇詳細(xì)研究了Java中的反射操作以及Class類相關(guān)內(nèi)容闻坚,但在Java開發(fā)過程中,除了反射兢孝,往往還有泛型窿凤、
注解等相關(guān)特性操作組合使用來實(shí)現(xiàn)一些高級(jí)技術(shù),如Spring中就大量使用了反射和注解跨蟹,實(shí)現(xiàn)了諸如Bean
容器管理機(jī)制等操作雳殊,SpringMvc框架中大量使用了注解,實(shí)現(xiàn)了servlet容器的簡易操作等窗轩,現(xiàn)在我們開始詳
細(xì)的學(xué)習(xí)Java中的注解機(jī)制

注解是什么

日常開發(fā)中經(jīng)常提到注解夯秃,那么注解是什么呢?在Java中痢艺,注解就是給程序添加一些信息仓洼,用字符@開頭,可以用來修飾后續(xù)的其他代碼元素堤舒,比如類色建、接口、字段植酥、方法、參數(shù)、構(gòu)造方法等友驮,往往注解還搭配編譯器漂羊、程序運(yùn)行時(shí)以及其他工具或者插件使用,用于實(shí)現(xiàn)代碼功能的增強(qiáng)或者修改程序的行為等操作

Java內(nèi)置注解

在Java中內(nèi)置了一些注解卸留,用來在類走越、方法申明使用,從而實(shí)現(xiàn)編譯器檢查耻瑟、避免編譯器檢查等操作旨指,同時(shí)也提高了java邏輯的嚴(yán)謹(jǐn)性,而在Java中內(nèi)置的常見注解莫過于@Override喳整、@Deprecated谆构、@SuppressWarnings三個(gè),下面分別介紹這三個(gè)常見的java內(nèi)置注解的作用

@Override

@Override注解修飾于方法上框都,表明當(dāng)前方法不是當(dāng)前類首先申明的搬素,而是由父類或者接口中繼承來的方法,并且當(dāng)前類進(jìn)行了方法重寫操作魏保,比如:

public class Base {
    public void action() {};
}

現(xiàn)在有一個(gè)父類Base熬尺,申明一個(gè)方法--action,現(xiàn)在有一個(gè)子類繼承了Base類谓罗,并且重寫了action方法的內(nèi)部實(shí)現(xiàn)粱哼,如下:

public class Child extends Base {
    @Override
    public void action(){
        System.out.println("child action");
    }
}

可以看到在Child類的action方法上有一個(gè)@Override注解,代表著此方法是Child類重新實(shí)現(xiàn)并且繼承于Base類檩咱,但是當(dāng)我們把這個(gè)注解刪除揭措,發(fā)現(xiàn)工程無論是編譯還是運(yùn)行,和之前一般無二税手,那么編譯器為什么還要給我們默認(rèn)添加這個(gè)注解呢蜂筹?其實(shí)我們可以反過來思考,如果我們的類中有一個(gè)方法芦倒,并且使用了@Override注解進(jìn)行修飾艺挪,但是當(dāng)前類并沒有從別的類或者接口繼承來這個(gè)方法,或者此類本身就是獨(dú)立的類兵扬,這個(gè)時(shí)候會(huì)發(fā)生什么呢麻裳?

public class Child extends Base {
    @Override
    public void action(){
        System.out.println("child action");
    }
    //action1方法在Base中不存在
    @Override
    public void action1(){
        System.out.println("child class");
    }
}

很明顯當(dāng)我們加上注解的瞬間,編譯器就會(huì)直接提示異常器钟,要求我們刪除當(dāng)前注解津坑。從這可以看出來,@Override注解有助于幫助編譯器檢查語法錯(cuò)誤傲霸,執(zhí)行更嚴(yán)格的代碼檢查疆瑰。

@Deprecated

@Deprecated注解可以修飾的范圍很廣眉反,不僅可以作用在方法上,還可以修飾類穆役、字段以及參數(shù)等所有注解可以修飾的范圍寸五,此注解代表被修飾的元素已經(jīng)過時(shí),并且警告使用者建議使用其他方法耿币,不再使用當(dāng)前方法梳杏。例如,Date類中就有很多方法被標(biāo)識(shí)已經(jīng)過時(shí)了:

@Deprecated
public Date(int year, int month, int date){
    .....
}
    
@Deprecated
public int getYear(){
    .....
}

當(dāng)我們?cè)谑褂眠@些方法的時(shí)候淹接,ide往往會(huì)給我們加上一個(gè)刪除線用來輔助提示調(diào)用者此方法已經(jīng)過時(shí)十性,隨時(shí)都可能在下個(gè)版本刪除,不建議使用塑悼。而在Java9中劲适,Deprecated注解又多了可填的屬性,分別是sinceforRemoval拢肆,since屬性類型為String類型,表示被修飾的元素從哪一個(gè)版本號(hào)開始過時(shí)减响,不建議使用,而forRemoval屬性為Boolean類型郭怪,表示將來在過時(shí)的時(shí)候是否會(huì)刪除當(dāng)前元素支示,標(biāo)記為true代表將來會(huì)刪除此元素,使用者請(qǐng)慎用鄙才。而在Java9中颂鸿,Integer包裝類的構(gòu)造方法中就有使用此特性標(biāo)識(shí)的,如下:

@Deprecated(since="9")
public Integer(int value) {
    this.value = value;
}

@SuppressWarnings

@SuppressWarnings注解則是表示壓制Java中的編譯警告措施攒庵,使得編譯器放棄被修飾元素的編譯警告嘴纺,此注解有一個(gè)必填參數(shù),表示壓制的是哪種類型的警告浓冒,此注解也可以使用在幾乎所有元素中栽渴。比如我們?cè)陂_發(fā)中使用了Date類中的一些過期或者有異常拋出風(fēng)險(xiǎn)的方法,ide往往就會(huì)添加一個(gè)警告的黃線稳懒,而我們?cè)诜椒ㄉ咸砑覢SuppressWarnings注解闲擦,則會(huì)發(fā)現(xiàn)警告消失不見,如下:

//添加在方法上场梆,取消掉編譯器的嚴(yán)格檢查警告機(jī)制
@SuppressWarnings({"deprecation","unused"})
public static void main(String[] args) {
    Date date = new Date(2019, 10, 12);
    int year = date.getYear();
}

常見的庫中的注解

日常開發(fā)使用的庫中也有著大量的注解墅冷,例如Jackson、SpringMvc等或油,下面就簡單介紹下常見庫中的常見注解使用

Jackson

Jackson是一個(gè)通用的序列化庫寞忿,程序員使用過程中可以使用它提供的注解機(jī)制對(duì)序列化進(jìn)行定制化操作,比如:

.使用@JsonIgnore和@JsonIgnoreProperties配置序列化的過程中忽略部分字段

.使用@JsonManagedReference和@JsonBackReference可以配置實(shí)例之間的互相引用

.使用@JsonProperty和@JsonFormat配置序列化的過程中字段名稱和屬性字段的格式等

Servlet3.0

隨著web開發(fā)技術(shù)的發(fā)展顶岸,Java web已經(jīng)發(fā)展到了Servlet3.0腔彰,在早期使用Servlet的時(shí)候叫编,我們只能在web.xml中配置,但是當(dāng)我們使用Servlet3.0的時(shí)候開始霹抛,已經(jīng)開始支持注解了宵溅,比如我們可以使用@WebServlet配置一個(gè)類為Servlet類,如下:

@WebServlet(urlPatterns = "/async", asyncSupported = true)
public class AsyncDemoServlet extends HttpServlet {
  //..............
}
SpringMvc

同樣的上炎,在web開發(fā)中,我們往往還會(huì)使用SpringMvc框架來簡化開發(fā)雏搂,其框架的大量注解可以幫助我們減少大量的業(yè)務(wù)代碼藕施,例如一個(gè)請(qǐng)求的參數(shù)和字段/實(shí)例之間的映射關(guān)系,一個(gè)方法使用的是Http的什么請(qǐng)求方法凸郑,對(duì)應(yīng)請(qǐng)求的某個(gè)路徑裳食,同樣的請(qǐng)求如何解析,返回的響應(yīng)報(bào)文格式定義等芙沥,這些都可以使用注解來簡化實(shí)現(xiàn)诲祸,一個(gè)簡單的Mvc操作如下:

@Controller
@RequestMapping("/hello")
public class HelloController {

    @GetMapping("/test")
    @ResponseBody
    public String test(){
        return "hello test";
    }
}

其中@Controller注解標(biāo)明當(dāng)前的類是SpringMvc接管的一個(gè)Bean實(shí)例,@RequestMapping("/hello")則是代表當(dāng)前Bean的前置請(qǐng)求路徑比如是/hello開頭而昨, @GetMapping("/test")則是表示test方法被訪問必須是Http請(qǐng)求的get請(qǐng)求救氯,并且路徑必須是/hello/test為路徑前置,@ResponseBody注解則是標(biāo)明了當(dāng)前請(qǐng)求的相應(yīng)信息按照默認(rèn)的格式返回(根據(jù)后綴名來確定格式)

注解的創(chuàng)建

從上面我們可以看到使用注解的確可以很方便的簡化我們開發(fā)過程歌憨,因此很多庫和開發(fā)過程中着憨,也會(huì)使用大量的注解簡化開發(fā),那么這些注解我們?nèi)绾螌?shí)現(xiàn)呢务嫡?首先我們先看看最常見的注解@Override的創(chuàng)建:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

從上面我們可以看到甲抖,Override類中使用了@Target元注解和@Retention元注解來定義整個(gè)注解,@Target表示需要注解的目標(biāo)心铃,@Retention則是標(biāo)明當(dāng)前注解的信息可以保留到Java的什么階段准谚,而除了這兩個(gè)元注解以外,還有兩個(gè)元注解@Documented和@Inherited去扣,@Documented用來表示當(dāng)前的注解信息是否包含到文檔中柱衔,@Inherited注解則是和注解之間的繼承有對(duì)應(yīng)的關(guān)系,那么這些元注解具體有什么作用厅篓,以及具體有哪些參數(shù)可以選擇呢秀存?接下來我們便分別學(xué)習(xí)一下

@Target

@Target注解表示當(dāng)前注解可以使用在什么類型的元素上,這里的值可以多選羽氮,即一個(gè)注解可以作用在多種不同類型的元素上净嘀,具體的可選值在ElementType枚舉類中,值如下:

取值 解釋
TYPE 表示作用在類纫雁、接口上
FIELD 表示作用在字段,包括枚舉常量中
METHOD 表示作用在方法中
PARAMETER 表示作用在方法中的參數(shù)中
CONSTRUCTOR 表示作用在構(gòu)造方法中
LOCAL_VARIABLE 表示作用在本地常量中
MODULE 表示作用在部分模塊中(Java9引入的概念)
ANNOTATION_TYPE 表示當(dāng)前注解作用在定義其他注解中祈纯,即元注解
PACKAGE 表示當(dāng)前注解使用在包的申明中
TYPE_PARAMETER 表明當(dāng)前注解使用在類型參數(shù)的申明中(Java8新增)
TYPE_USE 表明當(dāng)前注解使用在具體使用類型中(Java8新增)

當(dāng)使用多個(gè)作用域范圍的時(shí)候,使用{}包裹多個(gè)參數(shù)叼耙,比如@SuppressWarnings注解的Target就有多個(gè)腕窥,在Java7中的定義為:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

即代表SuppressWarnings注解均可以作用在這七種元素上

@Retention

@Retention注解則是表明了當(dāng)前注解可以保留到Java多個(gè)階段的哪一個(gè)階段,參數(shù)類型為RetentionPolicy枚舉類筛婉,可取值如下:

取值 解釋
SOURCE 此注解僅在源代碼階段保留簇爆,編譯后即丟失注解部分
CLASS 表示編譯后依然保留在Class字節(jié)碼中,但是加載時(shí)不一定會(huì)在內(nèi)存中
RUNTIME 表示不僅保留在Class字節(jié)碼中爽撒,一直到內(nèi)存使用時(shí)仍然存在

此注解有默認(rèn)值入蛆,即當(dāng)我們沒有申明@Retention的時(shí)候,默認(rèn)則是Class取值范圍

@Documented

@Documented注解沒有具體的參數(shù)硕勿,使用此元注解哨毁,則表示帶有類型的注解將由javadoc記錄

@Inherited

@Inherited注解與注解的繼承有關(guān)系,具體關(guān)系為如果使用了當(dāng)前的元注解源武,則表示此注解可以被其他的注解的子類直接繼承扼褪,但是需要注意的是對(duì)已實(shí)現(xiàn)接口上的注解將沒有作用。

我們通過一個(gè)案例來了解@Inherited注解的作用粱栖,首先我們定義一個(gè)Test注解:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}

Test注解上有@Inherited元注解修飾话浇,則表明Test注解會(huì)被繼承,接著我們?cè)贐ase類上使用Test注解:

@Test
public class Base {
}

這個(gè)時(shí)候?qū)崿F(xiàn)一個(gè)子類Child闹究,我們來通過反射類Class中的isAnnotationPresent方法打印凳枝,看此類中的Test是否存在且是來自于父類繼承而來:

public class Child extends Base {
}
-----------------------------------
 public static void main(String[] args) {
    System.out.println(Child.class.isAnnotationPresent(Test.class));//true
}

最后輸出的結(jié)果為true,則表明Child類中存在@Test注解跋核,并且是由繼承父類而來

使用注解實(shí)現(xiàn)簡單定制序列化

上面我們已經(jīng)學(xué)習(xí)了注解定義和創(chuàng)建相關(guān)的內(nèi)容岖瑰,接下來我們利用注解簡單實(shí)現(xiàn)通用格式化輸出的類SimpleFormatter,類中有一個(gè)方法format砂代,方法參數(shù)為Object類型的實(shí)例對(duì)象蹋订,即表明了對(duì)象的序列化方式,類定義如下:

/**
 * 通用格式轉(zhuǎn)換輸出類
 */
public class SimpleFormatter {
    /**
     * 通用格式化方法==>將obj對(duì)象輸出為String
     * @param obj
     * @return
     */
    public static String format(Object obj){
        try{
            Class<?> cls = obj.getClass();
            StringBuilder builder = new StringBuilder();
            for (Field field : cls.getDeclaredFields()) {
                if(!field.isAccessible()){
                    field.setAccessible(true);//放棄java安全檢測刻伊,設(shè)置可以訪問私有字段
                }
                //獲取Label注解-輸出的字段名稱
                Label label = field.getAnnotation(Label.class);
                String name = null == label ? field.getName() : label.value();
                //獲取字段對(duì)應(yīng)的value
                Object value = field.get(obj);
                //如果是Date類型露戒,走時(shí)間格式化
                if(null != value && field.getType() == Date.class){
                    value = formatter(field,value);
                }
                builder.append(name + "?" + value + "\n");
            }
            return builder.toString();
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException("格式化輸出失敗:"+e.getMessage());
        }
    }

    /**
     * 針對(duì)時(shí)間類型字段進(jìn)行格式化的方法
     */
    private static Object formatter(Field field, Object value) {
        Format format = field.getAnnotation(Format.class);
        if(null == format){
            return value;
        }
        String pattern = format.pattern();
        String timezone = format.timezone();
        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
        sdf.setTimeZone(TimeZone.getTimeZone(timezone));
        return sdf.format(value);
    }
}

而除了格式化的類以外,我們還定義了兩個(gè)注解捶箱,一個(gè)用來表明格式化的時(shí)候的字段名稱智什,一個(gè)用來針對(duì)時(shí)間格式字段輸出的格式,如下:

/**
 * Labl表明當(dāng)前字段輸出的名稱丁屎,僅作用在字段上
 */
@Retention(RUNTIME)
@Target(FIELD)
public @interface Label {
    String value() default "";
}
--------------------------------
/**
 * Format注解作用在字段上荠锭,針對(duì)時(shí)間字段類型的輸出格式
 */
@Retention(RUNTIME)
@Target(FIELD)
public @interface Format {
    String pattern() default "yyyy-MM-dd HH:mm:ss";
    String timezone() default "GMT+8";
}

除此之外,我們還需要一個(gè)實(shí)例類:

public class Student {
    @Label("姓名")
    private String name;
    @Label("出生日期")
    @Format(pattern="yyyy/MM/dd")
    private Date born;
    @Label("分?jǐn)?shù)")
    private double score;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Date getBorn() {
        return born;
    }

    public void setBorn(Date born) {
        this.born = born;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }
}

定義完畢后晨川,我們可以這么來使用:

public static void main(String[] args) throws ParseException {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        Student zhangsan = new Student();
        zhangsan.setName("張三");
        zhangsan.setBorn(format.parse("1990-12-12"));
        zhangsan.setScore(655);
        System.out.println(SimpleFormatter.format(zhangsan));
    }

輸出的結(jié)果為:

姓名?張三
出生日期?1990/12/12
分?jǐn)?shù)?655.0

至此证九,一個(gè)簡單的格式化輸出類就完成了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末删豺,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子愧怜,更是在濱河造成了極大的恐慌呀页,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拥坛,死亡現(xiàn)場離奇詭異蓬蝶,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)猜惋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門疾党,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人惨奕,你說我怎么就攤上這事〗叨郏” “怎么了梨撞?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長香罐。 經(jīng)常有香客問我卧波,道長,這世上最難降的妖魔是什么庇茫? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任港粱,我火速辦了婚禮,結(jié)果婚禮上旦签,老公的妹妹穿的比我還像新娘查坪。我一直安慰自己,他們只是感情好宁炫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布偿曙。 她就那樣靜靜地躺著,像睡著了一般羔巢。 火紅的嫁衣襯著肌膚如雪望忆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天竿秆,我揣著相機(jī)與錄音启摄,去河邊找鬼。 笑死幽钢,一個(gè)胖子當(dāng)著我的面吹牛歉备,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播匪燕,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼威创,長吁一口氣:“原來是場噩夢啊……” “哼落午!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起肚豺,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤溃斋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后吸申,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體梗劫,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年截碴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了梳侨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡日丹,死狀恐怖走哺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情哲虾,我是刑警寧澤丙躏,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站束凑,受9級(jí)特大地震影響晒旅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜汪诉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一废恋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扒寄,春花似錦鱼鼓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至上渴,卻和暖如春岸梨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背稠氮。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國打工曹阔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人隔披。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓赃份,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子抓韩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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