深入浅析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一个新文件。

然后把相关内容写入。

(0)

相关推荐

  • Android注解框架对比分析

    Java的注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,标记可以加在包,类,属性,方法,本地变量上.然后你可以写一个注解处理器去解析处理这些注解(人称编译时注解),也可以在程序运行时利用反射得到注解做出相应的处理(人称运行时注解). 开发Android程序时,没完没了的findViewById, setOnClickListener等等方法,已经让大多数开发者头疼不已.好在市面上有所谓的注解框架可以帮助开发者简化一些过程.比较流行的有butterknife

  • 详解Java的Hibernate框架中的注解与缓存

    注解 Hibernate注解是一个没有使用XML文件来定义映射的最新方法.可以在除或替换的XML映射元数据使用注解. Hibernate的注解是强大的方式来提供元数据对象和关系表的映射.所有的元数据被杵到一起的代码POJO java文件这可以帮助用户在开发过程中同时要了解表的结构和POJO. 如果打算让应用程序移植到其他EJB3规范的ORM应用程序,必须使用注解来表示映射信息,但仍然如果想要更大的灵活性,那么应该使用基于XML的映射去. 环境设置Hibernate注释 首先,必须确保使用的是JD

  • java教程之java注解annotation使用方法

    1.概述 注解可以定义到方法上,类上,一个注解相当与一个类,就相当于实例了一个对象,加上了注解,就相当于加了一个标志. 常用的注解:@Override:表示重新父类的方法,这个也可以判断是否覆盖的父类方法,在方法前面加上此语句,如果提示的错误,那么你不是覆盖的父类的方法,要是提示的没有错误,那么就是覆盖的父类的方法.@SuppressWarnings("deprecation"):取消编译器的警告(例如你使用的方法过时了)@Deprecated:在方法的最上边也上此语句,表示此方法过时

  • 深入分析安卓(Android)中的注解

    归纳而言,Android中的注解大概有以下好处 1.提高我们的开发效率 2.更早的发现程序的问题或者错误 3.更好的增加代码的描述能力 4.更加利于我们的一些规范约束 5.提供解决问题的更优解 准备工作 默认情况下,Android中的注解包并没有包括在framework中,它独立成一个单独的包,通常我们需要引入这个包. dependencies { compile 'com.android.support:support-annotations:22.2.0' } 但是如果我们已经引入了 app

  • Android 中的注解深入探究

    本文系GDG Android Meetup分享内容总结文章 注解是我们经常接触的技术,Java有注解,Android也有注解,本文将试图介绍Android中的注解,以及ButterKnife和Otto这些基于注解的库的一些工作原理. 归纳而言,Android中的注解大概有以下好处 提高我们的开发效率 更早的发现程序的问题或者错误 更好的增加代码的描述能力 更加利于我们的一些规范约束 提供解决问题的更优解 准备工作 默认情况下,Android中的注解包并没有包括在framework中,它独立成一个

  • Spring常用注解汇总

    本文汇总了Spring的常用注解,以方便大家查询和使用,具体如下: 使用注解之前要开启自动扫描功能 其中base-package为需要扫描的包(含子包). <context:component-scan base-package="cn.test"/> @Configuration把一个类作为一个IoC容器,它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean. @Scope注解 作用域 @Lazy(true) 表示延迟初始化 @Service用于

  • Android注解ButterKnife的基本使用

    ButterKnife的最新版本是8.4.0. 首先,需要导入ButterKnife的jar包. 在AndroidStudio中,File->Project Structure->Dependencies->Library dependency 搜索butterknife即可,第一个就是. 另外一种就是直接在build:grade(app)dependencies里添加: compile 'com.jakewharton:butterknife:8.4.0' annotationProc

  • 详解Java的Spring框架中的注解的用法

    1. 使用Spring注解来注入属性 1.1. 使用注解以前我们是怎样注入属性的 类的实现: class UserManagerImpl implements UserManager { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } ... } 配置文件: <bean id="userManagerImpl" class="com.

  • Java注解之Retention、Documented、Inherited介绍

    Retention注解 Retention(保留)注解说明,这种类型的注解会被保留到那个阶段. 有三个值: 1.RetentionPolicy.SOURCE -- 这种类型的Annotations只在源代码级别保留,编译时就会被忽略 2.RetentionPolicy.CLASS -- 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略 3.RetentionPolicy.RUNTIME -- 这种类型的Annotations将被JVM保留,所以他们能在运行时

  • Android 中的注解详细介绍

    注解是我们经常接触的技术,Java有注解,Android也有注解,本文将试图介绍Android中的注解,以及ButterKnife和Otto这些基于注解的库的一些工作原理. 归纳而言,Android中的注解大概有以下好处 提高我们的开发效率 更早的发现程序的问题或者错误 更好的增加代码的描述能力 更加利于我们的一些规范约束 提供解决问题的更优解 准备工作 默认情况下,Android中的注解包并没有包括在framework中,它独立成一个单独的包,通常我们需要引入这个包. dependencies

随机推荐