前言
對注解,一開始是在學(xué)習(xí)java的時(shí)候接觸到的,就是在《Thinking in Java》里草草看過。后來開發(fā)android,自己接了項(xiàng)目時(shí),把xUtils3里的注解模塊摳出來使用了,再到后來畢業(yè)了來了大公司,同事們都說不要使用注解,用反射影響性能,于是,注解就從入門到放棄了。后來發(fā)現(xiàn)注解也可以在編譯時(shí)生成代碼,并不會怎么影響性能,于是準(zhǔn)備寫點(diǎn)來復(fù)習(xí)下注解
基礎(chǔ)概況
注解是Java SE5中的重要特性,也被稱為元數(shù)據(jù)。為我們在代碼中添加信息提供一種形式化的方式,使我們可以在稍后某個(gè)時(shí)刻非常方便地使用這些數(shù)據(jù)。主要作用:
- 可以由編譯器來測試和驗(yàn)證格式
- 存儲有關(guān)程序的額外信息
- 可以用來生成描述符文件或新的類定義
- 減少編寫樣板代碼的負(fù)擔(dān)
分類
- 根據(jù)注解中成員個(gè)數(shù)(0個(gè),1個(gè),多個(gè))把注解分為:標(biāo)記注解,單值注解,完整注解
- 也可以根據(jù)把注解的來源分為jdk自帶的,元注解,和我們自己定義的注解
1. Java SE5中自帶了三種標(biāo)準(zhǔn)注解
@Override 表示當(dāng)前的方法定義將覆蓋超類中的方法
@Deprecated 表示廢棄的意思,使用了該注解的方法或者對象,則會有提示。
@SuppressWarnings 關(guān)閉不當(dāng)?shù)木幾g器警告信息
java 8新特性:加入了 @Repeatable注解,允許多次使用同一個(gè)注解
@Repeatable(Authorities.class)
public @interface Num{
int value();
}
public class Opera{
@Num(value = 1)
@Num(value = 2)
public void add(){ }
}
2. Java SE5中還有四個(gè)元注解,元注解專職負(fù)責(zé)注解其他注解。在 java.lang.annotation下
| 元注解 | 作用 |
|---|---|
| @Target | 表示該注解可以用于什么地方,可能在ElementType參數(shù)包括 CONSTRUCTOR: 用于描述構(gòu)造器 FIELD: 用于描述域 LOCAL_VARIABLE: 用于描述局部變量 METHOD: 用于描述方法 PACKAGE: 用于描述包 PARAMETER: 用于描述參數(shù) TYPE: 用于描述類、接口(包括注解類型) 或enum聲明 |
| @Retention | 表示需要在什么級別保存該注解信息??蛇x的RetentionPolicy參數(shù)包括: SOURCE: 注解將被編譯器丟棄 CLASS: 注解在class文件中可用,但會被VM丟棄。 RUNTIME: VM將在運(yùn)行期也保留注解,因此可以通過反射機(jī)制讀取注解的信息 |
| @Document | 將此注解包含在javadoc中 |
| @Inhrited | 允許子類繼承父類中的注解 |
java 8新特性:java 8之前注解只能是在聲明的地方所使用,比如類,方法,屬性;java 8里面,注解可以應(yīng)用在任何地方,比如方法參數(shù)前面等。
3.自定義注解
先寫幾個(gè)簡單的注解:
- 沒有元素的標(biāo)記注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {}
- 單值注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Num {
int value();
}
- 完整注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Event {
int[] value();
int[] parentId() default 0;
Class<?> type() default View.OnClickListener.class;
String setter() default "";
String method() default "";
}
自定義注解格式:
public @interface 注解名{注解體}。注解體中注解元素可以被public修飾,也可以什么也不寫,元素類型可以是:
- 所有基本數(shù)據(jù)類型(int,float,boolean等)
- String類型
- Class類型
- enum類型
- Annotation類型(說明注解可以嵌套)
- 以上所有類型的數(shù)組
默認(rèn)值的限定:
編譯器對默認(rèn)值過分的挑剔,要么有確定的默認(rèn)值,要么在使用的時(shí)候提供元素的值。
基本類型的元素都有默認(rèn)值,不用寫default也可以,但是想String這類就必須要寫,而且不能寫null,因此在某些需要分清是null還是空字符串的地方要注意。
4. 注解的使用
@Event(value = R.id.btn_test1, type = View.OnClickListener.class)
private void onTestClick(View view) {
……
}
注解元素使用時(shí)變現(xiàn)為鍵值對的形式,如上的value=R.id.btn_test1,沒有賦值的就用默認(rèn)的值了。
當(dāng)然為了簡便,也可以直接寫上值,特別是單值注解
@Num (2)
class Goods {
……
}
5. 注解處理器類庫的使用
如果沒有用來讀取注解的工具,那注解也不會比注釋更有用。使用注解的過程中很重要的一個(gè)部分就是創(chuàng)建與使用注解處理器。Java SE5擴(kuò)展了反射機(jī)制的API,在java.lang.reflect 包下新增了AnnotatedElement接口,該接口代表程序中可以接受注解的程序元素,以下是源碼,不要看到接口里的方法有具體實(shí)現(xiàn)感到驚訝(Java 8允許我們給接口添加一個(gè)非抽象的方法實(shí)現(xiàn),只需要使用 default關(guān)鍵字即可,這個(gè)特征又叫做擴(kuò)展方法),原來的注釋太長我去掉了,寫上簡單的漢字注釋,java 8比起之前新增了兩個(gè)方法。
public interface AnnotatedElement {
//該元素是否被注解標(biāo)記了
default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
return getAnnotation(annotationClass) != null;
}
//獲取該元素指定注解類型的值
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
//獲取該元素所有的注解
Annotation[] getAnnotations();
//1.8新增,返回重復(fù)注解(@Repeatable)的類型
default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
T[] result = getDeclaredAnnotationsByType(annotationClass);
if (result.length == 0 && // Neither directly nor indirectly present
this instanceof Class && // the element is a class
AnnotationType.getInstance(annotationClass).isInherited()) { // Inheritable
Class<?> superClass = ((Class<?>) this).getSuperclass();
if (superClass != null) {
// Determine if the annotation is associated with the
// superclass
result = superClass.getAnnotationsByType(annotationClass);
}
}
return result;
}
//返回直接存在于此元素上的所有注釋,不考慮繼承下來的
default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {
Objects.requireNonNull(annotationClass);
// Loop over all directly-present annotations looking for a matching one
for (Annotation annotation : getDeclaredAnnotations()) {
if (annotationClass.equals(annotation.annotationType())) {
// More robust to do a dynamic cast at runtime instead
// of compile-time only.
return annotationClass.cast(annotation);
}
}
return null;
}
//1.8新增,返回直接或者間接標(biāo)記在該元素的注解類型,不考慮繼承。
default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) {
Objects.requireNonNull(annotationClass);
return AnnotationSupport.
getDirectlyAndIndirectlyPresent(Arrays.stream(getDeclaredAnnotations()).
collect(Collectors.toMap(Annotation::annotationType, Function.identity(),
((first, second) -> first), LinkedHashMap::new)), annotationClass);
}
Annotation[] getDeclaredAnnotations();
}
該接口主要有如下幾個(gè)實(shí)現(xiàn)類:
- Class:類定義
- Constructor:構(gòu)造器定義
- Field:累的成員變量定義
- Method:類的方法定義
- Package:類的包定義
通過反射獲取成員名再獲取注解值得一般寫法,同樣的可以通過反射獲取方法等
Field fields[] = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Num.class)) {
Num num= field.getAnnotation(Num.class);
……
}
……
}
在網(wǎng)上找到了一張注解的提綱,非常詳細(xì),除了沒有java 8里注解新特性,這里引用下:

