当前位置: 移动技术网 > IT编程>开发语言>Java > 深入浅析Java注解框架

深入浅析Java注解框架

2019年07月22日  | 移动技术网IT编程  | 我要评论
我们经常会在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一个新文件。

然后把相关内容写入。

如您对本文有疑问或者有任何想说的,请 点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网