我們經(jīng)常會(huì)在java代碼里面看到:“@Override”,“@Target”等等樣子的東西,這些是什么?
在java里面它們是“注解”。
下面是百度百科的解釋:java.lang.annotation.Retention可以在您定義Annotation型態(tài)時(shí),指示編譯器如何對待您的自定義 Annotation,預(yù)設(shè)上編譯器會(huì)將Annotation資訊留在class檔案中,但不被虛擬機(jī)器讀取,而僅用于編譯器或工具程式運(yùn)行時(shí)提供資訊。
也就是說,注解是建立在class文件基礎(chǔ)上的東西,同C語言的宏有異曲同工的效果。
class文件里面根本看不到注解的痕跡。
注解的基礎(chǔ)就是反射。所以注解可以理解為java特有的一種概念。
1.元注解
在java.lang.annotation包里面,已經(jīng)定義了4種annotation的“原語”。
1).@Target,用于明確被修飾的類型:(方法,字段,類,接口等等)
2).@Retention,描述anntation存在的為止:
RetentionPolicy.RUNTIME 注解會(huì)在class字節(jié)碼文件中存在,在運(yùn)行時(shí)可以通過反射獲取到
RetentionPolicy.CLASS 默認(rèn)的保留策略,注解會(huì)在class字節(jié)碼文件中存在,但運(yùn)行時(shí)無法獲得
RetentionPolicy.SOURCE 注解僅存在于源碼中,在class字節(jié)碼文件中不包含
3).@Documented,默認(rèn)情況下,注解不會(huì)在javadoc中記錄,但是可以通過這個(gè)注解來表明這個(gè)注解需要被記錄。
4).@Inherited 元注解是一個(gè)標(biāo)記注解,@Inherited闡述了某個(gè)被標(biāo)注的類型是被繼承的。
如果一個(gè)使用了@Inherited修飾的annotation類型被用于一個(gè)class,則這個(gè)annotation將被用于該class的子類。
2.自定義注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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 "" ; } |
首先,一個(gè)注解一般需要2個(gè)元注解修飾:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
具體作用上面已解釋。
所有的注解都會(huì)有一個(gè)類似于“func”的部分。這個(gè)可以理解為注解的參數(shù)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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)]
沒有賦值成功,為什么?應(yīng)為注解的“Apple”到底怎么賦值該filed,目前編譯器還不知道則怎么做呢。
3.注解處理器
我們還需要一個(gè)處理器來解釋 注解到底是怎樣工作的,不然就跟注釋差不多了。
通過反射的方式,可以獲取注解的內(nèi)容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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注解框架解析
從上面可以看到,注解框架的使用,本質(zhì)上還是要用到反射。
但是我如果用反射的功能在使用注解框架,那么,我還不如直接使用它,反而簡單。
如果有一種機(jī)制,可以避免寫大量重復(fù)的相似代碼,尤其在android開發(fā)的時(shí)候,大量的findviewbyid & onClick等事件相應(yīng)。
代碼的模式是一致的,但是代碼又各不相同,這個(gè)時(shí)候,使用注解框架可以大量節(jié)省開發(fā)時(shí)間,當(dāng)然相應(yīng)的會(huì)增加其他的開銷。
以下就是一個(gè)使用butterknife的例子:
@BindString(R.string.login_error)
String loginErrorMessage;
看上去很簡單,就是把字符串賦一個(gè)string res對應(yīng)的初值。這樣寫可以節(jié)省一些時(shí)間。當(dāng)然這只是一個(gè)例子,
如果大量使用其他的注解,可以節(jié)省很大一部分的開發(fā)時(shí)間。
我們下面來看看怎么實(shí)現(xiàn)的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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,只有一個(gè)參數(shù),value,也就是賦值為@StringRes.
同上,上面是注解定義和使用的地方,但是真正解釋注解的地方如下:ButterKnifeProcessor
private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env)
這個(gè)函數(shù),截取部分代碼:
1
2
3
4
5
6
7
8
9
|
// 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注解的元素,然后開始分析:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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); } |
首先驗(yàn)證element是不是string類型。
1
2
3
|
// 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的方式一一對應(yīng)存放。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@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 ; } |
這就是注解框架啟動(dòng)的地方,一個(gè)獨(dú)立的進(jìn)程。具體細(xì)節(jié)本文不研究,只需清除,這里是框架驅(qū)動(dòng)的地方。
從上面的信息已經(jīng)清除,所有的注解信息都存放在targetClassMap 里面。
上面標(biāo)紅的代碼,應(yīng)該是注解框架的核心之處。
自從Java SE5開始,Java就引入了apt工具,可以對注解進(jìn)行預(yù)處理,Java SE6,更是支持?jǐn)U展注解處理器,
并在編譯時(shí)多趟處理,我們可以使用自定義注解處理器,在Java編譯時(shí),根據(jù)規(guī)則,生成新的Java代碼。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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(); } |
這段話的關(guān)鍵是會(huì)create一個(gè)新文件。
然后把相關(guān)內(nèi)容寫入。