天天看點

Java注解(入門級)

Java注解(入門級)

Java中的注釋就是将中繼資料工具添加到Java元素中。與類、接口或枚舉一樣,注釋在Java中定義了一種類型,它們可以應用于多個Java元素。讀取和解釋注釋的工具将從獲得的元資訊實作許多功能。例如,它們可以確定類之間的一緻性,可以檢查客戶機在運作時傳遞的參數的有效性,可以為一個項目生成大量的基礎代碼

Java注解

前言

近日在閱讀開源項目,發現項目裡好多奇奇怪怪的注解(

@DataScope

@Log

...)看得我一臉懵,不知道大家是否也有過這樣的經曆,回想了一下,發現自己對于注解的知識,好像隻停留在

@Override

。。。異常尴尬,是以今天就補補注解這個知識,并把自己的收獲記錄在此,與大家一同交流,如有不對的地方,敬請指正!

希望本文能給讀者帶來以下收獲:
  • 明白注解是什麼,大概有什麼用
  • 能了解别人代碼裡面注解的作用
  • 能使用自定義注解

什麼是注解

想要了解某個知識點,我首先推薦的都是去官網檢視,下面看看Java官方對注解的解釋:

Annotations, a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.

注解是中繼資料的一種形式,它提供有關程式的資料,但這些資料不是程式本身的一部分。注解對它們注釋的代碼的操作沒有直接影響。

一堆英文讀完,一陣雲裡霧裡。沒關系,這是正常操作,不過我們從翻譯中還是可以了解到注解可以提供資料,并且資料是獨立于程式的,那麼我們大緻可以推斷出,注解其實是介于程式和資料之間的一種媒介,程式和資料通過注解達成了某種聯系,即注解類似一根紅線,把資料和程式關聯在一起。

@Override

開始

通過對Java官方提供的注解解釋的翻譯,我們篩選推斷出了一個關鍵資訊——關聯。那到底如何了解這個詞呢?别急,我們從最熟悉的陌生人

@Override

開始,最熟悉是因為我們知道這是方法重寫,子類覆寫父類方法用到的注解,陌生是因為我們從來沒有點進去了解過這個注解,那接下來就進去看看吧!

import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
           

短短的5行,好像除了第一行,其他啥都不知道。。。不急,我們一行一行來解讀!

  • 注解導入了一個

    annotation

  • 注解的“套娃”行為

    @Target(ElementType.METHOD)

    @Retention(RetentionPolicy.SOURCE)

  • 不同于接口和類的聲明

    public @interface Override { }

除了對新注解不認識,我們大緻可以了解到注解的定義格式,

修飾符 @interface 注解名{}

。(有點接口的感覺)

禁止套娃——元注解

通過對

@Override

的剖析,我們了解了注解的定義格式,不過我們發現注解裡面又有新的注解,本着刨根問底的好奇心,我們繼續進入

@Target

注解一探究竟!

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}
           

一直點選,發現始終在

@Documented

@Retention

@Target

這幾個注解之間套娃,通過Java文檔我們了解到原來這些修飾注解的注解叫做元注解。元注解(meta-annotation)在

java.lang.annotation

包下:

@Retention

表示如何存儲被标記的注解(指定存儲級别),有以下三個級别

  • RetentionPolicy.SOURCE:隻保留到源碼級别,在編譯階段會被忽略,是以他們不會被寫入位元組碼。
  • RetentionPolicy.CLASS:(預設)編譯級别,在編譯時由編譯器保留,但被Java虛拟機(JVM)忽略。
  • RetentionPolicy.RUNTIME:由JVM保留,可以在運作時環境使用。
@Target

表示被标記的注解可以用于哪種java元素(類、接口、屬性、方法......),有以下八種

作用域 解釋
ElementType.ANNOTATION_TYPE 可用于注解類型
ElementType.CONSTRUCTOR 可以用于構造函數
ElementType.FIELD 可以用于字段或者屬性
ElementType.LOCAL_VARIABLE 可以用于局部變量
ElementType.METHOD 可以用于方法級注解
ElementType.PACKAGE 可以用于包聲明
ElementType.PARAMETER 可以用于方法的參數
ElementType.TYPE 可以用于類的任何元素
@Documented

無論何時使用指定的注解,都應使用Javadoc工具記錄這些元素。(即會在生成的javadoc中加入注解說明)

@Inherited

可以從超類繼承注釋類型,僅用于類的聲明(接口不會繼承)

@Repeatable

在Java SE 8中引入的,表示标記的注釋可以多次應用于相同的聲明或類型使用。

注解的分類

通過對元注解的了解,我明白了一個注解都是由這些元注解修飾而來,而且我們也收獲了一個重要資訊——注解可以修飾注解

這樣無限的套娃,就會有各種各樣的注解,那麼到底有哪些注解呢?常見的注解大緻分為以下四類:

元注解

即上文提及的5個元注解

jdk注解

常見的如

@Override

@Deprecated

@SuppressWarnings

@SafeVarargs

@FunctionalInterface

第三方注解

即第三方架構提供的注解,例如自動注入依賴

@Autowired

@Controller

自定義注解

即開發人員根據項目需求自定義的注解,用于一些工具在編譯、運作時進行解析和使用,起到說明、配置的功能。

實戰——定義自己的注解

看過了Java提供的注解,相信你已經對注解有個大緻的了解了。那你有沒有想過,注解是如何化腐朽為神奇,加了一個簡單的

@Autowired

就能實作依賴注入、

@Setter

就能實作set方法的生成,下面通過簡單的實戰來體會一下注解的神奇之處吧!

實戰目标:

