您好,登錄后才能下訂單哦!
這篇文章主要介紹“Java怎么自定義注解”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Java怎么自定義注解”文章能幫助大家解決問題。
注解為我們在代碼中添加信息提供一種形式化的方法,使我們可以在源碼、編譯時、運行時非常方便的使用這些數據。
注解是在JAVA SE5中引入的,注解讓代碼更干凈易讀并且可以實現編譯期類型檢查等。當創建描述性質的類或接口時,如果有重復性的工作,就可以考慮使用注解來簡化或自動化該過程。我們可以讓注解保存在源代碼中,并且利用Annotation API處理注解,得到我們想要的數據并加以處理,注解的使用比較簡單,JAVA SE5內置了3種:
@Override 表示當前類中的方法將覆蓋父類中的方法,如果不寫也不會有錯,但是@Override可以起到檢查作用,如方法名拼寫錯誤,編譯器就會報警告信息。
@Deprecated 表示被標注的方法已經被廢棄了,如果使用編譯器會發出警告信息。
@SuppressWarnings 關閉不當的編譯器警告信息。除非你確定編譯器的警告信息是錯誤的,否則最好不要使用這個注解。
先來看內置注解@Override是怎么被定義的,它位于package java.lang之下:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
@Target、@Retention稱為元注解,元注解負責注解其他的注釋,如:@Target定義聲明的注解的作用域(作用在類上還是方法上),@Retention定義注解在哪個級別可用,在源代碼中(SOURCE)、類文件中(CLASS)、還是運行時(RUNTIME)。除了@Target、@Retention還有@Documented及@Inherited,下面用一個表格來分別列出他們各自的作用:
元注解 | 作用 |
---|---|
@Target | 表示注解作用在什么地方,CONSTRUCTOR 聲明在構造器、FIELD 域聲明、METHOD 方法聲明、PACKAGE 包聲明、TYPE 類、接口或者enum聲明、PARAMETER參數聲明、LOCAL_VALABLE局部變量聲明 |
@Retention | 表示在什么級別保存注解信息,SOURCE注解在編譯器編譯時丟棄、CLASS注解在編譯之后的class文件中存在,但會被VM丟棄、RUNTIME VM將在運行期也保留注解,因此可以用反射讀取注解的信息 |
@Documented | 將此注解包含在JavaDoc中 |
@Inherited | 允許子類繼承父類中的注解 |
@Retention作用范圍如下圖所示:
首先來自定義一個注解:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AnnotationInfo { String[] value(); int requestCode() default 0; }
注解中定義的方法沒有參數,且返回類型僅限于原始類型,字符串,枚舉,注解或以上類型的集合
注解中定義的方法可以有默認值
@Target(ElementType.METHOD)指明了我們的注解是作用在方法上的
@Retention(RetentionPolicy.RUNTIME)表示注解在程序運行時期也會存在,即注解信息也會加載到虛擬機VM中,所以可以通過反射來獲取注解的相關信息:
編寫一個類,聲明方法,并在方法上聲明我們的自定義注解,如下:
public class AnnotationExample { /** * 注解模擬請求權限 */ @AnnotationInfo(value = {"android.permission.CALL_PHONE", "android.permission.CAMERA"}, requestCode = 10) public void requestPermission() { //其他邏輯 } }
接著來編寫一個運行時解析注解的Java類:AnnotationRuntimeProcessor.java
public class AnnotationRuntimeProcessor { public static void main(String[] args) { try { //獲取AnnotationExample的Class對象 Class<?> cls = Class.forName("com.javastudy.Annotation.AnnotationExample"); //獲取AnnotationExample類中的方法 Method[] methods = cls.getDeclaredMethods(); for (Method method : methods) { //過濾不含自定義注解AnnotationInfo的方法 boolean isHasAnnotation = method.isAnnotationPresent(AnnotationInfo.class); if (isHasAnnotation) { method.setAccessible(true); //獲取方法上的注解 AnnotationInfo aInfo = method.getAnnotation(AnnotationInfo.class); if (aInfo == null) return; //解析注解上對應的信息 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(); } } }
上面的邏輯很簡單,反射拿到有注解對應類的Class對象,篩選含有注解的方法,最后獲取方法上的注解并解析,運行結果如下:
value: [android.permission.CALL_PHONE, android.permission.CAMERA]
requestCode: 10
AbstractProcessor是javax下的API,java和javax都是Java的API(Application Programming Interface)包,java是核心包,javax的x是extension的意思,也就是擴展包。一般繼承AbstractProcessor需要實現下面的幾個方法:
public class ProcessorExample extends AbstractProcessor { @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { //processingEnvironment提供各種工具類 如Elements Filer Types SourceVersion等 super.init(processingEnvironment); } /** * 掃描 評估和處理注解代碼 生成Java代碼 * * @param set 注解類型 * @param roundEnvironment 有關當前和以前的信息環境 查詢出包含特定注解的被注解元素 * @return 返回true 表示注解已聲明 后續Processor不會再處理 false表示后續Processor會處理他們 */ @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): 每一個注解處理器類都必須有一個空的構造函數。然而,這里有一個特殊的init()方法,它會被注解處理工具調用,并輸入ProcessingEnviroment參數。ProcessingEnviroment提供很多有用的工具類Elements, Types和Filer。后面我們將看到詳細的內容。
process(Set (? extends TypeElement) annotations, RoundEnvironment env): 這相當于每個處理器的主函數main()。你在這里寫你的掃描、評估和處理注解的代碼,以及生成Java文件。輸入參數RoundEnviroment,可以讓你查詢出包含特定注解的被注解元素。后面我們將看到詳細的內容。
getSupportedAnnotationTypes(): 這里你必須指定,這個注解處理器是注冊給哪個注解的。注意,它的返回值是一個字符串的集合,包含本處理器想要處理的注解類型的合法全稱。換句話說,你在這里定義你的注解處理器注冊到哪些注解上。
getSupportedSourceVersion(): 用來指定你使用的Java版本。通常這里返回SourceVersion.latestSupported()。然而,如果你有足夠的理由只支持Java 6的話,你也可以返回SourceVersion.RELEASE_6。推薦使用前者。
下面來看一個具體的例子,我們在新建android的普通model和library工程是沒有javax的,所以我們需要新建一個java工程,先來看下整個包結構:
首先先定義了注解:
@Retention(RetentionPolicy.CLASS) @Target(ElementType.FIELD) public @interface CompileAnnotation { int value() default 0; }
可見我們的注解是定義在變量FIELD上的,接著來編寫我們的解析器:
@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提供各種工具類 如Elements Filer Types SourceVersion等 super.init(processingEnvironment); messager = processingEnv.getMessager(); filer = processingEnv.getFiler(); } /** * 掃描 評估和處理注解代碼 生成Java代碼 * * @param annotations 注解類型 * @param roundEnvironment 有關當前和以前的信息環境 查詢出包含特定注解的被注解元素 * @return 返回true 表示注解已聲明 后續Processor不會再處理 false表示后續Processor會處理他們 */ @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; } }
一個簡單的注解解析器就寫完了,看代碼知道我們只是簡單的把注解里的值打印出來,接下來要做的就是把我們的@CompileAnnotation注解定義到我們類中的變量FILED上了,直接上代碼:
public class MainActivity extends AppCompatActivity { @CompileAnnotation(value = 200) private TextView tv_text; //....省略其他代碼.... }
OK,接下來就是見證奇跡的時刻了,當我們迫不及待的按下編譯按鈕后,發現什么都沒有發生,注解里的信息并沒有打印出來,what fu**!!!不要方,其實還少一步操作,我們只是定義了注解解析器,但是并沒有把解析器注冊到javac中,怎么注冊呢,在main目錄下新建resources->META-INF->services->javax.annotation.processing.Processor文件,文件里指定解析器的全路徑,我的全路徑是:
com.suyun.aopermission.processor.AnnotationCompileProcessor
寫好之后的目錄如下:
接著再來編譯一下,這次有了結果:
注: ----------start----------
注: value: 200
注: ----------start----------
成功了,如果覺得上述的配置比較繁瑣的話,可以選擇使用Google開發的service庫來代替上述配置,在build.gradle中配置:
compile 'com.google.auto.service:auto-service:1.0-rc2'
然后我們的解析器中這樣寫:
@AutoService(Processor.class) public class AnnotationCompileProcessor extends AbstractProcessor { //....其他邏輯.... }
沒錯,我們在注解解析器里又定義了@AutoService(Processor.class)注解,這樣和上述配置是一樣的效果
自動生成.class代碼
編譯時期我們可以根據需要自動生成.class代碼,跟我們手動寫.java代碼編譯生成的.class代碼是一樣的,自動生成有一樣好處就是一些公共的或者重復的邏輯我們可以通過自動生成來減輕我們的工作了,通常自動生成.class代碼需要用到JavaFileObject類,如下:
try { //packageName是包名 JavaFileObject source = mFiler.createSourceFile(packageName); Writer writer = source.openWriter(); //classStr代表的類里所有的字符 writer.write(classStr); writer.flush(); writer.close(); } catch (Exception e) { e.printStackTrace(); }
具體JavaFileObject的用法大家可以去搜下,因為也不復雜,這里就不多說了,因為整個類都需要我們手動寫,一是比較麻煩,二是容易出錯,square做了一個開源的javapoet庫來幫我們減少工作量。
來看簡單的一個栗子:
//創建method方法類 MethodSpec methodSpec = MethodSpec.methodBuilder("getValue") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(int.class) .addStatement("return " + proAnnotation.value()) .build(); //創建.class類 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目錄下就可以看到自動生成的.class類了:
// this is auto generated package org.ninetripods.mq.javastudy; public final class autoGenerate { public static int getValue() { return 100; } }
關于“Java怎么自定義注解”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。