關於註解我們應該知道的

寧願呢發表於2019-05-13

從JDK5開始,Java增加對註解的支援,註解可以在編譯,類載入和執行時被讀取,並執行相應一些定義好的處理。通過註解可以在不改變原有程式碼和邏輯的情況下進行一些其他的補充操作。

系統註解

元註解

在java中系統為我們預置了一部分註解,我們可以通過這些註解來定義其他註解的作用和有效範圍等特性。

@Target

@Target用於說明Annotation所修飾的物件範圍,所能修飾的範圍都被定義在列舉類ElementType中。

public enum ElementType {
    TYPE,//表示可以用於類,介面,註解或者列舉定義中
    FIELD,//欄位
    METHOD,//方法(不包括構造方法)
    PARAMETER,//方法的引數
    CONSTRUCTOR,//構造方法上
    LOCAL_VARIABLE,//區域性變數
    ANNOTATION_TYPE,//只能用在註解上
    PACKAGE,//作用包上 package-info.java 
    TYPE_PARAMETER,//表示註解能寫在型別變數(泛型引數)的宣告語句中如 List<Integer> list = new @Save ArrayList<>();
    TYPE_USE //表示註解能寫在使用型別的任何語句中(宣告語句、泛型和強制轉換語句中的型別)。
}
TYPE_PARAMETER
@Target(ElementType.TYPE_PARAMETER)
public @interface Save {
}

public class Test<@Save T> {
    List<Integer> list = new @Save ArrayList<>();//僅用於展示可以用到的地方
}

@Retention

Retention 定義了該Annotation被保留的時間長短:表示需要在什麼級別儲存註解資訊,用於描述註解的生命週期(即被描述的註解在什麼範圍內有效),取值被定義在列舉類RetentionPolicy中:

public enum RetentionPolicy {
    SOURCE,//表示在原始碼時有效,編譯後的檔案沒有該註解,一般該類註解僅用於標識如@SuppressWarnings

    CLASS, //預設行為 自定義註解如果沒有顯示的宣告則預設為該行為 在編譯時不會被拋棄,但是會被虛擬機器拋棄

    RUNTIME //保留到執行時,可以通過反射來獲取 一般該類註解會影響系統的執行
}

@Documented

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

從註解定義可以看到該註解用在註解定義上。

@Documented 用於描述其它型別的 annotation 應該被作為被標註的程式成員的公共 API,因
此可以被如javadoc之類的工具文件化。但是實際使用並不多,有其他更好的替代。

@Inherited

@Inherited是一個標記註解,@Inherited表示被其標註的型別是被繼承的。如果一
個使用了@Inherited 修飾的 annotation 型別被用於一個 class,則這個 annotation 將被用於該class 的子類。

簡單來說就是在子類中如果想要獲取父類被那些註解修飾,那麼子類能拿到的僅僅是被@Inherited標註過得註解。而其他沒有使用 @Inherited的註解是無法再子類獲取的。

標準註解

上面介紹的幾種元註解是在我們進行自定義註解的時候會用到的,而下面我們介紹幾種平時業務開發會經常使用的註解。

@Deprecated

@Deprecated用來描述在當前系統中已經被廢棄不推薦使用的類或方法等。

@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})

如果我們使用了被@Deprecated標註的類或方法等,在進行編譯的時候會顯示相應的提示資訊。

@Override

@Override是我們使用很頻繁的一個註解,由於重寫的操作僅存在於方法中,所以@Override也只能對方法進行標註。

@Override功能主要是用來校驗當前被標註的方法是否為重寫方法,平時我們在繼承抽象類或實現介面時都應使用該註解來標註被重寫的方法。

@SuppressWarnings

@SuppressWarnings用於可選擇的抑制編譯器在編譯時產生警告資訊。

//作用範圍
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})

@SuppressWarnings可選擇的值有很多:

  • deprecation:不產生使用過期方法(...)的警告,@SuppressWarnings("deprecation")
  • unchecked:執行了未檢查的轉換的警告
  • finally:finally語句無法正常完成時的警告
  • ...
  • all:任意型別的警告

自定義註解與解析

定義註解

自定義一個註解及其簡單,使用@interface關鍵字即可完成。同時我們需要確定我們定義的註解使用範圍和其具體用途,根據此來確定使用元註解的哪些引數來修飾我們定義的註解。

這裡我們定義一個@Log註解用於在對資料庫進行增刪改時插入一條日誌進行記錄。由於該註解需要在執行時對資料庫進行修改,所以很明顯作用有效期為RUNTIME,作用範圍可以使方法也可以使類。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Log {
    String value();//value的值為本次操作型別:insert,delete等
}

註解解析

定義介面並實現:

public interface UserService {
    @Log(OperationType.INSERT)
    void save(User user);
}
public class LogProxyExt implements InvocationHandler {

    private Object obj;

    static Connection connection = null;

    public static Connection getConnection() throws ClassNotFoundException, SQLException {
        if(connection == null){
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/fuxi?serverTimezone=UTC", "root", "root");
        }
        return connection;
    }

    public LogProxyExt(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //查詢方法上是否存在該註解
        Log annotation = method.getAnnotation(Log.class);
        if(annotation != null){
            User user = (User) args[0];
            if(user!=null && StringUtils.isNotBlank(user.getName())){
                connection = getConnection();
                PreparedStatement statement = connection.prepareStatement("insert into log(`log_detail`,`operation`) values(?,?)");
                statement.setString(1, user.getName());
                statement.setString(2, annotation.value());
                statement.executeUpdate();
            }
        }

        //查詢類上是否存在該註解
        proxy.getClass().getAnnotation(Log.class);
        //對類上的註解進行解析
        //todo
        Object invoke = method.invoke(obj, args);

        return invoke;
    }

    public static void main(String[] args) {
        UserService operat = new UserServiceImpl();
        LogProxyExt ext = new LogProxyExt(operat);
        //這裡不能是具體的類,必須是介面 否則會丟擲類轉換異常
        UserService proxy = (UserService) Proxy.newProxyInstance(operat.getClass().getClassLoader(), operat.getClass().getInterfaces(), ext);
        User user = new User();
        user.setName("lisi");
        proxy.save(user);
    }
}

上述解析通過JDK動態代理進行實現,對UserService介面進行代理,保證在UserService中任意位置使用@Log註解都能完成插入日誌操作。實際使用可以配合Spring Aop或者攔截器之類的對全域性請求進行處理。

小結

註解是一個很有用的特性,在現有的框架中大多數都已經支援或正在支援註解。使用註解可以大大提升我們平時的開發速度,保證程式碼的簡潔性。除了框架和JDK自帶的註解之外,我們也可以通過自定義註解來對我們的程式碼功能進行檢查和增強。

相關文章