深入浅析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.自定义注解
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,目前编译器还不知道则怎么做呢。
3.注解处理器
我们还需要一个处理器来解释 注解到底是怎样工作的,不然就跟注释差不多了。
通过反射的方式,可以获取注解的内容:
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注解框架解析
从上面可以看到,注解框架的使用,本质上还是要用到反射。
但是我如果用反射的功能在使用注解框架,那么,我还不如直接使用它,反而简单。
如果有一种机制,可以避免写大量重复的相似代码,尤其在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一个新文件。
然后把相关内容写入。
上一篇: PHP实现的简单操作SQLite数据库类与用法示例
下一篇: Yii 2.0自带的验证码使用经验分享
推荐阅读