淺析Java自定義注解的用法
注解
注解為我們?cè)诖a中添加信息提供一種形式化的方法,使我們可以在源碼、編譯時(shí)、運(yùn)行時(shí)非常方便的使用這些數(shù)據(jù)。
注解是在JAVA SE5中引入的,注解讓代碼更干凈易讀并且可以實(shí)現(xiàn)編譯期類(lèi)型檢查等。當(dāng)創(chuàng)建描述性質(zhì)的類(lèi)或接口時(shí),如果有重復(fù)性的工作,就可以考慮使用注解來(lái)簡(jiǎn)化或自動(dòng)化該過(guò)程。我們可以讓注解保存在源代碼中,并且利用Annotation API處理注解,得到我們想要的數(shù)據(jù)并加以處理,注解的使用比較簡(jiǎn)單,JAVA SE5內(nèi)置了3種:
- @Override 表示當(dāng)前類(lèi)中的方法將覆蓋父類(lèi)中的方法,如果不寫(xiě)也不會(huì)有錯(cuò),但是@Override可以起到檢查作用,如方法名拼寫(xiě)錯(cuò)誤,編譯器就會(huì)報(bào)警告信息。
- @Deprecated 表示被標(biāo)注的方法已經(jīng)被廢棄了,如果使用編譯器會(huì)發(fā)出警告信息。
- @SuppressWarnings 關(guān)閉不當(dāng)?shù)木幾g器警告信息。除非你確定編譯器的警告信息是錯(cuò)誤的,否則最好不要使用這個(gè)注解。
定義注解
先來(lái)看內(nèi)置注解@Override是怎么被定義的,它位于package java.lang之下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}@Target、@Retention稱為元注解,元注解負(fù)責(zé)注解其他的注釋,如:@Target定義聲明的注解的作用域(作用在類(lèi)上還是方法上),@Retention定義注解在哪個(gè)級(jí)別可用,在源代碼中(SOURCE)、類(lèi)文件中(CLASS)、還是運(yùn)行時(shí)(RUNTIME)。除了@Target、@Retention還有@Documented及@Inherited,下面用一個(gè)表格來(lái)分別列出他們各自的作用:
| 元注解 | 作用 |
|---|---|
| @Target | 表示注解作用在什么地方,CONSTRUCTOR 聲明在構(gòu)造器、FIELD 域聲明、METHOD 方法聲明、PACKAGE 包聲明、TYPE 類(lèi)、接口或者enum聲明、PARAMETER參數(shù)聲明、LOCAL_VALABLE局部變量聲明 |
| @Retention | 表示在什么級(jí)別保存注解信息,SOURCE注解在編譯器編譯時(shí)丟棄、CLASS注解在編譯之后的class文件中存在,但會(huì)被VM丟棄、RUNTIME VM將在運(yùn)行期也保留注解,因此可以用反射讀取注解的信息 |
| @Documented | 將此注解包含在JavaDoc中 |
| @Inherited | 允許子類(lèi)繼承父類(lèi)中的注解 |
@Retention作用范圍如下圖所示:

