Java注解一谈

阅读目录

我们经常会在java代码里面看到:“@Override”,“@Target”等等样子的东西,这些是什么?

在java里面它们是“注解”。

下面是百度百科的解释:java.lang.annotation.Retention可以在您定义Annotation型态时,指示编译器如何对待您的自定义 Annotation,

预设上编译器会将Annotation资讯留在class档案中,但不被虚拟机器读取,而仅用于编译器或工具程式运行时提供资讯。

也就是说,注解是建立在class文件基础上的东西,同C语言的宏有异曲同工的效果。

class文件里面根本看不到注解的痕迹。

注解的基础就是反射。所以注解可以理解为java特有的一种概念。

回到顶部

1.元注解

在java.lang.annotation包里面,已经定义了4种annotation的“原语”。

1)[email protected],用于明确被修饰的类型:(方法,字段,类,接口等等)  
2)[email protected],描述anntation存在的为止:

RetentionPolicy.RUNTIME 注解会在class字节码文件中存在,在运行时可以通过反射获取到

RetentionPolicy.CLASS 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得

RetentionPolicy.SOURCE 注解仅存在于源码中,在class字节码文件中不包含

3)[email protected],默认情况下,注解不会在javadoc中记录,但是可以通过这个注解来表明这个注解需要被记录。
4)[email protected] 元注解是一个标记注解,@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

时间: 2024-08-29 20:34:47

Java注解一谈的相关文章

Java注解浅谈

注解定义(来自百度百科):指示编译器如何对待您的自定义 Annotation,预设上编译器会将Annotation资讯留在class档案中,但不被虚拟机器读取,而仅用于编译器或工具程式运行时提供资讯. 随着零配置的流行,注解的使用也越来越大众化,注解的学习也很有必要.最近学习了下Spring的几个注解,这里与大家分享下自己对注解的理解.首先我们来看下@Controller这个注解的源码: ? 1 2 3 4 5 6 7 8 9 10 package org.springframework.ste

Java 注解(Annoation) 事实说话

1 Junit中的@Test为例: 1.1 用注解(@Test)前 private boolean isTestMethod(Method m) { return m.getParameterTypes().length == 0 && m.getName().startsWith("test") && m.getReturnType().equals(Void.TYPE); } 用注解前(Junit4之前),Junit一般通过类似与上面的代码来获取一个

Java 注解那些事儿

今日无事,应狒狒之邀,写一篇详细的关于注解的文章. 注解一般有如下几种作用 生成文档,这是大家最常见的也是Java最早提供的注解功能.比如看源码的时候方法注释上面的 @see @param @return 等等: 减少配置,可以进行运行时动态处理,得到注解信息,实现代替配置文件的功能:也可以通过插件进行编译时处理,以解决解析注解而导致的反射性能消耗: 减少重复工作,比如第三方框架ButterKnife等,通过注解@BindView减少对findViewById的调用: 限定作用域,例如在编译时进

Java 注解指导手册 – 终极向导

原文链接 原文作者:Dani Buiza 译者:Toien Liu  校对:深海 编者的话:注解是java的一个主要特性且每个java开发者都应该知道如何使用它. 我们已经在Java Code Geeks提供了丰富的教程, 如Creating Your Own Java Annotations, Java Annotations Tutorial with Custom Annotation 和 Java Annotations: Explored & Explained. 我们也有些文章是关于

Java注解(2)-注解处理器(运行时|RetentionPolicy.RUNTIME)

如果没有用来读取注解的工具,那注解将基本没有任何作用,它也不会比注释更有用.读取注解的工具叫作注解处理器.Java提供了两种方式来处理注解:第一种是利用运行时反射机制:另一种是使用Java提供的API来处理编译期的注解. 反射机制方式的注解处理器 仅当定义的注解的@Retention为RUNTIME时,才能够通过运行时的反射机制来处理注解.下面结合例子来说明这种方式的处理方法. Java中的反射API(如java.lang.Class.java.lang.reflect.Field等)都实现了接

Java注解(1)-注解基础

注解(Annotation)是在JAVA5中开始引入的,它为在代码中添加信息提供了一种新的方式.注解在一定程度上把元数据与源代码文件结合在一起,正如许多成熟的框架(Spring)所做的那样.那么,注解到底可以做什么呢? 1.注解的作用. 提供用来完整地描述程序所需要的信息,如编译期校验程序信息. 生成描述符文件,或生成新类的定义. 减轻编写"样板"代码(配置文件)的负担,可以使用注解自动生成. 更加干净易读的代码. 编译期类型检查. 2.Java提供的注解 Java5内置了一些原生的注

Java注解(3)-注解处理器(编译期|RetentionPolicy.SOURCE)

注解的处理除了可以在运行时通过反射机制处理外,还可以在编译期进行处理.在编译期处理注解时,会处理到不再产生新的源文件为止,之后再对所有源文件进行编译. Java5中提供了apt工具来进行编译期的注解处理.apt是命令行工具,与之配套的是一套描述"程序在编译时刻的静态结构"的API:Mirror API(com.sun.mirror.*).通过Mirror API可以获取到被注解的Java类型元素的信息,从而提供自定义的处理逻辑.具体的处理工具交给apt来处理.编写注解处理器的核心是两个

java注解中的元注解

一:java注解中的元注解 四个元注解分别是:@Target,@Retention,@Documented,@Inherited , 再次强调下元注解是java API提供,是专门用来定义注解的注解,其作用分别如下: @Target 表示该注解用于什么地方,可能的值在枚举类 ElemenetType 中,包括: ElemenetType.CONSTRUCTOR----------------------------构造器声明 ElemenetType.FIELD ----------------

Java注解

注解是一种元数据形式,提供关于不是程序部分的程序的数据.操作代码上的注解不影响注解的代码. 注解有许多用途,其中: 编译器信息 -- 注解被用于编译器检测错误或抑制警告. 编译时和部署时处理 -- 软件工具能处理注解信息生成代码.XML文件等等. 运行时处理 -- 一些注解可用在运行时检查. 1     注解基础 1.1    注解的格式 最简单的注解形式如下所示: @Entity 在符号字符(@)告诉编译器这是一个注解.在下面的例子中,注解的名称是Override: @Override voi