天天看點

如何使用Java自定義注解?什麼是注解Java内置的注解元注解自定義注解總結

文章已收錄Github精選,歡迎Star: https://github.com/yehongzhi/learningSummary

什麼是注解

注解是JDK1.5引入的新特性,主要用于簡化代碼,提高程式設計的效率。其實在日常開發中,注解并不少見,比如Java内置的

@Override

@SuppressWarnings

,或者Spring提供的

@Service

@Controller

等等,随着這些注解使用的頻率越來越高,作為開發人員當真有必要深入學習一番。

Java内置的注解

先說說Java内置的三個注解,分别是:

@Override

:檢查目前的方法定義是否覆寫父類中的方法,如果沒有覆寫,編譯器就會報錯。

@SuppressWarnings

:忽略編譯器的警告資訊。

如何使用Java自定義注解?什麼是注解Java内置的注解元注解自定義注解總結
如何使用Java自定義注解?什麼是注解Java内置的注解元注解自定義注解總結

@Deprecated

:用于辨別該類或方法已過時,建議開發人員不要使用該類或方法。

如何使用Java自定義注解?什麼是注解Java内置的注解元注解自定義注解總結
如何使用Java自定義注解?什麼是注解Java内置的注解元注解自定義注解總結

元注解

元注解其實就是描述注解的注解。主要有四個元注解,分别是:

@Target

用于描述注解的使用範圍,也就是注解可以用在什麼地方,取值有:

CONSTRUCTOR:用于描述構造器。

FIELD:用于描述字段。

LOCAL_VARIABLE:用于描述局部變量。

METHOD:用于描述方法。

PACKAGE:用于描述包。

PARAMETER:用于描述參數。

TYPE:用于描述類,包括class,interface,enum。

@Retention

表示需要在什麼級别儲存該注釋資訊,用于描述注解的生命周期,取值由枚舉RetentionPoicy定義。

如何使用Java自定義注解?什麼是注解Java内置的注解元注解自定義注解總結

SOURCE:在源檔案中有效(即源檔案保留),僅出現在源代碼中,而被編譯器丢棄。

CLASS:在class檔案中有效(即class保留),但會被JVM丢棄。

RUNTIME:JVM将在運作期也保留注釋,是以可以通過反射機制讀取注解的資訊。

如果隻是做一些檢查性操作,使用SOURCE,比如@Override,@SuppressWarnings。

如果要在編譯時進行一些預處理操作,就用 CLASS。

如果需要擷取注解的屬性值,去做一些運作時的邏輯,可以使用RUNTIME。

@Documented

将此注解包含在 javadoc 中 ,它代表着此注解會被javadoc工具提取成文檔。它是一個标記注解,沒有成員。

如何使用Java自定義注解?什麼是注解Java内置的注解元注解自定義注解總結

@Inherited

是一個标記注解,用來指定該注解可以被繼承。使用 @Inherited 注解的 Class 類,表示這個注解可以被用于該 Class 類的子類。

自定義注解

下面實戰一下,自定義一個注解@LogApi,用于方法上,當被調用時即列印日志,在控制台顯示調用方傳入的參數和調用傳回的結果。

定義注解

首先定義注解

@LogApi

,在方法上使用,為了能在反射中讀取注解資訊,當然是設定為

RUNTIME

@Target(value = ElementType.METHOD)
@Documented
@Retention(value = RetentionPolicy.RUNTIME)
public @interface LogApi {
}           

這種沒有屬性的注解,屬于标記注解。

多說幾句,如果需要傳遞屬性值,也可以設定屬性值value,比如

@RequestMapping

注解。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
    @AliasFor("path")
    String[] value() default {};
}           

如果在使用時。隻設定value值,可以忽略value,比如這樣:

//完整是@RequestMapping(value = {"/list"})
//忽略value不寫
@RequestMapping("/list")
public Map<String, Object> list() throws Exception {
    Map<String, Object> userMap = new HashMap<>();
    userMap.put("1号佳麗", "李嘉欣");
    userMap.put("2号佳麗", "袁詠儀");
    userMap.put("3号佳麗", "張敏");
    userMap.put("4号佳麗", "張曼玉");
    return userMap;
}           

标記注解

剛剛定義完注解之後,就可以在需要的地方标記注解,很簡單。

@LogApi
@RequestMapping("/list")
public Map<String, Object> list() throws Exception {
    //業務代碼...
}           