注解處理器
首先來(lái)自定義一個(gè)注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationInfo {
String[] value();
int requestCode() default 0;
}
- 注解中定義的方法沒(méi)有參數(shù),且返回類(lèi)型僅限于原始類(lèi)型,字符串,枚舉,注解或以上類(lèi)型的集合
- 注解中定義的方法可以有默認(rèn)值
運(yùn)行時(shí)解析注解
@Target(ElementType.METHOD)指明了我們的注解是作用在方法上的
@Retention(RetentionPolicy.RUNTIME)表示注解在程序運(yùn)行時(shí)期也會(huì)存在,即注解信息也會(huì)加載到虛擬機(jī)VM中,所以可以通過(guò)反射來(lái)獲取注解的相關(guān)信息:
編寫(xiě)一個(gè)類(lèi),聲明方法,并在方法上聲明我們的自定義注解,如下:
public class AnnotationExample {
/**
* 注解模擬請(qǐng)求權(quán)限
*/
@AnnotationInfo(value = {"android.permission.CALL_PHONE", "android.permission.CAMERA"}, requestCode = 10)
public void requestPermission() {
//其他邏輯
}
}
接著來(lái)編寫(xiě)一個(gè)運(yùn)行時(shí)解析注解的Java類(lèi):AnnotationRuntimeProcessor.java
public class AnnotationRuntimeProcessor {
public static void main(String[] args) {
try {
//獲取AnnotationExample的Class對(duì)象
Class<?> cls = Class.forName("com.javastudy.Annotation.AnnotationExample");
//獲取AnnotationExample類(lèi)中的方法
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
//過(guò)濾不含自定義注解AnnotationInfo的方法
boolean isHasAnnotation = method.isAnnotationPresent(AnnotationInfo.class);
if (isHasAnnotation) {
method.setAccessible(true);
//獲取方法上的注解
AnnotationInfo aInfo = method.getAnnotation(AnnotationInfo.class);
if (aInfo == null) return;
//解析注解上對(duì)應(yīng)的信息
String[] permissions = aInfo.value();
System.out.println("value: " + Arrays.toString(permissions));
int requestCode = aInfo.requestCode();
System.out.println("requestCode: " + requestCode);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}上面的邏輯很簡(jiǎn)單,反射拿到有注解對(duì)應(yīng)類(lèi)的Class對(duì)象,篩選含有注解的方法,最后獲取方法上的注解并解析,運(yùn)行結(jié)果如下:
value: [android.permission.CALL_PHONE, android.permission.CAMERA]
requestCode: 10
編譯時(shí)解析注解
AbstractProcessor是javax下的API,java和javax都是Java的API(Application Programming Interface)包,java是核心包,javax的x是extension的意思,也就是擴(kuò)展包。一般繼承AbstractProcessor需要實(shí)現(xiàn)下面的幾個(gè)方法:
public class ProcessorExample extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
//processingEnvironment提供各種工具類(lèi) 如Elements Filer Types SourceVersion等
super.init(processingEnvironment);
}
/**
* 掃描 評(píng)估和處理注解代碼 生成Java代碼
*
* @param set 注解類(lèi)型
* @param roundEnvironment 有關(guān)當(dāng)前和以前的信息環(huán)境 查詢出包含特定注解的被注解元素
* @return 返回true 表示注解已聲明 后續(xù)Processor不會(huì)再處理 false表示后續(xù)Processor會(huì)處理他們
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}
}
- init(ProcessingEnvironment env): 每一個(gè)注解處理器類(lèi)都必須有一個(gè)空的構(gòu)造函數(shù)。然而,這里有一個(gè)特殊的init()方法,它會(huì)被注解處理工具調(diào)用,并輸入ProcessingEnviroment參數(shù)。ProcessingEnviroment提供很多有用的工具類(lèi)Elements, Types和Filer。后面我們將看到詳細(xì)的內(nèi)容。
- process(Set (? extends TypeElement) annotations, RoundEnvironment env): 這相當(dāng)于每個(gè)處理器的主函數(shù)main()。你在這里寫(xiě)你的掃描、評(píng)估和處理注解的代碼,以及生成Java文件。輸入?yún)?shù)RoundEnviroment,可以讓你查詢出包含特定注解的被注解元素。后面我們將看到詳細(xì)的內(nèi)容。
- getSupportedAnnotationTypes(): 這里你必須指定,這個(gè)注解處理器是注冊(cè)給哪個(gè)注解的。注意,它的返回值是一個(gè)字符串的集合,包含本處理器想要處理的注解類(lèi)型的合法全稱。換句話說(shuō),你在這里定義你的注解處理器注冊(cè)到哪些注解上。
- getSupportedSourceVersion(): 用來(lái)指定你使用的Java版本。通常這里返回SourceVersion.latestSupported()。然而,如果你有足夠的理由只支持Java 6的話,你也可以返回SourceVersion.RELEASE_6。推薦使用前者。
下面來(lái)看一個(gè)具體的例子,我們?cè)谛陆╝ndroid的普通model和library工程是沒(méi)有javax的,所以我們需要新建一個(gè)java工程,先來(lái)看下整個(gè)包結(jié)構(gòu):
首先先定義了注解:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface CompileAnnotation {
int value() default 0;
}可見(jiàn)我們的注解是定義在變量FIELD上的,接著來(lái)編寫(xiě)我們的解析器:
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("com.suyun.aopermission.annotation.CompileAnnotation")
public class AnnotationCompileProcessor extends AbstractProcessor {
private Messager messager;
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
//processingEnvironment提供各種工具類(lèi) 如Elements Filer Types SourceVersion等
super.init(processingEnvironment);
messager = processingEnv.getMessager();
filer = processingEnv.getFiler();
}
/**
* 掃描 評(píng)估和處理注解代碼 生成Java代碼
*
* @param annotations 注解類(lèi)型
* @param roundEnvironment 有關(guān)當(dāng)前和以前的信息環(huán)境 查詢出包含特定注解的被注解元素
* @return 返回true 表示注解已聲明 后續(xù)Processor不會(huì)再處理 false表示后續(xù)Processor會(huì)處理他們
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
messager.printMessage(Diagnostic.Kind.NOTE, "----------start----------");
for (TypeElement annotation : annotations) {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(annotation);
for (Element element : elements) {
if (element.getKind() != ElementKind.FIELD) {
messager.printMessage(Diagnostic.Kind.ERROR, "Only FIELD can be annotated with AnnotationInfo");
return true;
}
//獲取注解
CompileAnnotation annotationInfo = element.getAnnotation(CompileAnnotation.class);
//獲取注解中的值
int value = annotationInfo.value();
messager.printMessage(Diagnostic.Kind.NOTE, "value: " + value);
}
}
return true;
}
}
一個(gè)簡(jiǎn)單的注解解析器就寫(xiě)完了,看代碼知道我們只是簡(jiǎn)單的把注解里的值打印出來(lái),接下來(lái)要做的就是把我們的@CompileAnnotation注解定義到我們類(lèi)中的變量FILED上了,直接上代碼:
public class MainActivity extends AppCompatActivity {
@CompileAnnotation(value = 200)
private TextView tv_text;
//....省略其他代碼....
}OK,接下來(lái)就是見(jiàn)證奇跡的時(shí)刻了,當(dāng)我們迫不及待的按下編譯按鈕后,發(fā)現(xiàn)什么都沒(méi)有發(fā)生,注解里的信息并沒(méi)有打印出來(lái),what fu**!!!不要方,其實(shí)還少一步操作,我們只是定義了注解解析器,但是并沒(méi)有把解析器注冊(cè)到j(luò)avac中,怎么注冊(cè)呢,在main目錄下新建resources->META-INF->services->javax.annotation.processing.Processor文件,文件里指定解析器的全路徑,我的全路徑是:
com.suyun.aopermission.processor.AnnotationCompileProcessor
寫(xiě)好之后的目錄如下:

接著再來(lái)編譯一下,這次有了結(jié)果:
注: ----------start----------
注: value: 200
注: ----------start----------
成功了,如果覺(jué)得上述的配置比較繁瑣的話,可以選擇使用Google開(kāi)發(fā)的service庫(kù)來(lái)代替上述配置,在build.gradle中配置:
compile 'com.google.auto.service:auto-service:1.0-rc2'
然后我們的解析器中這樣寫(xiě):
@AutoService(Processor.class)
public class AnnotationCompileProcessor extends AbstractProcessor {
//....其他邏輯....
}沒(méi)錯(cuò),我們?cè)谧⒔饨馕銎骼镉侄x了@AutoService(Processor.class)注解,這樣和上述配置是一樣的效果
自動(dòng)生成.class代碼
編譯時(shí)期我們可以根據(jù)需要自動(dòng)生成.class代碼,跟我們手動(dòng)寫(xiě).java代碼編譯生成的.class代碼是一樣的,自動(dòng)生成有一樣好處就是一些公共的或者重復(fù)的邏輯我們可以通過(guò)自動(dòng)生成來(lái)減輕我們的工作了,通常自動(dòng)生成.class代碼需要用到JavaFileObject類(lèi),如下:
try {
//packageName是包名
JavaFileObject source = mFiler.createSourceFile(packageName);
Writer writer = source.openWriter();
//classStr代表的類(lèi)里所有的字符
writer.write(classStr);
writer.flush();
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
具體JavaFileObject的用法大家可以去搜下,因?yàn)橐膊粡?fù)雜,這里就不多說(shuō)了,因?yàn)檎麄€(gè)類(lèi)都需要我們手動(dòng)寫(xiě),一是比較麻煩,二是容易出錯(cuò),square做了一個(gè)開(kāi)源的javapoet庫(kù)來(lái)幫我們減少工作量,javapoet地址:https://github.com/square/javapoet
來(lái)看簡(jiǎn)單的一個(gè)栗子:
//創(chuàng)建method方法類(lèi)
MethodSpec methodSpec = MethodSpec.methodBuilder("getValue")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(int.class)
.addStatement("return " + proAnnotation.value())
.build();
//創(chuàng)建.class類(lèi)
TypeSpec typeSpec = TypeSpec.classBuilder("autoGenerate")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(methodSpec)
.build();
String packageName = processingEnv.getElementUtils().
getPackageOf(element).getQualifiedName().toString();
try {
JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
.addFileComment("this is auto generated")
.build();
javaFile.writeTo(filer);
} catch (Exception e) {
e.printStackTrace();
}編譯我們的代碼,然后再build->generated->source->apt->debug目錄下就可以看到自動(dòng)生成的.class類(lèi)了:
// this is auto generated
package org.ninetripods.mq.javastudy;
public final class autoGenerate {
public static int getValue() {
return 100;
}
}更多用法請(qǐng)去javapoet的github上查看。
總結(jié)
自定義注解在一些優(yōu)秀的三方庫(kù)(如:EventBus ButterKnife等)中很常見(jiàn),用于簡(jiǎn)化我們的代碼,可以通過(guò)編譯時(shí)解析注解生成.class類(lèi),統(tǒng)一去處理,所以學(xué)習(xí)自定義注解還是很有必要的。
到此這篇關(guān)于淺析Java自定義注解的用法的文章就介紹到這了,更多相關(guān)Java自定義注解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決idea無(wú)法導(dǎo)入識(shí)別本地類(lèi)的問(wèn)題
今天做實(shí)驗(yàn)不知道按了哪里不能導(dǎo)入識(shí)別本地的類(lèi),只有jar包的類(lèi),百度搜索也沒(méi)有找到合理的解決方案,經(jīng)過(guò)朋友援助問(wèn)題根源找到,下面小編把解決方法分享給大家,需要的朋友參考下吧2021-08-08
springboot處理url中帶斜杠/\字符的參數(shù)報(bào)400問(wèn)題
這篇文章主要介紹了springboot處理url中帶斜杠/\字符的參數(shù)報(bào)400問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
SpringMVC?RESTFul實(shí)戰(zhàn)案例訪問(wèn)首頁(yè)
這篇文章主要為大家介紹了SpringMVC?RESTFul實(shí)戰(zhàn)案例訪問(wèn)首頁(yè),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
springboot單文件下載和多文件壓縮zip下載的實(shí)現(xiàn)
這篇文章主要介紹了springboot單文件下載和多文件壓縮zip下載的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
JAVA代碼調(diào)用ffmpeg程序進(jìn)行視頻轉(zhuǎn)碼和推流方式
本文介紹了如何通過(guò)Java代碼調(diào)用FFmpeg進(jìn)行多媒體視頻處理,包括兩種方式:使用第三方封裝的jar包和使用ProcessBuilder類(lèi)創(chuàng)建進(jìn)程,兩種方式各有優(yōu)缺點(diǎn),選擇時(shí)需根據(jù)具體需求和場(chǎng)景2025-02-02
SpringBoot中定時(shí)任務(wù)的使用方法解析
這篇文章主要介紹了SpringBoot中定時(shí)任務(wù)的使用方法解析,@EnableScheduling?注解,它的作用是發(fā)現(xiàn)注解?@Scheduled的任務(wù)并由后臺(tái)執(zhí)行,沒(méi)有它的話將無(wú)法執(zhí)行定時(shí)任務(wù),需要的朋友可以參考下2024-01-01
Java BeanUtils.copyProperties的詳解
這篇文章主要介紹了Java BeanUtils.copyProperties的詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
ExpressionUtil工具類(lèi)的應(yīng)用實(shí)例
這篇文章主要給大家介紹了關(guān)于ExpressionUtil工具類(lèi)的應(yīng)用實(shí)例,常用的工具類(lèi)有很多,這是其中一個(gè),了解基本的API可以幫助我們更好的開(kāi)發(fā),文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-04-04
java發(fā)送http請(qǐng)求并獲取狀態(tài)碼的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇java發(fā)送http請(qǐng)求并獲取狀態(tài)碼的簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-05-05

