android APT 之IOC功能

          上章AOP章节讲到了APT功能,这里我们来详解下APT功能

          APT(Annotation Processing Tool)即注解处理器,注解处理器 Java5 中叫APT(Annotation Processing Tool),在Java6开始,规范化为 Pluggable Annotation Processing。Apt应该是这其中我们最常见到的了,难度也最低。定义编译期的注解,再通过继承Proccesor实现代码生成逻辑,实现了编译期生成代码的逻辑。

          上个项目里之所以做了IOC注入Bean对象这个功能,是为了解决,在安卓开发中,关于activity、业务、ui之间的架构问题。目前架构有MVC 、MVP 、MVVM种种,都很不错,但是这种框架,无疑会让工程变得无比臃肿,接口满天飞,特别不理解,ui层也套接口,还一个控件,一个接口,界面一旦调整,改起来那是想死的心都有了。所以其实没必要为了接口而接口,为了框架而框架,有的人说当项目大了,它的优势也就来了,一个小项目代码都那么臃肿了,大项目的业务何其复杂,更加不适合这种更加会让项目变得臃肿的结构设计,倒是谷歌官方提供了一种databinding MVVM框架,我觉得这种更为适合处理业务与ui之间的结构问题。

          所以我还是采用了MVC方案,activity负责生命周期调度,component 负责业务处理,ui层负责所有的ui操作。层与层之间通过EventBus之类的消息组件框架进行交互与通讯。由AS插件负责生成三层java、xml相关的代码,插件我们下回在分解。再由本文中所讲的apt ioc功能注入各自的对象。

结构如下:

           言归正传,我们继续详说APT。

创建Java Module Library

build.gradle如下

apply plugin: 'java-library'

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.squareup:javapoet:1.4.0'
    implementation project(':apiannot')//自定义注解库
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

  1、apt-annotation(自定义注解)

  @Bean :  用于标注需要注入的字段

package cn.jesse.apilib.annotation.apt;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;



@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface Bean {
}

   @IBean :  用于标注在实体对象上,默认实体对象可不标注,当实体对象需要IOC构成成单例模式时,可标注此注解,并把

scope = IBean.Scope.Singleton 即可。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface IBean {


    public enum Scope {
        Default,  Singleton, 
    }

    Scope scope() default Scope.Default;//是否单例模式,默认非单例模式
}

APT 这里采用javapoet生产代码, 代码如下:


import com.app.apt.helper.EBeanModel;
import com.app.apt.processor.ApiFactoryProcessor;
import com.app.apt.processor.InstanceProcessor;
import com.app.apt.processor.OkBusAnnotacionProcessor;
import com.app.apt.util.Utils;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.FilerException;
import javax.annotation.processing.Messager;
import javax.annotation.processing.Processor;
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.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;

import cn.jesse.apilib.annotation.apt.Bean;
import cn.jesse.apilib.annotation.apt.IBean;

import static com.squareup.javapoet.TypeSpec.classBuilder;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;

@AutoService(Processor.class)//自动生成 javax.annotation.processing.IProcessor 文件
@SupportedSourceVersion(SourceVersion.RELEASE_8)//java版本支持
@SupportedAnnotationTypes({//标注注解处理器支持的注解类型
        "cn.jesse.apilib.annotation.apt.Bean",
})
public class IOCProcessor extends AbstractProcessor {
    public Filer mFiler; //文件相关的辅助类
    public Elements mElements; //元素相关的辅助类
    public Messager mMessager; //日志相关的辅助类


    private Map<TypeElement, List<Element>> mIOCClassMap = new LinkedHashMap<>();
    private Map<String,Element> SingletonMap = new LinkedHashMap<>();

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        mFiler = roundEnv.getFiler();
        mElements = roundEnv.getElementUtils();
        mMessager = roundEnv.getMessager();


        String CLASS_NAME = "IocFactory";
        TypeSpec.Builder tb = classBuilder(CLASS_NAME).addModifiers(PUBLIC, FINAL).addJavadoc("@  此类由apt自动生成");//类注释



        MethodSpec.Builder methodBuilder1 = MethodSpec.methodBuilder("bind")   // MethodSpec.Builder   创建一个函数,可设置函数名称、注释、修饰符、入参、返回值等。
                .addJavadoc("@此方法由apt自动生成")
                .addModifiers(PUBLIC, STATIC)
                .addParameter(Object.class, "object");


        List<ClassName> mList = new ArrayList<>();


        CodeBlock.Builder blockBuilderIoc = CodeBlock.builder();//创建一段代码块


        blockBuilderIoc.beginControlFlow(" switch (object.getClass().getName())");//括号开始


        List<EBeanModel> meBeanModels = new ArrayList<>();
        try {


            /*处理单例模式*/
            processSingleton(roundEnv, tb);


            /**
             * 获得 Bean 注解集合
             */
            queryBeanAnnot(roundEnv, meBeanModels);



            //处理所有需要IOC注入的类
            for (TypeElement classElementAnnotationIn : mIOCClassMap.keySet()) {

                // 全限定名称
                ClassName currentType = ClassName.get(classElementAnnotationIn);

                //类名
                String clazzName = currentType.simpleName().toLowerCase();


                final List<Element> list = mIOCClassMap.get(classElementAnnotationIn);


                blockBuilderIoc.add("case $S: \n", currentType);//

                blockBuilderIoc.add("{"+currentType +"  "+clazzName+"=("+currentType+")object   ;");
                for (Element methodElement : list) {

                    String singName=getSingName(methodElement.asType().toString());

                    if(SingletonMap.get(singName)!=null){//单例模式
                        blockBuilderIoc.add(clazzName+"." + methodElement.getSimpleName() + "= " +singName+ ";");
                    }else{//构造新对象
                        blockBuilderIoc.add(clazzName+"." + methodElement.getSimpleName() + "=new " + methodElement.asType().toString() + "();");
                    }
                }
                blockBuilderIoc.addStatement("}break");

            }


            blockBuilderIoc.addStatement("default: break");
            blockBuilderIoc.endControlFlow();
            methodBuilder1.addCode(blockBuilderIoc.build());




            tb.addMethod(methodBuilder1.build());//添加函数

            JavaFile javaFile = JavaFile.builder(Utils.PackageName, tb.build()).build();// 生成源代码
            javaFile.writeTo(mFiler);// 在 app module/build/generated/source/包名/ 生成一份源代码
        } catch (FilerException e) {
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return true;
    }