解析注解

最關鍵的一步來了,解析注解,一般在項目中會使用Spring的AOP技術解析注解,當然如果隻需要解析一次的話,也可以使用Spring容器的生命周期函數。

這裡的場景是列印每次方法被調用的日志,是以使用AOP比較合适。

建立一個切面類

LogApiAspect

進行解析。

@Aspect
@Component
public class LogApiAspect {
    //切面點為标記了@LogApi注解的方法
    @Pointcut("@annotation(io.github.yehongzhi.user.annotation.LogApi)")
    public void logApi() {
    }
    
    //環繞通知
    @Around("logApi()")
    @SuppressWarnings("unchecked")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        long starTime = System.currentTimeMillis();
        //通過反射擷取被調用方法的Class
        Class type = joinPoint.getSignature().getDeclaringType();
        //擷取類名
        String typeName = type.getSimpleName();
        //擷取日志記錄對象Logger
        Logger logger = LoggerFactory.getLogger(type);
        //方法名
        String methodName = joinPoint.getSignature().getName();
        //擷取參數清單
        Object[] args = joinPoint.getArgs();
        //參數Class的數組
        Class[] clazz = new Class[args.length];
        for (int i = 0; i < args.length; i++) {
            clazz[i] = args[i].getClass();
        }
        //通過反射擷取調用的方法method
        Method method = type.getMethod(methodName, clazz);
        //擷取方法的參數
        Parameter[] parameters = method.getParameters();
        //拼接字元串,格式為{參數1:值1,參數2::值2}
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            String name = parameter.getName();
            sb.append(name).append(":").append(args[i]).append(",");
        }
        if (sb.length() > 0) {
            sb.deleteCharAt(sb.lastIndexOf(","));
        }
        //執行結果
        Object res;
        try {
            //執行目标方法,擷取執行結果
            res = joinPoint.proceed();
            logger.info("調用{}.{}方法成功,參數為[{}],傳回結果[{}]", typeName, methodName, sb.toString(), JSONObject.toJSONString(res));
        } catch (Exception e) {
            logger.error("調用{}.{}方法發生異常", typeName, methodName);
            //如果發生異常,則抛出異常
            throw e;
        } finally {
            logger.info("調用{}.{}方法,耗時{}ms", typeName, methodName, (System.currentTimeMillis() - starTime));
        }
        //傳回執行結果
        return res;
    }
}           

定義完切面類後,需要在啟動類添加啟動AOP的注解。

@SpringBootApplication
//添加此注解,開啟AOP
@EnableAspectJAutoProxy
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }

}           

測試

我們再在Controller控制層增加一個有參數的接口。

@LogApi
@RequestMapping("/get/{id}")
public String get(@PathVariable(name = "id") String id) throws Exception {
    HashMap<String, Object> user = new HashMap<>();
    user.put("id", id);
    user.put("name", "關之琳");
    user.put("經典角色", "十三姨");
    return JSONObject.toJSONString(user);
}           

啟動項目,然後請求接口

list()

,我們可以看到控制台出現被調用方法的日志資訊。

如何使用Java自定義注解?什麼是注解Java内置的注解元注解自定義注解總結

請求有參數的接口

get()

,可以看到參數名稱和參數值都被列印在控制台。

如何使用Java自定義注解?什麼是注解Java内置的注解元注解自定義注解總結

這種記錄接口請求參數和傳回值的功能,在實際項目中基本上都會使用,因為這能利于系統的排錯和性能調優等等。

我們也可以在這個例子中,學會使用注解和切面程式設計,可謂是一舉兩得!

總結

注解的使用能大大地減少開發的代碼量,是以在實際項目的開發中會使用到非常多的注解。特别是做一些公共基礎的功能,比如日志記錄,事務管理,權限控制這些功能,使用注解就非常高效且優雅。

對于自定義注解,主要有三個步驟,定義注解,标記注解,解析注解,并不是很難。

這篇文章講到這裡了,感謝大家的閱讀,希望看完這篇文章能有所收獲!

覺得有用就點個贊吧,你的點贊是我創作的最大動力~

我是一個努力讓大家記住的程式員。我們下期再見!!!

如何使用Java自定義注解?什麼是注解Java内置的注解元注解自定義注解總結
能力有限,如果有什麼錯誤或者不當之處,請大家批評指正,一起學習交流!