• 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

  • 相关阅读:
    字符编码笔记:ASCII,Unicode 和 UTF-8
    nginx 负载均衡设置
    ubuntu 修改时区
    js 高阶函数 filter
    js 高阶函数 map reduce
    省市联级菜单--js+html
    php代码优化技巧
    json、xml ---- 数据格式生成类
    初识设计模式(1)---单例、工厂、注册树
    php 链式操作的实现 学习记录
  • 原文地址:https://www.cnblogs.com/deman/p/5519901.html
Copyright © 2020-2023  润新知