    /**
     * 获得对象中所有标注了 Bean 注解 字段
     *
     * @param roundEnv
     * @param meBeanModels
     */
    private void queryBeanAnnot(RoundEnvironment roundEnv, List<EBeanModel> meBeanModels) {
        for (Element element : roundEnv.getElementsAnnotatedWith(Bean.class)) {

            //  找到注解字段所在类的类名,比如user 字段写在MainActivity中,那么这里返回的TypeElement  就描述了关于 MainActivity的详细信息
            TypeElement classElementAnnotationIn = (TypeElement) element.getEnclosingElement();


            List<Element> methodList = mIOCClassMap.get(classElementAnnotationIn);
            if (methodList == null) {
                methodList = new ArrayList<>();
                mIOCClassMap.put(classElementAnnotationIn, methodList);
            }
            //存储element
            methodList.add(element);


            EBeanModel eBeanModel = new EBeanModel();
            eBeanModel.setElement(classElementAnnotationIn);

            //返回该元素直接包含的子元素,  包含构造函数、函数、字段等。这里取标注有Bean 注解的元素。并记录保存
            for (Element childElement : element.getEnclosedElements()) {
                Bean bean = childElement.getAnnotation(Bean.class);
                if (bean != null) {
                    eBeanModel.getBeanElements().add(childElement);
                }
            }
            meBeanModels.add(eBeanModel);
        }
    }

    /*  处理单例模式
        如果该对象为单例模式时, 则生成静态字段, 并在static代码块里构造此对象。
    *
    * */
    private void processSingleton(RoundEnvironment roundEnv, TypeSpec.Builder tb) {
        StringBuffer staticField=new StringBuffer();
        for (Element element : roundEnv.getElementsAnnotatedWith(IBean.class)) {
            IBean iBean= element.getAnnotation(IBean.class);
            TypeName typeName =  ClassName.get(element.asType());

            if(iBean.scope()==IBean.Scope.Singleton){

                String fieldName=getSingName(element.asType().toString());
                SingletonMap.put(fieldName,element);

                //创建静态字段
                FieldSpec extraField = FieldSpec.builder(ParameterizedTypeName.get(element.asType()), fieldName)  .addModifiers(Modifier.PUBLIC, Modifier.STATIC)  .build();
                tb.addField(extraField);

                //并在静态代码块中,构造此对象
                staticField.append(fieldName).append("=new ").append(typeName+"();\n");
            }
        }
        staticField.append("\r\n");
        tb.addStaticBlock(CodeBlock.builder().add(staticField.toString()).build());
    }

    private String getSingName(String typeName) {
        return typeName.replace(".","_");
    }
}

其中EBeanModel对象


import java.util.ArrayList;
import java.util.List;

import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;


public class EBeanModel {

    TypeElement element;//当前注入对象元素


    List<Element> beanElements = new ArrayList<>();//要注入的Element列表

    public List<Element> getBeanElements() {
        return beanElements;
    }


    public TypeElement getElement() {
        return element;
    }

    public void setElement(TypeElement mElement) {
        this.element = mElement;
    }


}

打完收工,测试代码如下:


public class MainActivity extends AppCompatActivity {


    private static final String TAG = MainActivity.class.getSimpleName();


    @Bean
    public  User user;
    @Bean
    public Book book;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main3);

        IocFactory.bind(this);
       
    }
}

apt  IocFactory代码如下:

import com.app.model.Book;
import java.lang.Object;

/**
 * @  此类由apt自动生成 */
public final class IocFactory {
  public static Book com_app_model_Book;

  static {
    com_app_model_Book=new com.app.model.Book();
    
  }

  /**
   * @此方法由apt自动生成 */
  public static void bind(Object object) {
     switch (object.getClass().getName()) {
      case "com.app.ui.MainActivity": 
      {com.app.ui.MainActivity  mainactivity=(com.app.ui.MainActivity)object   ;mainactivity.user=new com.app.model.User();mainactivity.book= com_app_model_Book;}break;
      default: break;
    }
  }
}

 

关于apt的优缺点,有一篇博文中总结得很好,那我就做一次搬运工吧。

难点:
就apt本身来说没有任何难点可言,难点一在于设计模式和解耦思想的灵活应用,二在与代码生成的繁琐,你可以手动字符串拼接,当然有更高级的玩法用squareup的javapoet,用建造者的模式构建出任何你想要的源代码。
想详细了解可以看官网或这篇博客:
Android 利用 APT 技术在编译期生成代码

 

优点:
它的强大之处无需多言,看代表框架的源码,你可以学到很多新姿势。总的一句话:它可以做任何你不想做的繁杂的工作,它可以帮你写任何你不想重复代码。懒人福利,老司机必备神技,可以提高车速,让你以任何姿势漂移。它可以生成任何源代码供你在任何地方使用,就像剑客的剑,快疾如风,无所不及。

关于apt 详细api使用可以查看 https://www.jianshu.com/p/899063e8452e

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值