深入浅析Java注解框架
我们经常会在java代码里面看到:“@Override”,“@Target”等等样子的东西,这些是什么?
在java里面它们是“注解”。
下面是百度百科的解释:java.lang.annotation.Retention可以在您定义Annotation型态时,指示编译器如何对待您的自定义Annotation,预设上编译器会将Annotation资讯留在class档案中,但不被虚拟机器读取,而仅用于编译器或工具程式运行时提供资讯。
也就是说,注解是建立在class文件基础上的东西,同C语言的宏有异曲同工的效果。
class文件里面根本看不到注解的痕迹。
注解的基础就是反射。所以注解可以理解为java特有的一种概念。
1.元注解
在java.lang.annotation包里面,已经定义了4种annotation的“原语”。
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的子类。
2.自定义注解
packagecom.joyfulmath.jvmexample.annnotaion;
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
/**
*@authordeman.lu
*@versionon2016-05-2313:36
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public@interfaceFruitName{
Stringvalue()default"";
}
首先,一个注解一般需要2个元注解修饰:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
具体作用上面已解释。
所有的注解都会有一个类似于“func”的部分。这个可以理解为注解的参数。
packagecom.joyfulmath.jvmexample.annnotaion;
importcom.joyfulmath.jvmexample.TraceLog;
/**
*@authordeman.lu
*@versionon2016-05-2313:37
*/
publicclassApple{
@FruitName("Apple")
StringappleName;
publicvoiddisplayAppleName()
{
TraceLog.i(appleName);
}
}
这段代码的log:
05-2313:39:38.78026792-26792/com.joyfulmath.jvmexampleI/Apple:displayAppleName:null[at(Apple.java:16)]
没有赋值成功,为什么?应为注解的“Apple”到底怎么赋值该filed,目前编译器还不知道则怎么做呢。
3.注解处理器
我们还需要一个处理器来解释注解到底是怎样工作的,不然就跟注释差不多了。
通过反射的方式,可以获取注解的内容:
packagecom.joyfulmath.jvmexample.annnotaion;
importcom.joyfulmath.jvmexample.TraceLog;
importjava.lang.reflect.Field;
/**
*@authordeman.lu
*@versionon2016-05-2314:08
*/
publicclassFruitInfoUtils{
publicstaticvoidgetFruitInfo(Class<?>clazz)
{
StringfruitNameStr="";
Field[]fields=clazz.getDeclaredFields();
for(Fieldfield:fields)
{
if(field.isAnnotationPresent(FruitName.class))
{
FruitNamefruitName=field.getAnnotation(FruitName.class);
fruitNameStr=fruitName.value();
TraceLog.i(fruitNameStr);
}
}
}
}
这是注解的一般用法。
android注解框架解析
从上面可以看到,注解框架的使用,本质上还是要用到反射。
但是我如果用反射的功能在使用注解框架,那么,我还不如直接使用它,反而简单。
如果有一种机制,可以避免写大量重复的相似代码,尤其在android开发的时候,大量的findviewbyid&onClick等事件相应。
代码的模式是一致的,但是代码又各不相同,这个时候,使用注解框架可以大量节省开发时间,当然相应的会增加其他的开销。
以下就是一个使用butterknife的例子:
@BindString(R.string.login_error)
StringloginErrorMessage;
看上去很简单,就是把字符串赋一个stringres对应的初值。这样写可以节省一些时间。当然这只是一个例子,
如果大量使用其他的注解,可以节省很大一部分的开发时间。
我们下面来看看怎么实现的:
packagebutterknife;
importandroid.support.annotation.StringRes;
importjava.lang.annotation.Retention;
importjava.lang.annotation.Target;
importstaticjava.lang.annotation.ElementType.FIELD;
importstaticjava.lang.annotation.RetentionPolicy.CLASS;
/**
*BindafieldtothespecifiedstringresourceID.
*<pre><code>
*{@literal@}BindString(R.string.username_error)StringusernameErrorText;
*</code></pre>
*/
@Retention(CLASS)@Target(FIELD)
public@interfaceBindString{
/**StringresourceIDtowhichthefieldwillbebound.*/
@StringResintvalue();
}
BindString,只有一个参数,value,也就是赋值为@StringRes.
同上,上面是注解定义和使用的地方,但是真正解释注解的地方如下:ButterKnifeProcessor
privateMap<TypeElement,BindingClass>findAndParseTargets(RoundEnvironmentenv)
这个函数,截取部分代码:
//Processeach@BindStringelement.
for(Elementelement:env.getElementsAnnotatedWith(BindString.class)){
if(!SuperficialValidation.validateElement(element))continue;
try{
parseResourceString(element,targetClassMap,erasedTargetNames);
}catch(Exceptione){
logParsingError(element,BindString.class,e);
}
}
找到所有BindString注解的元素,然后开始分析:
privatevoidparseResourceString(Elementelement,Map<TypeElement,BindingClass>targetClassMap,
Set<TypeElement>erasedTargetNames){
booleanhasError=false;
TypeElementenclosingElement=(TypeElement)element.getEnclosingElement();
//VerifythatthetargettypeisString.
if(!STRING_TYPE.equals(element.asType().toString())){
error(element,"@%sfieldtypemustbe'String'.(%s.%s)",
BindString.class.getSimpleName(),enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError=true;
}
//Verifycommongeneratedcoderestrictions.
hasError|=isInaccessibleViaGeneratedCode(BindString.class,"fields",element);
hasError|=isBindingInWrongPackage(BindString.class,element);
if(hasError){
return;
}
//Assembleinformationonthefield.
Stringname=element.getSimpleName().toString();
intid=element.getAnnotation(BindString.class).value();
BindingClassbindingClass=getOrCreateTargetClass(targetClassMap,enclosingElement);
FieldResourceBindingbinding=newFieldResourceBinding(id,name,"getString",false);
bindingClass.addResource(binding);
erasedTargetNames.add(enclosingElement);
}
首先验证element是不是string类型。
//Assembleinformationonthefield. Stringname=element.getSimpleName().toString(); intid=element.getAnnotation(BindString.class).value();
获取field的name,以及stringid。
最终
Map<TypeElement,BindingClass>targetClassMap
元素和注解描述,已map的方式一一对应存放。
@Overridepublicbooleanprocess(Set<?extendsTypeElement>elements,RoundEnvironmentenv){
Map<TypeElement,BindingClass>targetClassMap=findAndParseTargets(env);
for(Map.Entry<TypeElement,BindingClass>entry:targetClassMap.entrySet()){
TypeElementtypeElement=entry.getKey();
BindingClassbindingClass=entry.getValue();
try{
bindingClass.brewJava().writeTo(filer);
}catch(IOExceptione){
error(typeElement,"Unabletowriteviewbinderfortype%s:%s",typeElement,
e.getMessage());
}
}
returntrue;
}
这就是注解框架启动的地方,一个独立的进程。具体细节本文不研究,只需清除,这里是框架驱动的地方。
从上面的信息已经清除,所有的注解信息都存放在targetClassMap里面。
上面标红的代码,应该是注解框架的核心之处。
自从JavaSE5开始,Java就引入了apt工具,可以对注解进行预处理,JavaSE6,更是支持扩展注解处理器,
并在编译时多趟处理,我们可以使用自定义注解处理器,在Java编译时,根据规则,生成新的Java代码。
JavaFilebrewJava(){
TypeSpec.Builderresult=TypeSpec.classBuilder(generatedClassName)
.addModifiers(PUBLIC);
if(isFinal){
result.addModifiers(Modifier.FINAL);
}else{
result.addTypeVariable(TypeVariableName.get("T",targetTypeName));
}
TypeNametargetType=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));
}elseif(!isFinal){
result.addMethod(createBindToTargetMethod());
}
returnJavaFile.builder(generatedClassName.packageName(),result.build())
.addFileComment("GeneratedcodefromButterKnife.Donotmodify!")
.build();
}
这段话的关键是会create一个新文件。
然后把相关内容写入。