講了這么多,都是純java和運(yùn)行時(shí)注解的,下面開始結(jié)合Android Studio講講編譯時(shí)注解
APT
講編譯時(shí)注解,先了解下注解處理器工具APT(Annotation Processing Tool)
APT(Annotation processing tool)是一種處理注釋的工具,它對源代碼文件進(jìn)行檢測找出其中的Annotation,使用Annotation進(jìn)行額外的處理??梢栽诰幾g時(shí)進(jìn)行注解處理,也可以在運(yùn)行時(shí)通過反射API進(jìn)行注解處理。編譯時(shí)進(jìn)行注解處理是根據(jù)源文件中的Annotation生成額外的源文件和其它的文件,將它們一起生成class文件。
使用APT主要的目的是簡化開發(fā)者的工作量,因?yàn)锳PT可以編譯程序源代碼的同時(shí),生成一些附屬文件(比如源文件,類文件,程序發(fā)布描述文件等),這些附屬文件的內(nèi)容也都是與源代碼相關(guān)的,換句話說,使用APT可以代替?zhèn)鹘y(tǒng)的對代碼信息和附屬文件的維護(hù)工作。
下面將結(jié)合Android Studio實(shí)現(xiàn)編譯時(shí)注解,主要內(nèi)容來自這篇文章:THE 10-STEP GUIDE TO ANNOTATION PROCESSING IN ANDROID STUDIO。先看下項(xiàng)目的結(jié)構(gòu),如果是第一次嘗試,包名最好先完全一樣,省的哪里出錯(cuò)。

