咱們常常會在java代碼裏面看到:「@Override」,「@Target」等等樣子的東西,這些是什麼?java
在java裏面它們是「註解」。android
下面是百度百科的解釋:java.lang.annotation.Retention能夠在您定義Annotation型態時,指示編譯器如何對待您的自定義 Annotation,git
預設上編譯器會將Annotation資訊留在class檔案中,但不被虛擬機器讀取,而僅用於編譯器或工具程式運行時提供資訊。github
也就是說,註解是創建在class文件基礎上的東西,同C語言的宏有殊途同歸的效果。app
class文件裏面根本看不到註解的痕跡。框架
註解的基礎就是反射。因此註解能夠理解爲java特有的一種概念。jvm
在java.lang.annotation包裏面,已經定義了4種annotation的「原語」。ide
1).@Target,用於明確被修飾的類型:(方法,字段,類,接口等等)
2).@Retention,描述anntation存在的爲止:函數
RetentionPolicy.RUNTIME 註解會在class字節碼文件中存在,在運行時能夠經過反射獲取到工具
RetentionPolicy.CLASS 默認的保留策略,註解會在class字節碼文件中存在,但運行時沒法得到
RetentionPolicy.SOURCE 註解僅存在於源碼中,在class字節碼文件中不包含
3).@Documented,默認狀況下,註解不會在javadoc中記錄,可是能夠經過這個註解來代表這個註解須要被記錄。
4).@Inherited 元註解是一個標記註解,@Inherited闡述了某個被標註的類型是被繼承的。
若是一個使用了@Inherited修飾的annotation類型被用於一個class,則這個annotation將被用於該class的子類。
package com.joyfulmath.jvmexample.annnotaion; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author deman.lu * @version on 2016-05-23 13:36 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface FruitName { String value() default ""; }
首先,一個註解通常須要2個元註解修飾:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
具體做用上面已解釋。
全部的註解都會有一個相似於「func」的部分。這個能夠理解爲註解的參數。
package com.joyfulmath.jvmexample.annnotaion; import com.joyfulmath.jvmexample.TraceLog; /** * @author deman.lu * @version on 2016-05-23 13:37 */ public class Apple { @FruitName("Apple") String appleName; public void displayAppleName() { TraceLog.i(appleName); } }
這段代碼的log:
05-23 13:39:38.780 26792-26792/com.joyfulmath.jvmexample I/Apple: displayAppleName: null [at (Apple.java:16)]
沒有賦值成功,爲何?應爲註解的「Apple」到底怎麼賦值該filed,目前編譯器還不知道則怎麼作呢。
咱們還須要一個處理器來解釋 註解究竟是怎樣工做的,否則就跟註釋差很少了。
經過反射的方式,能夠獲取註解的內容:
package com.joyfulmath.jvmexample.annnotaion; import com.joyfulmath.jvmexample.TraceLog; import java.lang.reflect.Field; /** * @author deman.lu * @version on 2016-05-23 14:08 */ public class FruitInfoUtils { public static void getFruitInfo(Class<?> clazz) { String fruitNameStr = ""; Field[] fields = clazz.getDeclaredFields(); for(Field field:fields) { if(field.isAnnotationPresent(FruitName.class)) { FruitName fruitName = field.getAnnotation(FruitName.class); fruitNameStr = fruitName.value(); TraceLog.i(fruitNameStr); } } } }
這是註解的通常用法。
從上面能夠看到,註解框架的使用,本質上仍是要用到反射。
可是我若是用反射的功能在使用註解框架,那麼,我還不如直接使用它,反而簡單。
若是有一種機制,能夠避免寫大量重複的類似代碼,尤爲在android開發的時候,大量的findviewbyid & onClick等事件相應。
代碼的模式是一致的,可是代碼又各不相同,這個時候,使用註解框架能夠大量節省開發時間,固然相應的會增長其餘的開銷。
如下就是一個使用butterknife的例子:
@BindString(R.string.login_error)
String loginErrorMessage;
看上去很簡單,就是把字符串賦一個string res對應的初值。這樣寫能夠節省一些時間。固然這只是一個例子,
若是大量使用其餘的註解,能夠節省很大一部分的開發時間。
咱們下面來看看怎麼實現的:
package butterknife; import android.support.annotation.StringRes; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.CLASS; /** * Bind a field to the specified string resource ID. * <pre><code> * {@literal @}BindString(R.string.username_error) String usernameErrorText; * </code></pre> */ @Retention(CLASS) @Target(FIELD) public @interface BindString { /** String resource ID to which the field will be bound. */ @StringRes int value(); }
BindString,只有一個參數,value,也就是賦值爲@StringRes.
同上,上面是註解定義和使用的地方,可是真正解釋註解的地方以下:ButterKnifeProcessor
private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env)
這個函數,截取部分代碼:
// Process each @BindString element. for (Element element : env.getElementsAnnotatedWith(BindString.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceString(element, targetClassMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindString.class, e); } }
找到全部BindString註解的元素,而後開始分析:
private void parseResourceString(Element element, Map<TypeElement, BindingClass> targetClassMap, Set<TypeElement> erasedTargetNames) { boolean hasError = false; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Verify that the target type is String. if (!STRING_TYPE.equals(element.asType().toString())) { error(element, "@%s field type must be 'String'. (%s.%s)", BindString.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } // Verify common generated code restrictions. hasError |= isInaccessibleViaGeneratedCode(BindString.class, "fields", element); hasError |= isBindingInWrongPackage(BindString.class, element); if (hasError) { return; } // Assemble information on the field. String name = element.getSimpleName().toString(); int id = element.getAnnotation(BindString.class).value(); BindingClass bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement); FieldResourceBinding binding = new FieldResourceBinding(id, name, "getString", false); bindingClass.addResource(binding); erasedTargetNames.add(enclosingElement); }
首先驗證element是否是string類型。
// Assemble information on the field. String name = element.getSimpleName().toString(); int id = element.getAnnotation(BindString.class).value();
獲取field的name,以及 string id。
最終
Map<TypeElement, BindingClass> targetClassMap
元素和註解描述,已map的方式一一對應存放。
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env); for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingClass bindingClass = entry.getValue(); try { bindingClass.brewJava().writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write view binder for type %s: %s", typeElement, e.getMessage()); } } return true; }
這就是註解框架啓動的地方,一個獨立的進程。具體細節本文不研究,只需清除,這裏是框架驅動的地方。
從上面的信息已經清除,全部的註解信息都存放在targetClassMap 裏面。
上面標紅的代碼,應該是註解框架的核心之處。
自從Java SE5開始,Java就引入了apt工具,能夠對註解進行預處理,Java SE6,更是支持擴展註解處理器,
並在編譯時多趟處理,咱們可使用自定義註解處理器,在Java編譯時,根據規則,生成新的Java代碼。
JavaFile brewJava() { TypeSpec.Builder result = TypeSpec.classBuilder(generatedClassName) .addModifiers(PUBLIC); if (isFinal) { result.addModifiers(Modifier.FINAL); } else { result.addTypeVariable(TypeVariableName.get("T", targetTypeName)); } TypeName targetType = isFinal ? targetTypeName : TypeVariableName.get("T"); if (hasParentBinding()) { result.superclass(ParameterizedTypeName.get(parentBinding.generatedClassName, targetType)); } else { result.addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, targetType)); } result.addMethod(createBindMethod(targetType)); if (isGeneratingUnbinder()) { result.addType(createUnbinderClass(targetType)); } else if (!isFinal) { result.addMethod(createBindToTargetMethod()); } return JavaFile.builder(generatedClassName.packageName(), result.build()) .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); }
這段話的關鍵是會create一個新文件。
而後把相關內容寫入。
參考:
https://github.com/JakeWharton/butterknife