怎么在Java中自定義注解?
什么是注解文章已收錄Github精選,歡迎Star:https://github.com/yehongzhi/learningSummary
注解是JDK1.5引入的新特性,主要用于簡化代碼,提高編程的效率。其實在日常開發(fā)中,注解并不少見,比如Java內(nèi)置的@Override、@SuppressWarnings,或者Spring提供的@Service、@Controller等等,隨著這些注解使用的頻率越來越高,作為開發(fā)人員當(dāng)真有必要深入學(xué)習(xí)一番。
先說說Java內(nèi)置的三個注解,分別是:
@Override:檢查當(dāng)前的方法定義是否覆蓋父類中的方法,如果沒有覆蓋,編譯器就會報錯。
@SuppressWarnings:忽略編譯器的警告信息。


@Deprecated:用于標(biāo)識該類或方法已過時,建議開發(fā)人員不要使用該類或方法。

元注解元注解其實就是描述注解的注解。主要有四個元注解,分別是:
@Target
用于描述注解的使用范圍,也就是注解可以用在什么地方,取值有:
CONSTRUCTOR:用于描述構(gòu)造器。
FIELD:用于描述字段。
LOCAL_VARIABLE:用于描述局部變量。
METHOD:用于描述方法。
PACKAGE:用于描述包。
PARAMETER:用于描述參數(shù)。
TYPE:用于描述類,包括class,interface,enum。
@Retention
表示需要在什么級別保存該注釋信息,用于描述注解的生命周期,取值由枚舉RetentionPoicy定義。

SOURCE:在源文件中有效(即源文件保留),僅出現(xiàn)在源代碼中,而被編譯器丟棄。
CLASS:在class文件中有效(即class保留),但會被JVM丟棄。
RUNTIME:JVM將在運行期也保留注釋,因此可以通過反射機制讀取注解的信息。
如果只是做一些檢查性操作,使用SOURCE,比如@Override,@SuppressWarnings。
如果要在編譯時進(jìn)行一些預(yù)處理操作,就用 CLASS。
如果需要獲取注解的屬性值,去做一些運行時的邏輯,可以使用RUNTIME。
@Documented
將此注解包含在 javadoc 中 ,它代表著此注解會被javadoc工具提取成文檔。它是一個標(biāo)記注解,沒有成員。