-
新建一個(gè)android項(xiàng)目 AnnotationProcessor,包名為com.stablekernel.annotationprocessor,再建一個(gè)Java Library 的module,如下圖所示
構(gòu)建注解module
并且設(shè)置下app模塊依賴processor模塊
Paste_Image.png 設(shè)置兼容性
- 在app 模塊的gradle里添加
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
如下圖所示:

- 在processor 模塊的gradle里添加
sourceCompatibility = 1.7
targetCompatibility = 1.7
如下圖所示:

-
在processor 模塊里創(chuàng)建注解
Paste_Image.png
CustomAnnotation是自定義的注解,源碼很簡單:
package com.stablekernel.annotationprocessor.processor;
public @interface CustomAnnotation {
}
- 在processor 模塊里創(chuàng)建注解處理器CustomAnnotationProcessor是注解處理器,源碼如下:
@SupportedAnnotationTypes("com.stablekernel.annotationprocessor.processor.CustomAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class CustomAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
StringBuilder builder = new StringBuilder()
.append("package com.stablekernel.annotationprocessor.generated;\n\n")
.append("public class GeneratedClass {\n\n") // open class
.append("\tpublic String getMessage() {\n") // open method
.append("\t\treturn \"");
// for each javax.lang.model.element.Element annotated with the CustomAnnotation
for (Element element : roundEnv.getElementsAnnotatedWith(CustomAnnotation.class)) {
String objectType = element.getSimpleName().toString();
// this is appending to the return statement
builder.append(objectType).append(" says hello!\\n");
}
builder.append("\";\n") // end return
.append("\t}\n") // close method
.append("}\n"); // close class
try { // write the file
JavaFileObject source = processingEnv.getFiler()
.createSourceFile("com.stablekernel.annotationprocessor.generated.GeneratedClass");
Writer writer = source.openWriter();
writer.write(builder.toString());
writer.flush();
writer.close();
} catch (IOException e) {
// Note: calling e.printStackTrace() will print IO errors
// that occur from the file already existing after its first run, this is normal
}
return true;
}
}
稍微解釋下,注解器需要繼承AbstractProcessor ,并且實(shí)現(xiàn)process方法,CustomAnnotationProcessor 類上面兩個(gè)注解@SupportedAnnotationTypes 和@SupportedSourceVersion 分別表示支持的注解的類型和支持的版本,在process里面的StringBuilder 就是在編譯時(shí)要?jiǎng)?chuàng)建的新文件里面的內(nèi)容了,里面一個(gè)for循環(huán)是遍歷了所有使用該CustomAnnotation.class注解的元素,取出其名字。最下面的try catch 里是將該文件寫入source下, 編譯成功后,具體位置如下圖:

-
創(chuàng)建resources
Paste_Image.png
在processor 的main文件夾下 新建文件夾resources ,新建文件夾META-INF ,新建文件javax.annotation.processing.Processor。該文件里面的內(nèi)容就是注解處理器的路徑,如果有多個(gè)注解處理器,記得換行,每行寫一個(gè)。
Paste_Image.png -
在全局的gradle里添加android-apt依賴,再在app 模塊的gradle中添加插件。
Paste_Image.png
Paste_Image.png -
設(shè)置構(gòu)建依賴:
Paste_Image.png
或者
dependencies {
compile files('libs/processor.jar')
……
}
然后寫個(gè)任務(wù),使得processor.jar復(fù)制到app的lib下,和預(yù)構(gòu)建的任務(wù)
task processorTask(type: Copy) {
from '../processor/build/libs/processor.jar'
into 'libs/'
}
processorTask.dependsOn(':processor:build')
preBuild.dependsOn(processorTask)
app的gralde最終變成這樣,供參考:

到這里其實(shí)就已經(jīng)結(jié)束了,最后為了驗(yàn)證下,在MainActivity中調(diào)用下就可以
@CustomAnnotation
public class MainActivity extends AppCompatActivity {
@CustomAnnotation
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
showAnnotationMessage();
}
private void showAnnotationMessage() {
GeneratedClass generatedClass = new GeneratedClass();
String message = generatedClass.getMessage();
// android.support.v7.app.AlertDialog
new AlertDialog.Builder(this)
.setPositiveButton("Ok", null)
.setTitle("Annotation Processor Messages")
.setMessage(message)
.show();
}
}
為了加深對編譯時(shí)注解的理解,再推薦下這篇文章:ButterKnife源碼分析







