1.什麼是註解
1.1概念
說明程序的,給計算機看的。
什麼是註釋:用文字描述程序的,給程序員看的。
百度上的解釋:
Java 註解(Annotation)又稱 Java 標註,是 JDK5.0 引入的一種註釋機制,是一種代碼級別的說明。Java 語言中的類、方法、變量、參數和包等都可以被標註。和 Javadoc 不同,Java 標註可以通過反射獲取標註內容。在編譯器生成類文件時,標註可以被嵌入到字節碼中。Java 虛擬機可以保留標註內容,在運行時可以獲取到標註內容 。 當然它也支持自定義 Java 標註。
概念描述:
- JDK1.5 之後的新特性
- 用來說明程序的
- 使用註解:@註解名稱
1.2 作用分類
- 編譯檢查:通過代碼裏標識的註解讓編譯器能夠實現基本的編譯檢查【Override】
- 編寫文檔:通過代碼裏標識的註解生成文檔【生成文檔doc文檔】,API文檔是通過抽取代碼中的文檔註釋生成的。
例如新建一個類:
打開命令行窗口,用 javadoc AnnoDemo1.java 這個命令進行抽取,點開生成文件中的index.html:
3. 代碼分析:通過代碼裏標識的註解對代碼進行分析【使用反射】
2. JDK中預定義的一些註解
@Override : 檢測被該註解標註的方法是否蒐集繼承自父類(接口)的
@Deprecated :將該註解標註的內容,表示已過時
@SuppressWarnings :壓制警告,一般傳遞參數all @SuppressWarnings("all")
3. 自定義註解
3.1 格式
元註解: public @interface 註解名稱{}
3.2 註解的本質
註解本質上就是一個接口,該接口默認繼承Annotation接口
將以下註解編譯過後進行反編譯,得到結果:
編譯前:
public interface MyAnno{
}
反編譯後:
public interface MyAnno extends java.lang.annotation.Annotation{
}
3.3 屬性
可以理解爲接口中可以定義的抽象方法。
要求:
1.屬性的返回值類型只能爲以下幾種:
- 基本數據類型
- String
- 枚舉
- 註解
- 以上類型的數組
2. 定義了的屬性(本質上是抽象方法),在使用時需要進行賦值
- 如果定義屬性時,使用default關鍵字給屬性默認初始化值,則使用註解時,可以不進行屬性的賦值。
- 如果只有一個屬性需要賦值,並且這個屬性的名稱是value,那麼value可以省略,直接賦值即可。
- 數組賦值時,值使用大括號包裹。如果數組中只有一個值,那麼{}可以省略
3.4 元註解
概念:用於描述註解的註解。
@Target:描述能夠作用的位置
@Target(value = {ElementType.TYPE}) //表示該MyAnno註解只能作用於類上
public @interface MyAnno {
}
其中value中ElementType取值可以有以下幾種情況:
- TYPE:可以作用在類上
- METHOD:可以作用在方法上
- FIELD:可以作用於成員變量上
@Target(value = {ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
//表示該MyAnno註解可以同時作用於類上,方法上和成員變量上
public @interface MyAnno {
}
@Retention:描述註解被保留的階段
@Retention(RetentionPolicy.RUNTIME):當前被描述的註解,會保留到字節碼文件中,並被JVM讀取到,一般自己定義的註解都加RUNTIME
@Documented:描述該註解是否會被抽取到api文檔中
@Inherited:描述註解是否被子類繼承
4. 在程序中使用註解
註解在程序中經常和反射一起使用,註解大多數來說都是用來替換配置文件的,拿之前反射的程序來舉例:
被測試的類AnnoTest1:
public class AnnoTest1 {
public void play(){
System.out.println("AnnoTest1 method play()");
}
}
原始的反射代碼:
package cn.other.reflect;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* @author liwenlong
* @data 2020/3/6
*/
public class ReflectTest {
public static void main(String[] args) throws Exception {
/**
* 前提:不能改變該類的任何代碼。可以創建任意類的對象,可以執行任意方法
* 即:拒絕硬編碼
*/
//1.加載配置文件
//1.1創建Properties對象
Properties pro = new Properties();
//1.2加載配置文件,轉換爲一個集合
//1.2.1獲取class目錄下的配置文件 使用類加載器
ClassLoader classLoader = ReflectTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("pro.properties");
pro.load(is);
//2.獲取配置文件中定義的數據
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
//3.加載該類進內存
Class cls = Class.forName(className);
//4.創建對象
Object obj = cls.newInstance();
//5.獲取方法對象
Method method = cls.getMethod(methodName);
//6.執行方法
method.invoke(obj);
}
}
對應的配置文件pro:
className=cn.other.annotation.AnnoTest1
methodName=play
新建註解AnnoReflect :
package cn.other.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** 描述需要執行的類名和方法名
* @author liwenlong
* @data 2020/3/6
*/
@Target(ElementType.TYPE) //可以被作用在類上
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnoReflect {
String className();
String methodName();
}
使用註解的方式來淘汰配置文件(註釋很重要):
package cn.other.annotation;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* @author liwenlong
* @data 2020/3/6
*/
@AnnoReflect(className = "cn.other.annotation.AnnoTest1",methodName = "play")
public class ReflectAnnotationTest {
public static void main(String[] args) throws Exception {
/**
* 前提:不能改變該類的任何代碼。可以創建任意類的對象,可以執行任意方法
* 即:拒絕硬編碼
*/
//1. 解析註解
//1.1 獲取該類的字節碼文件對象
Class<ReflectAnnotationTest> rac = ReflectAnnotationTest.class;
//1.2 獲取上面的註解對象,其實就是在內存中生成了一個該註解接口的子類實現對象
AnnoReflect an = rac.getAnnotation(AnnoReflect.class);
/*
相當於
public class AnnotationReflect implements AnnoReflect{
public String className(){
return "cn.other.annotation.AnnoTest1";
}
public String methodName(){
return "play";
}
}
*/
//2. 調用註解對象中定義的抽象方法,獲取返回值
String className = an.className();
String methodName = an.methodName();
//3.加載該類進內存
Class cls = Class.forName(className);
//4.創建對象
Object obj = cls.newInstance();
//5.獲取方法對象
Method method = cls.getMethod(methodName);
//6.執行方法
method.invoke(obj);
}
}
運行結果:
使用總結:
在程序中使用註解:獲取註解中定義的屬性值
- 獲取註解定義的位置的對象 (Class, Method, Field)
- 獲取指定的註解:getAnnotation(Class)
- 調用註解中的抽象方法獲取配置的屬性值
案例:簡單的測試框架
需求:設計一個框架,檢測一個類中的方法使用有異常,並進行統計。
待測試的類
package cn.other.annotation.demo;
/** 計算器類
* @author liwenlong
* @data 2020/3/6
*/
public class calculator {
public void add(){
System.out.println("1+0="+(1+0));
}
public void sub(){
System.out.println("1-0="+(1-0));
}
public void mul(){
System.out.println("1*0="+(1*0));
}
public void div(){
System.out.println("1/0="+(1/0));
}
public void show(){
System.out.println("今天天氣真不錯!");
}
}
如何實現
首先自定義一個註解:
@Retention(RetentionPolicy.RUNTIME) //運行時
@Target(ElementType.METHOD) //加在方法前面
public @interface Check {
}
然後編寫一個類專門用於檢查(注意註釋):
package cn.other.annotation.demo;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/** 簡單的測試框架
* 當主方法執行後,會自動自行檢測所有方法(加了check註解的方法),判斷方法是否有異常並記錄
* @author liwenlong
* @data 2020/3/6
*/
public class TestCheck {
public static void main(String[] args) throws IOException {
//1. 創建計算機對象
calculator c = new calculator();
//2. 獲取字節碼文件對象
Class cls = c.getClass();
//3. 獲取所有方法
Method[] methods = cls.getMethods();
int num = 0; //記錄出現異常的次數
BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
for(Method method:methods){
//4. 判斷方法上是否有Check註解
if(method.isAnnotationPresent(Check.class)){
//5. 有註解就執行,捕獲異常
try {
method.invoke(c);
} catch (Exception e) {
e.printStackTrace();
//6.將異常記錄在文件中
num++;
bw.write(method.getName()+"方法出異常了");
bw.newLine();
bw.write("異常的名稱是:"+e.getCause().getClass().getSimpleName());
bw.newLine();
bw.write("異常原因:"+e.getCause().getMessage());
bw.newLine();
bw.write("=====================");
bw.newLine();
}
}
}
bw.write("本次測試一共出現"+num+"次異常");
bw.flush();
bw.close();
}
}
在待測試的類中每個需要測試的方法前面都加上@Check
package cn.other.annotation.demo;
/** 計算器類
* @author liwenlong
* @data 2020/3/6
*/
public class calculator {
@Check
public void add(){
System.out.println("1+0="+(1+0));
}
@Check
public void sub(){
System.out.println("1-0="+(1-0));
}
@Check
public void mul(){
System.out.println("1*0="+(1*0));
}
@Check
public void div(){
System.out.println("1/0="+(1/0));
}
public void show(){
System.out.println("今天天氣真不錯!");
}
}
運行TestCheck類中的主方法,就會自動檢查所有註解@Check的方法是否異常:
小結 :
- 大多數時候,我們會使用註解而不是自定義註解
- 註解給編譯器和解析程序用
- 註解不是程序的一部分,可以理解爲表情