@Inherited
是一個標(biāo)記注解,用來指定該注解可以被繼承。使用 @Inherited 注解的 Class 類,表示這個注解可以被用于該 Class 類的子類。
自定義注解下面實戰(zhàn)一下,自定義一個注解@LogApi,用于方法上,當(dāng)被調(diào)用時即打印日志,在控制臺顯示調(diào)用方傳入的參數(shù)和調(diào)用返回的結(jié)果。
定義注解
首先定義注解@LogApi,在方法上使用,為了能在反射中讀取注解信息,當(dāng)然是設(shè)置為RUNTIME。
@Target(value?=?ElementType.METHOD)
@Documented
@Retention(value?=?RetentionPolicy.RUNTIME)
public?@interface?LogApi?{
}
這種沒有屬性的注解,屬于標(biāo)記注解。
多說幾句,如果需要傳遞屬性值,也可以設(shè)置屬性值value,比如@RequestMapping注解。
@Target({ElementType.METHOD,?ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public?@interface?RequestMapping?{
????@AliasFor("path")
?String[]?value()?default?{};
}
如果在使用時。只設(shè)置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;
}
標(biāo)記注解
剛剛定義完注解之后,就可以在需要的地方標(biāo)記注解,很簡單。
@LogApi
@RequestMapping("/list")
public?Map<String,?Object>?list()?throws?Exception?{
?//業(yè)務(wù)代碼...
}
解析注解
最關(guān)鍵的一步來了,解析注解,一般在項目中會使用Spring的AOP技術(shù)解析注解,當(dāng)然如果只需要解析一次的話,也可以使用Spring容器的生命周期函數(shù)。
這里的場景是打印每次方法被調(diào)用的日志,所以使用AOP比較合適。
創(chuàng)建一個切面類LogApiAspect進(jìn)行解析。
@Aspect
@Component
public?class?LogApiAspect?{
?//切面點為標(biāo)記了@LogApi注解的方法
????@Pointcut("@annotation(io.github.yehongzhi.user.annotation.LogApi)")
????public?void?logApi()?{
????}
????
?//環(huán)繞通知
????@Around("logApi()")
????@SuppressWarnings("unchecked")
????public?Object?around(ProceedingJoinPoint?joinPoint)?throws?Throwable?{
????????long?starTime?=?System.currentTimeMillis();
????????//通過反射獲取被調(diào)用方法的Class
????????Class?type?=?joinPoint.getSignature().getDeclaringType();
????????//獲取類名
????????String?typeName?=?type.getSimpleName();
????????//獲取日志記錄對象Logger
????????Logger?logger?=?LoggerFactory.getLogger(type);
????????//方法名
????????String?methodName?=?joinPoint.getSignature().getName();
????????//獲取參數(shù)列表
????????Object[]?args?=?joinPoint.getArgs();
????????//參數(shù)Class的數(shù)組
????????Class[]?clazz?=?new?Class[args.length];
????????for?(int?i?=?0;?i?<?args.length;?i++)?{
????????????clazz[i]?=?args[i].getClass();
????????}
????????//通過反射獲取調(diào)用的方法method
????????Method?method?=?type.getMethod(methodName,?clazz);
????????//獲取方法的參數(shù)
????????Parameter[]?parameters?=?method.getParameters();
????????//拼接字符串,格式為{參數(shù)1:值1,參數(shù)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(","));
????????}
????????//執(zhí)行結(jié)果
????????Object?res;
????????try?{
????????????//執(zhí)行目標(biāo)方法,獲取執(zhí)行結(jié)果
????????????res?=?joinPoint.proceed();
????????????logger.info("調(diào)用{}.{}方法成功,參數(shù)為[{}],返回結(jié)果[{}]",?typeName,?methodName,?sb.toString(),?JSONObject.toJSONString(res));
????????}?catch?(Exception?e)?{
????????????logger.error("調(diào)用{}.{}方法發(fā)生異常",?typeName,?methodName);
????????????//如果發(fā)生異常,則拋出異常
????????????throw?e;
????????}?finally?{
????????????logger.info("調(diào)用{}.{}方法,耗時{}ms",?typeName,?methodName,?(System.currentTimeMillis()?-?starTime));
????????}
????????//返回執(zhí)行結(jié)果
????????return?res;
????}
}
定義完切面類后,需要在啟動類添加啟動AOP的注解。
@SpringBootApplication
//添加此注解,開啟AOP
@EnableAspectJAutoProxy
public?class?UserApplication?{
????public?static?void?main(String[]?args)?{
????????SpringApplication.run(UserApplication.class,?args);
????}
}
測試
我們再在Controller控制層增加一個有參數(shù)的接口。
@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",?"關(guān)之琳");
????user.put("經(jīng)典角色",?"十三姨");
????return?JSONObject.toJSONString(user);
}
啟動項目,然后請求接口list(),我們可以看到控制臺出現(xiàn)被調(diào)用方法的日志信息。

請求有參數(shù)的接口get(),可以看到參數(shù)名稱和參數(shù)值都被打印在控制臺。

這種記錄接口請求參數(shù)和返回值的功能,在實際項目中基本上都會使用,因為這能利于系統(tǒng)的排錯和性能調(diào)優(yōu)等等。
我們也可以在這個例子中,學(xué)會使用注解和切面編程,可謂是一舉兩得!
總結(jié)注解的使用能大大地減少開發(fā)的代碼量,所以在實際項目的開發(fā)中會使用到非常多的注解。特別是做一些公共基礎(chǔ)的功能,比如日志記錄,事務(wù)管理,權(quán)限控制這些功能,使用注解就非常高效且優(yōu)雅。
對于自定義注解,主要有三個步驟,定義注解,標(biāo)記注解,解析注解,并不是很難。
這篇文章講到這里了,感謝大家的閱讀,希望看完這篇文章能有所收獲!
覺得有用就點個贊吧,你的點贊是我創(chuàng)作的最大動力~
我是一個努力讓大家記住的程序員。我們下期再見?。?!
能力有限,如果有什么錯誤或者不當(dāng)之處,請大家批評指正,一起學(xué)習(xí)交流!
