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一个新文件。
然后把相关内容写入。
参考:
https://github.com/JakeWharton/butterknife
Java注解一谈的更多相关文章
- Java注解浅谈
注解定义(来自百度百科):指示编译器如何对待您的自定义 Annotation,预设上编译器会将Annotation资讯留在class档案中,但不被虚拟机器读取,而仅用于编译器或工具程式运行时提供资讯. ...
- Java 注解指导手册 – 终极向导
原文链接 原文作者:Dani Buiza 译者:Toien Liu 校对:深海 编者的话:注解是java的一个主要特性且每个java开发者都应该知道如何使用它. 我们已经在Java Code Gee ...
- 你真的理解Java 注解吗?
你真的理解Java 注解吗? 1.什么是注解? 官方解释: Java 注解用于为 Java 代码提供元数据.作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的.Java ...
- Java 注解指导手册(上)
编者的话:注解是java的一个主要特性且每个java开发者都应该知道如何使用它. 我们已经在Java Code Geeks提供了丰富的教程, 如Creating Your Own Java A ...
- Java注解
Java注解其实是代码里的特殊标记,使用其他工具可以对其进行处理.注解是一种元数据,起到了描述.配置的作用,生成文档,所有的注解都隐式地扩展自java.lang.annotation.Annotati ...
- 19.Java 注解
19.Java注解 1.Java内置注解----注解代码 @Deprecated //不推荐使用的过时方法 @Deprecated ...
- Java注解入门
注解的分类 按运行机制分: 源码注解:只在源码中存在,编译后不存在 编译时注解:源码和编译后的class文件都存在(如@Override,@Deprecated,@SuppressWarnin ...
- java注解(Annotation)解析
注解(Annotation)在java中应用非常广泛.它既能帮助我们在编码中减少错误,(比如最常见的Override注解),还可以帮助我们减少各种xml文件的配置,比如定义AOP切面用@AspectJ ...
- JAVA 注解的几大作用及使用方法详解
JAVA 注解的几大作用及使用方法详解 (2013-01-22 15:13:04) 转载▼ 标签: java 注解 杂谈 分类: Java java 注解,从名字上看是注释,解释.但功能却不仅仅是注释 ...
随机推荐
- C程序编译过程浅析
前几天看了<程序员的自我修养——链接.装载与库>中的第二章“编译和链接”,主要根据其中的内容简单总结一下C程序编译的过程吧. 我现在一般都是用gcc,所以自然以GCC编译hellworld ...
- 15天玩转redis —— 第五篇 集合对象类型
这篇我们来看看Redis五大类型中的第四大类型:“集合类型”,集合类型还是蛮有意思的,第一个是因为它算是只使用key的Dictionary简易版, 这样说来的话,它就比Dictionary节省很多内存 ...
- SSH中的jar包讲解(1)
我们在搭建SSH框架的时候,需要引入各自的一些jar包,相信很多初学者跟我一样,搜个资料,照搬过来(当然版本还得对应),至于为什么要引入这些个jar包,引入它们的作用是啥子,一头雾水,今天我就来跟这些 ...
- 跨平台日志清理工具 Log-Cutter v1.0.3 正式发布
Log-Cutter 是JessMA开源组织开发的一个简单实用的日志切割清理工具.对于服务器的日常维护来说,日志清理是非常重要的事情,如果残留日志过多则严重浪费磁盘空间同时影响服务的性能.如果用手工方 ...
- java内存模型-锁
锁的释放-获取建立的 happens before 关系 锁是 java 并发编程中最重要的同步机制.锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息.下面是锁释放-获取的示 ...
- 为Apple Push开发的PHP PEAR 包:Services_Apple_PushNotification
Apple Push Notification Service:通过苹果服务器向app用户推送消息,无需启动app. 苹果官方文档:http://developer.apple.com/library ...
- HTML5 表单新增属性
1. 表单内元素的form属性 在H5中可以把form放到页面的任何地方,然后为该元素指定一个form属性,属性值为该表单的id,这样就可以声明该元素从属于指定表单了 <form id=&quo ...
- vs2010 未能将脚本调试器附加到计算机上的进程。已附加了一个调试器
图片: 上周不小心升级了IE10,今天,VS2010报:未能将脚本调试器附加到计算机XXX上的进程iexplore.exe . 已附加了一个调试器”.启动调试失败. 到网上查找解决办法,最后用这个解决 ...
- JavaScript基础20——element对象
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- 第一次写jquery插件,来个countdown计时器吧
之前同学做个购物商城秒杀活动需要计时器的功能,在用jquery提供的countdown插件时,一直报错,貌似还需要依赖除jquery之外的其他插件,搞了半天也没搞成功,就叫我帮忙写个.然而我并没有写过 ...