美文网首页android dev每天写1000字程序员
[78→100]编译时Annotation的处理流程

[78→100]编译时Annotation的处理流程

作者: 沉思的Panda | 来源:发表于2016-07-13 17:17 被阅读527次

    神奇的说明——Java Annotation(注解)中介绍了如何在运行时透过反射的方式获取注解信息。
    那么编译时如何获取注解信息呢?
    其实,编译时 Annotation 指 @Retention 为 CLASS 的 Annotation,是由 APT(Annotation Processing Tool) 自动解析的。APT在编译时根据resources资源文件夹下的META-INF/services/javax.annotation.processing.Processor自动查找所有继承自 AbstractProcessor 的类,然后调用他们的 process 方法去处理。

    核心步骤如下:

    1. 新建两个注解PrintMePrintMe2
    package panda.annotation;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    @Retention(RetentionPolicy.SOURCE)
    public @interface PrintMe {
    }
    
    package panda.annotation;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    @Retention(RetentionPolicy.SOURCE)
    public @interface PrintMe2 {
    }
    
    1. 新建编译时处理类MyProcessor
    package panda.annotation;
    import java.util.Set;
    import javax.annotation.processing.AbstractProcessor;
    import javax.annotation.processing.Messager;
    import javax.annotation.processing.RoundEnvironment;
    import javax.annotation.processing.SupportedAnnotationTypes;
    import javax.lang.model.SourceVersion;
    import javax.lang.model.element.Element;
    import javax.lang.model.element.TypeElement;
    import javax.tools.Diagnostic;
    @SupportedAnnotationTypes({"panda.annotation.PrintMe","panda.annotation.PrintMe2"})
    public class MyProcessor extends AbstractProcessor {
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
            Messager messager = processingEnv.getMessager();
            for (TypeElement te : annotations) {
                messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + te);
                if (te instanceof PrintMe){
                    for (Element e : env.getElementsAnnotatedWith(te)) {
                        messager.printMessage(Diagnostic.Kind.NOTE, "\t**Printing: " + e.toString());
                    }
                }
                else{
                    for (Element e : env.getElementsAnnotatedWith(te)) {
                        messager.printMessage(Diagnostic.Kind.NOTE, "\t--Printing: " + e.toString());
                    }
                }
    
            }
            return true;
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    }
    
    1. 为了这个处理器在编译时被调用,需要在META-INF中显示标识。在resources资源文件夹下新建META-INF/services/
      javax.annotation.processing.Processor,把MyProcessor放在里面
    panda.annotation.MyProcessor
    
    1. 新建测试类,进行注解测试
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import panda.annotation.PrintMe;
    import panda.annotation.PrintMe2;
    public class MainActivity extends AppCompatActivity {
        @PrintMe
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
        @PrintMe
        @Override
        protected void onStart() {
            super.onStart();
        }
        @PrintMe
        @Override
        protected void onResume() {
            super.onResume();
        }
        @PrintMe2
        @Override
        protected void onPause() {
            super.onPause();
        }
        @PrintMe2
        @Override
        protected void onStop() {
            super.onStop();
        }
        @PrintMe2
        @Override
        protected void onDestroy() {
            super.onDestroy();
        }
    }
    

    编译后,输入的编译日志为

    注: Printing: panda.annotation.PrintMe
    注:  --Printing: onCreate(android.os.Bundle)
    注:  --Printing: onStart()
    注:  --Printing: onResume()
    注: Printing: panda.annotation.PrintMe2
    注:  --Printing: onPause()
    注:  --Printing: onStop()
    注:  --Printing: onDestroy()
    

    解析JsonAnnotation

    JsonAnnotation是一个典型的编译时注解库。

    package kale.net.json.annotation;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @author Jack Tony
     * @date 2015/8/13
     */
    @Documented
    @Retention(RetentionPolicy.CLASS)
    @Target(ElementType.FIELD)
    public @interface Json2Model {
    
        String modelName();
        
        String jsonStr();
    
        // custom the model's package name
        String packageName() default "";
    }
    

    其处理流程为:

    1. 根据注解,获得包名类名json字符串
    2. 类名json字符串 送入 jsonformat 包,获得对应的Model类字符串
    3. 根据包名类名Model类字符串,写入java文件。

    核心代码如下:

    package kale.net.json.processor;
    
    import com.jsonformat.JsonParserHelper;
    
    import java.io.IOException;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.nio.charset.Charset;
    import java.util.Set;
    
    import javax.annotation.processing.AbstractProcessor;
    import javax.annotation.processing.ProcessingEnvironment;
    import javax.annotation.processing.RoundEnvironment;
    import javax.annotation.processing.SupportedAnnotationTypes;
    import javax.annotation.processing.SupportedSourceVersion;
    import javax.lang.model.SourceVersion;
    import javax.lang.model.element.Element;
    import javax.lang.model.element.TypeElement;
    import javax.lang.model.element.VariableElement;
    import javax.lang.model.util.Elements;
    import javax.tools.Diagnostic;
    import javax.tools.JavaFileObject;
    
    import kale.net.json.annotation.Json2Model;
    
    /**
     * @author Jack Tony
     * @date 2015/8/13
     */
    @SupportedAnnotationTypes({"kale.net.json.annotation.Json2Model"})
    @SupportedSourceVersion(SourceVersion.RELEASE_7)
    public class Json2ModelProcessor extends AbstractProcessor {
    
        private static final String TAG = "[ " + Json2Model.class.getSimpleName() + " ]:";
    
        private Elements elementUtils;
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            elementUtils = processingEnv.getElementUtils();
        }
    
        private String packageName;
    
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            for (TypeElement te : annotations) {
                for (Element e : roundEnv.getElementsAnnotatedWith(te)) {
                    log("Working on: " + e.toString());
                    VariableElement varE = (VariableElement) e;
    
                    Json2Model json2Model = e.getAnnotation(Json2Model.class);
                    if (json2Model.packageName().equals("")) {
                        // no custom package name
                        /**
                         *  example:
                         *  String GET_USER_INFO = "create/info/user/info"; 
                         *  result:create/info/user/info
                         */
                        if (varE.getConstantValue() == null) {
                            fatalError("jsonStr couldn't be final");
                        }
                        String url = varE.getConstantValue().toString();
                        packageName = url2packageName(url);
                    } else {
                        // has custom package name
                        packageName = json2Model.packageName();
                    }
                    if (json2Model.jsonStr() == null || json2Model.jsonStr().equals("")) {
                        fatalError("json string is null");
                    }
    
                    final String clsName = json2Model.modelName();
    
                    JsonParserHelper helper = new JsonParserHelper();
                    helper.parse(json2Model.jsonStr(), clsName, new JsonParserHelper.ParseListener() {
                        public void onParseComplete(String str) {
                            createModelClass(packageName, clsName, "package " + packageName + ";\n" + str);
                        }
    
                        public void onParseError(Exception e) {
                            e.printStackTrace();
                            fatalError(e.getMessage());
                        }
                    });
                    log("Complete on: " + e.toString());
                }
            }
            return true;
        }
    
        private void createModelClass(String packageName, String clsName, String content) {
            //PackageElement pkgElement = elementUtils.getPackageElement("");
            TypeElement pkgElement = elementUtils.getTypeElement(packageName);
    
            OutputStreamWriter osw = null;
            try {
                // create a model file
                JavaFileObject fileObject = processingEnv.getFiler().createSourceFile(packageName + "." + clsName, pkgElement);
                OutputStream os = fileObject.openOutputStream();
                osw = new OutputStreamWriter(os, Charset.forName("UTF-8"));
                osw.write(content, 0, content.length());
    
            } catch (IOException e) {
                e.printStackTrace();
                fatalError(e.getMessage());
            } finally {
                try {
                    if (osw != null) {
                        osw.flush();
                        osw.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    fatalError(e.getMessage());
                }
            }
        }
    
        private void log(String msg) {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, TAG + msg);
        }
    
        private void fatalError(String msg) {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, TAG + " FATAL ERROR: " + msg);
        }
    
        /**
         * /user/test/ - > user.test
         */
        public static String url2packageName(String url) {
            String packageName = url.replaceAll("/", ".");
            if (packageName.startsWith(".")) {
                packageName = packageName.substring(1);
            }
            if (packageName.substring(packageName.length() - 1).equals(".")) {
                packageName = packageName.substring(0, packageName.length() - 1);
            }
            return packageName;
        }
    
    }
    

    思考

    编译时注解可以将一些固定的代码隐藏起来,只保留核心逻辑,最典型的应用是butterknife
    透过注解BindView把冗余常见的findViewById替换掉了,最后的代码结构简单清晰,便于理解。

    class ExampleActivity extends Activity {
      @BindView(R.id.title) TextView title;
      @BindView(R.id.subtitle) TextView subtitle;
      @BindView(R.id.footer) TextView footer;
    
      @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.simple_activity);
        ButterKnife.bind(this);
        // TODO Use fields...
      }
    }
    

    而json字符串放在java里面,并不是一个可读性很强的结构,以后字段增减看起来也不直观。

         String simpleStr = "{\n"
                + "    \"id\": 100,\n"
                + "    \"body\": \"It is my post\",\n"
                + "    \"number\": 0.13,\n"
                + "    \"created_at\": \"2014-05-22 19:12:38\"\n"
                + "}";
        // 简单格式
        @Json2Model(modelName = "Simple", jsonStr = simpleStr)
        String TEST_SIMPLE = "test/simple"; // api url
    

    所以这时候采用编译时注解并不太合适。还是提取一个jar包,批量将json转成model,以后的增减由GsonFormat单个操作吧。

    参考

    1. Java 注解
    2. Annotation实战【自定义AbstractProcessor】
    3. Java Annotation 及几个常用开源项目注解原理简析

    Panda
    2016-07-13

    相关文章

      网友评论

        本文标题:[78→100]编译时Annotation的处理流程

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