美文网首页
那些年被迫读过的注解

那些年被迫读过的注解

作者: 负了时光不负卿 | 来源:发表于2017-09-05 18:32 被阅读0次

    1. 引言

    @Override这个注解相信所有Java开发者都不会陌生,当然,如果你真的不熟悉,那么恭喜你,提前知道了有这么一个亲戚,他以后就会成为你开发中不时的来串门。向这样的注解是Java已经定义好的,除此以外,还有@Deprecated、@SuppressWarning,这些统称为Java的内建注解。

    • Override 重写父类的某个方法。
    • Deprecated 用于声明被注解的这个元素已经被废弃了,可能是不安全,或者是有更好的替代方案等等。
    • SuppressWarning 移除被注解的这个元素产生的警告

    2. Java中定义的注解

    1. 上面看到的都是Java已经创建好的注解,我们点进去看看这个注解到底是怎么写的,我点!!
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Override {
    }
    

    没错,正如你看到的一样,声明一个注解就是这么的简单,代码精简的一塌糊涂,看上去那么高大上的东西不过也就是这样嘛。虽然,代码量不多,但是我们也是需要好好的琢磨一下,看看它到底是个什么样的套路,以后好写出我们自己的注解。

    • @interface 这个是声明注解的关键字,就如class声明一个类,interface声明一个接口一样。

    • @Targer @Retention 这些是元注解,什么鬼? 其实,注解也是需要注解来做进一步的说明的,简单的来说就是,元注解的作用就是修饰自定义注解的。

    元注解处于这样东西之外还有@Decumented、@Inherited、@Target、@Retention。这些用户修饰的元注解的作用都是直白。

    • @Docemented: 这个注解用户在生成doc文档时使用,对代码的编写没有实质性的影响,可要可不要。

    • @Inherited: 被修饰的注解在使用上,是否因为类的继承而继承。

    • @Target: 说明该注解作用的位置,可以是类上、属性、方法上...

      • TYPE : 用于修饰类
      • FIELD : 用于修饰字段
      • METHOD : 用于修饰方法
      • PARAMETER : 用户修饰参数
      • CONSTRUCTOR : 用于修饰构造器
      • LOCAL_VARIABLE : 用于修饰本地变量
      • ANNOTATION_TYPE : 用于修饰注解
      • PACKAGE : 用于修饰包的
      • TYPE_PARAMETER : 表示这个 Annotation 可以用在 Type 的声明式前
      • TYPE_USE : 表示这个 Annotation 可以用在所有使用 Type 的地方(如:泛型,类型转换等)
    • @Retention: 指定注解的作用域

      • SOURCE : 注解只保留在源文件中
      • CLASS :注解保留在class文件中,在加载到JVM虚拟机时丢弃
      • RUNTIME : 注解保留在程序运行期间,此时可以通过反射获得定义在某个类上的所有注解。

    3. 自定义注解

    接下来我们开始着手自己写注解点,是不是有点小激动。根据注解的作用域来分我们自定义注解就存在三种情况,我们一一来突破。

    3.1 运行时期的注解(RetentionPolicy.RUNTIME)

    这次我们需要创建一个@netinfo的注解,用于处理网络请求Url的拼接。
    比如说我们需要一个类似如:http://www.baidu.com?age=xx&name=xxxx 的Get网络请求,不同的网络地址,不同的键值对,我们可能通过像getNetInfo(String url, Map<String,Object> content)的方式,将内容手动的put到Map中,这样的代码就没有任何的封装性,但是如果通过注解的方式,情况就不一样了,形如:

    @netinfo(url = "http://www.baidu.com")
    public class TestParam {
        public String name;
        public int age;
    }
    

    每一种请求体都封装成一个JavaBean对象,使用时,如下:

    RequestParam request = new RequestParam();
    request.name = "yanglang";
    request.age = 23;
    String url = NetInfoFactory.getNetRequest(request);
    

    通过创建一个RequestParam实例,并给相应的字段赋值之后,传入NetInfoFactory.getNetRequest()处理后,就变成了一个完整的Url地址了。接下来,我们看看NetInfoFactory处理逻辑,主要是通过反射拿到所需的内容。

    public class NetInfoFactory {
    
        public static String getNetRequest(Object target) {
            Class<?> cls = target.getClass();  //获取对象的字节码
            String url = null;
            if (cls.isAnnotationPresent(netinfo.class)) { //判断该类是否被netinfo注解
                netinfo netinfo = cls.getAnnotation(netinfo.class);//获取该类上面的注解对象
                url = netinfo.url(); //通过注解对象获取其中的值
            }
            //获取键值对,(字段名 : 字段值)
            Field[] fields = cls.getDeclaredFields(); //获取该类的所有属性
            Map<String, Object> keyValue = new HashMap<>();
            for (Field field : fields) {
                try {
                    field.setAccessible(true); //修改字段的访问权限
                    String key = field.getName(); //获取字段的名称,作为键
                    Object value = field.get(target);//获取字段的值
                    keyValue.put(key, value);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
            if (url == null && url.equals("")) {
                throw new IllegalArgumentException("url is not empty");
            }
            if (keyValue.size() > 0) {
                StringBuilder stringBuilder = new StringBuilder(url);
                stringBuilder.append("?");
                for (Map.Entry<String, Object> c : keyValue.entrySet()) {
                    stringBuilder.append(c.getKey()).append("=").append(c.getValue()).append("&");
                }
                url = stringBuilder.deleteCharAt(stringBuilder.length() - 1).toString();
            }
            return url;
        }
    }
    

    总结:运行时注解,无非就是自定义注解后,在使用时,用反射的方式,将注解中的值拿出来使用。

    3.2讲源码期注解RetentionPolicy.SOURCE)和编译期注解RetentionPolicy.CLASS)前的准备。

    前面说的是运行时注解(RetentionPolicy.RUNTIME),程序到了运行阶段了,代码中逻辑都已经确定下来了,不能改变代码的任何结构,一切都按照设计进行,此时的注解相当于某些数据的存储而已。但是源码期注解和编译期注解就不一样了,都是在生成程序之前,一切都还来的及改变,所以这两种注解不只是取值那么简单,它能改变代码的结构。既然这样,根据注解的内容进行代码逻辑的控制语句就不可能像反射那样直接写在代码中,而是需要写在注解处理器中,并且该处理器还需要被注册。

    什么是注解处理器?
    注解处理器其实也是一个Java类,不过该类需要继承一个抽象类AbstractProcessor。我们看一个最简单的注解处理器。

    /**
     * Created by YangLang on 2017/8/6.
     */
    
    public class SimpleAnnotationProcessor extends AbstractProcessor {
    
        /**
         * 指定该注解最低需要基于的Java版本
         * @return
         */
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return super.getSupportedSourceVersion();
        }
    
        /**
         * 指定该注解处理器需要处理的注解有哪类,并存入集合中
         * @return
         */
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> set = new HashSet<>();
            set.add(Override.class.getCanonicalName());
            return set;
        }
    
        /**
         * 注解处理前的初始化
         * @param processingEnvironment 处理注解前提供的环境变量,里面有很丰富的工具类
         *                              
         *  Element的概念: 
         *  一个Java文件中有包名、类名、变量名、方法等,而注解可以修饰在包名,类名...上面
         *  如果一个类被注解修饰了,在注解处理器中,这个类就成了TypeElement
         */
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            Elements elementUtils = processingEnvironment.getElementUtils(); //元素处理工具类
            Types typeUtils = processingEnvironment.getTypeUtils(); //类型处理工具类
            Messager messager = processingEnvironment.getMessager(); //信息处理工具类,信息的打印
            Filer filer = processingEnvironment.getFiler(); //文件处理工具类,自动生成java文件
            super.init(processingEnvironment);
        }
    
        /**
         * 开始处理注解
         * @param set  上面指定需要处理的注解类集合
         * @param roundEnvironment 存在于注解类集合相关的数据
         * @return
         */
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            //被指定注解修改过的元素
            Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(Override.class);
            return false;
        }
    }
    
    

    实践才是检验真理的唯一标准,接下来我们开始搭建我们的工程,工程结构如下,其中Annotationlib和processorlib都是javalib,分别用于存在自定的注解和注解处理器。processorlib依赖annotationlib为了能使用注解类,而app需要依赖annotationlib和processorlib。

    1.PNG

    接下来讲如何注册注解处理器
    为了更加方便的注册注解器,Google提供了一个AutoService的依赖库,只需要在processorlib的gradle中添加依赖: compile 'com.google.auto.service:auto-service:1.0-rc3'
    并在该类上使用依赖,代码如下:

    @AutoService(Processor.class) // add
    public class SimpleAnnotationProcessor extends AbstractProcessor {
     ...
     }
    
    3.3 编译时注解(RetentionPolicy.SOURCE)

    在编译时,我们可以利用注解指定某字段必须是String类型的,否则编译不通过。

    自定义注解
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * Created by YangLang on 2017/8/6.
     * declare this file must be string
     */
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface StringAnnotation {
    }
    
    注解处理器 + 注册
    @AutoService(Processor.class)
    public class StringAnnotationProcessor extends AbstractProcessor {
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.RELEASE_7; // must be 1.7
        }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> mElementType = new HashSet<>();
            mElementType.add(StringAnnotation.class.getCanonicalName());
            return mElementType;
        }
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(StringAnnotation.class);
            System.out.println("++++++++++++++++++++++++");
            for (Element element : elements) {
                System.out.println(element.asType());
                if (!element.asType().toString().equals("java.lang.String")) {
                    throw new IllegalArgumentException(element.getSimpleName() + " is not String type");
                }
            }
            System.out.println("++++++++++++++++++++++++");
            return true;
        }
    }
    
    使用
    public class MainActivity extends AppCompatActivity {
    
        private static final String TAG = "MainActivity";
    
        @StringAnnotation
        private String name;
    
        @StringAnnotation
        public int age; //此处编译不通过,不是String类型的字段
        
        ...
    }
    
    3.4编译期注解RetentionPolicy.CLASS)

    在Java代码的编写中,有时候会出现模板式的代码,我们可以依赖通过JavaPoet来生成java代码,注解可以为生成的代码提供数据。

    自定义注解
    /**
     * Created by YangLang on 2017/8/6.
     */
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.CLASS)
    public @interface MakeFileAnnotation {
         String packName();
         String name();
         String methodName();
    }
    
    自定义注解处理器 + 注册
    package com.example;
    
    import com.example.NetInfoUtils.MakeFileAnnotation;
    import com.google.auto.service.AutoService;
    import com.squareup.javapoet.JavaFile;
    import com.squareup.javapoet.MethodSpec;
    import com.squareup.javapoet.TypeSpec;
    
    import java.io.IOException;
    import java.util.HashSet;
    import java.util.Set;
    
    import javax.annotation.processing.AbstractProcessor;
    import javax.annotation.processing.Filer;
    import javax.annotation.processing.ProcessingEnvironment;
    import javax.annotation.processing.Processor;
    import javax.annotation.processing.RoundEnvironment;
    import javax.lang.model.SourceVersion;
    import javax.lang.model.element.Element;
    import javax.lang.model.element.Modifier;
    import javax.lang.model.element.TypeElement;
    
    /**
     * Created by YangLang on 2017/8/6.
     */
    
    @AutoService(Processor.class)
    public class FileMakeProcessor extends AbstractProcessor {
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> set = new HashSet<>();
            set.add(MakeFileAnnotation.class.getCanonicalName());
            return set;
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return super.getSupportedSourceVersion();
        }
    
        private Filer filer;
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            filer = processingEnvironment.getFiler();
            super.init(processingEnvironment);
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(MakeFileAnnotation.class);
            for (Element element : elementsAnnotatedWith) {
                MakeFileAnnotation annotation = element.getAnnotation(MakeFileAnnotation.class);
                String name = annotation.name();
                String methodName = annotation.methodName();
                String packageName = annotation.packName();
    
                MethodSpec methodSpec = MethodSpec.methodBuilder(methodName)
                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                        .addParameter(String.class, "content")
                        .returns(void.class)
                        .addStatement("$T.out.println(content);", System.class)
                        .build();
    
                TypeSpec typeSpec = TypeSpec.classBuilder(name)
                        .addMethod(methodSpec)
                        .build();
    
                JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
                        .addFileComment("this code is auto produce,do not to modifier")
                        .build();
    
                try {
                    javaFile.writeTo(filer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return true;
        }
    }
    
    
    使用
    @MakeFileAnnotation(packName = "abc",name = "PrintUtils",methodName = "say")
    public class MainActivity extends AppCompatActivity {
        ...
    }
    

    3.5 使用注解需要注意的地方: 我们主工程中直接依赖这个注解处理库,这样的做法是不理想的,因为注解处理库只是在我们生成程序前时候,到了编译的时候这个文件都没有任何的存在意义,所以我们可以使用apt(annotation processor tool),apt的引入流程如下:

    step 1: 在主工程中build.gradle中加入apt工具的远程仓库地址:

    buildscript {
        repositories {
            jcenter()
            mavenCentral()  // add
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:2.3.3'
            classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'  // add
        }
    }
    

    step 2: 在app下面的build.gradle做如下修改:

    apply plugin: 'com.android.application'
    apply plugin: 'com.neenbedankt.android-apt' // add
    
        ...
    
    dependencies {
        
        ...
        
        //compile project(':processorlib')
        apt project(':processorlib')
    }
    

    相关文章

      网友评论

          本文标题:那些年被迫读过的注解

          本文链接:https://www.haomeiwen.com/subject/svkpjxtx.html