使用自定義注解,通過在實體類及其屬性上加注解,實作對實體類查詢sql語句的構造

ps:類似

select * from t_user where t_name=\'kingwan\'

的形式
自定義注解的編寫規則

在開始實戰之前,我們先了解一下編寫自定義注解的規則:

  • 注解的定義為

    @interface

    ,所有的注解會自動繼承

    java.lang.Annotation

    這個接口,并且不能再去繼承别的類或者接口
  • 參數成員隻能用

    public

    default(預設)

    通路權限符修飾
  • 參數成員隻能用八大基本資料類型、

    String

    Enum

    Class

    annotations

    等資料類型,以及這些類型的數組
  • 要擷取類方法和字段的注解資訊,必須通過java反射機制來擷取
  • 注解也可以沒有定義成員(隻起到辨別作用)

了解了注解的定義規範,接下來我們開始進入正式的實戰環節。

1.自定義注解

@KingwanTable

KingwanColumn

對于實體類查詢的sql語句,我們需要知道兩個資訊:①查詢的表名②字段名。并且我們通常習慣将使用者表

t_user

對應于實體類

User

,那麼我們如何和把

t_user

User

進行關聯呢?一想到關聯,回顧我們最開始從官方文檔中提取出來的資訊,沒錯,就是使用注解關聯。接下來定義兩個自定義注解:

  • @KingwanTable

    :注解實體類對應的表名
    @Target(ElementType.TYPE)//作用在類/接口上
    @Retention(RetentionPolicy.RUNTIME)//保留作用域:保留到運作時
    public @interface KingwanTable {
        String value();//參數:表名
    }
               
  • @KingwanColumn

    :注解實體類屬性對應的表字段名
    @Target(ElementType.FIELD)//表示作用在字段上
    @Retention(RetentionPolicy.RUNTIME)//保留到運作時
    public @interface KingwanColumn {
        String value();//參數:字段名
    }
               
2.實體類添加上自定義注解

有了自定義的兩個注解,那麼我們現在就可以把它們加在實體類上。

  • 以下代碼定義了一個

    Student

    實體類,加上了

    @KingwanTable("t_student")

    映射表名,

    @KingwanColumn("stu_birth")

    映射字段名。
    @Data//簡化實體類的set、get方法
    @KingwanTable("t_student")
    public class Student {
        @KingwanColumn("stu_name")
        private String stuName;
        @KingwanColumn("stu_age")
        private Integer stuAge;
        @KingwanColumn("stu_birth")
        private Date stuBirth;
    }
               
  • 以下代碼建立了一個student對象,并初始化資訊
    public static void main(String[] args) {
        Student student = new Student();
        //初始化資訊
        init(student);
    }
    private static void init(Student student) {
        student.setStuName("kingwan");
        student.setStuAge(18);
        student.setStuBirth(new Date());
    }
               
3.反射擷取注解資訊

有了一個加了自定義注解的Student實體類,那麼我們想要構造SQL,就有以下思路:

擷取到注解的資訊(擷取表名、字段名)=>擷取屬性的值(字段值)=>構造SQL

如何擷取呢?規則裡說了,使用反射。

以下代碼通過擷取

student

class

對象,擷取類上的注解

@KingwanTable

資訊。

aClass.isAnnotationPresent

:判斷指定的注解是否存在

public static void main(String[] args) throws Exception {
    StringBuffer sql = new StringBuffer("");//即将拼接的SQL語句
    Student student = new Student();
    //初始化資訊
    init(student);
    //反射擷取class類
    Class<? extends Student> aClass = student.getClass();
    //1. 判斷實體類上是否存在注解@KingwanTable
    boolean exist = aClass.isAnnotationPresent(KingwanTable.class);//傳入我們自定義的注解類
    String tableName = null;
    if(exist){
        //1.1 存在注解即擷取注解值---(表名)
        KingwanTable annotation = aClass.getAnnotation(KingwanTable.class);
        tableName = annotation.value();
        sql.append("select * from ").append(tableName).append(" where 1=1");//拼接SQL
    }
    System.out.println(sql);
}
           

此時SQL列印的結果:

Java注解(入門級)

擷取到了類上的注解資訊,接下來我們來看看如何擷取屬性上的注解資訊

//2. 擷取屬性上的注解
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {
    //2.1 周遊每個屬性上是否有KingwanColumn注解
    KingwanColumn column = field.getAnnotation(KingwanColumn.class);
    if( column != null){
        //2.1.1 擷取該屬性的值
        String fieldName = field.getName();//屬性名
        String methodName = "get"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);//構造getter方法
        Method method = aClass.getMethod(methodName);
        //通過反射代理調用get方法,擷取屬性的值(name=\'kingwan\',age=18....)
        Object invoke = method.invoke(student);
        if(invoke instanceof String){
            String value = (String) invoke;
            //sql拼接
            sql.append(" and ").append(column.value()).append("=").append("\'")
                .append(value).append("\'"); 
        }else{
            //想想還有哪些情況...
        }
    }
    System.out.println(sql);
}
           

此時SQL的結果:

Java注解(入門級)

當然,如果有小夥伴跟着本文敲,可能在這一步就走不下去了,這是因為我們的get方法傳回的字段類型多種多樣,是以僅僅

invoke instanceof String

是不夠的,我們還需要考慮其他情況(

Integer

Date

),限于篇幅原因,這裡不做過多介紹,大家完全可以自行補充,如果想了解我的實作思路,移步:案例源碼位址

這樣,是不是就達到了我們要的效果了,對于任意簡單實體類,我們都可以通過加上該注解實作一個簡單的查找SQL的生成

你學廢了嗎!