欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

深入浅析Java注解框架

程序员文章站 2024-03-12 11:51:44
我们经常会在java代码里面看到:“@override”,“@target”等等样子的东西,这些是什么? 在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一个新文件。

然后把相关内容写入。