Android(Java)代码手动实现依赖注入--技术详解
编译期代码生成原理
首先在这之前,我们可能或多或少了解到一个叫JavaPoet的技术,首先我们从它开始,我们来到它的源码,你会发现它好像并没有做什么高深的事情,总共才十几个类,而且大多数只是做了一些类的封装,提供一些接口方便使用,然后还提供了一个工具类供我们使用,如图,它的源码仅仅只有下面几个类而已
这时候就迷了,难道编译期生成代码不是这个库帮我们完成的吗?
准确的说确实不是的,那它的功能是什么呢?它其实只是完成了我们所说的一半的功能,就是代码生成,而且是简单易懂的代码生成,因为它做了封装,对一些常用的代码生成需求基本都提供了相应的方法,大大减少了代码复杂度。那和我们想要的需求来比,少了一个编译期间生成,那这个怎么做呢?
其实要实现编译期间做一些事情,这个工作Java已经帮我们做好了,我们先来看一个类AbstractProcessor
,这个类可能平常不会用到,但是也没关系,我们简单了解一下它,首先你可以把它理解为一个抽象的注解处理器,它位于javax.annotation.processing.AbstractProcessor;
下面,是用于在编译时扫描和处理注解的类,我们要实现类似ButterKnife这样的功能,首先定义一个自己的注解处理器,然后继承它即可,如下
public class MyProcessor extends AbstractProcessor{ @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { return false; } }
可以看到我们需要实现一个叫process的抽象方法,这个方法里的内容就会在编译期间执行。
然后,怎么让jvm在编译期间调用我们自己写的这个注解处理器呢,有一个快捷办法就是使用谷歌的开源库auto,然后使用它提供的AutoService注解来实现,另外一种办法就是自己手动去创建指定的文件夹,然后配置我们的注解处理器的路径。
做完上述工作后,在编译时,jvm就会扫描到所有的AbstractProcessor
的实现类,这里也就是MyProcessor
,然后调用实现实现类的process方法,执行相应的操作,然后我们生成代码的工作就可以写在process这里,然后具体的生成代码的方法再借助JavaPoet工具来简化操作。
因为我们生成代码的工具是在编译期间执行的,最后生成的java代码会和普通的java代码一起编译,当做普通的类来使用,所以不会影响到最后程序运行时的性能,最多只是编译速度慢了点,因为要生成额外的类和代码。
至此我们知道了怎么在编译期间生成代码,基本的实现思路也有了,不过还有一个问题存在。
怎么使用注解处理器来处理一个注解,从而实现类似@BindView的效果呢?首先我们当然要自定义一个注解,具体自定义注解不了解的可以去学下,挺简单的,内容不多,由于不是本章重点,所以不作过多说明,当然也可以就在文末的demo链接里下载源码学习,在定义了一个注解之后,我们先看到我们的MyProcessor
注解处理器的 process()
方法的annotations
参数,这个参数就是在编译的时候,用来存储扫描到的所有的非元注解(非元注解也就是自定义注解,元注解一共四个,除了元注解,剩下的就是自定义注解),然后我们遍历这个集合,取到我们自定义注解时,再执行相应的逻辑。具体的代码如下,其中XXXAnnotation就是你的自定义注解的名称
for (TypeElement element : annotations) { if (element.getQualifiedName().toString().equals(XXXAnnotation.class.getCanonicalName())) { //执行你的逻辑 } }
然后我们还需要重写AbstractProcessor
类的getSupportedAnnotationTypes()
方法和getSupportedSourceVersion()
方法,getSupportedAnnotationTypes()
方法用来指定该注解处理器是用来处理哪个注解的,getSupportedSourceVersion()
方法用来指定java版本,一般给值为SourceVersion.latestSupported()
。
完成以上工作后,对于自定义注解作用的对象,编译期间就会自动执行相应process()
里的逻辑,比如生成辅助类,这个辅助类其实就可以理解为依赖类,这个编译期间通过注解生成辅助类的过程,就相当于实现了注入。
到这里,一整个依赖注入的思路和实现方法已经全部打通。
下面我们来动手实现一个小例子吧!
动手实现
首先我们新建一个Android工程,按照默认的配置就好,新建完毕后,会有个默认的app的module,我们暂且不管它,然后直接新建一个java module,新建方式为file -> New -> New Module
然后选择Java Libary,这里给libary取名为javapoet
然后在module对应的build.gradle下,加入下面两个依赖
implementation 'com.squareup:javapoet:1.11.1'implementation 'com.google.auto.service:auto-service:1.0-rc4'
第一个依赖是javaPoet的依赖,第二个是Google开源库auto的依赖
上面提到让jvm加载我们自己的注解处理器有两种方式,这里我们先试一下用谷歌的这个开源库
这个module用来编写我们的注解处理器的实现类,先放着,待会再写。
继续新建一个java module,我这里取名为libannotation,然后在里面新建一个自定义注解,内容如下
@Retention(RetentionPolicy.CLASS)@Target(ElementType.TYPE)public @interface HelloAnnotation {}
@Retention(RetentionPolicy.CLASS)
表示我们定义的这个注解会留存在class字节码文件中 ,但是在运行时是没有的,@Target(ElementType.TYPE)
表示我们的这个注解作用对象是类,然后我们看下整个目录的样子,
TestGenerator是一个我写的测试JavaPoet的测试类,可以忽略
现在我们开始写代码,首先在javapoet的build.gradle中加入libannotation module的依赖,如下
implementation project(path: ':libannotation')
然后在javapoet module中新建HelloProcessor类,用AutoService
标注它,然后继承AbstractProcessor
方法,重写相应的方法,具体怎么写,原理里解释的比较详细,然后注释里我作了详细说明,如下
import com.aiiage.libannotation.HelloAnnotation;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.Collections;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.Modifier;import javax.lang.model.element.TypeElement;/** * Created By HuangQing on 2018/7/20 15:38 **/@AutoService(Processor.class)public class HelloProcessor extends AbstractProcessor { private Filer filer; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); filer = processingEnv.getFiler(); // 获得filer对象,用来创建文件 } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (TypeElement element : annotations) {//遍历扫描到的注解集合 if (element.getQualifiedName().toString().equals(HelloAnnotation.class.getCanonicalName())) { // 当前注解是我们自定义的注解,也就是HelloAnnotation时,执行下列代码 TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")//声明类名为HelloWorld .addModifiers(Modifier.PUBLIC, Modifier.FINAL)//声明类的修饰符为 public final .addMethod(getMethodSpec("hello1", "Hello"))//为HelloWorld类添加名为hello1的方法,返回值为Hello .addMethod(getMethodSpec("hello2", "Java"))//同上 .addMethod(getMethodSpec("hello3", "Poet!"))//同上 .build(); try { // 建立 com.aiiage.testjavapoet.HelloWorld.java 对象 JavaFile javaFile = JavaFile.builder("com.aiiage.testjavapoet", helloWorld) .addFileComment(" This codes are generated automatically. Do not modify!") .build(); // 写入文件 javaFile.writeTo(filer); } catch (IOException e) { e.printStackTrace(); } } } return true; } /** * @param methodStr 方法名 * @param returnStr 返回值 * @return */ private static MethodSpec getMethodSpec(String methodStr, String returnStr) { return MethodSpec.methodBuilder(methodStr) .addModifiers(Modifier.PUBLIC, Modifier.STATIC)//指定方法修饰符为 public static .returns(String.class) //指定返回值为String类型 .addStatement("return $S", returnStr) //拼接返回值语句 .build(); } @Override public Set<String> getSupportedAnnotationTypes() { return Collections.singleton(HelloAnnotation.class.getCanonicalName()); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } }
上述代码中,一定不要忘了加AutoService的注解,通过这个注解,我们的HelloProcessor注解处理器相当于执行了一个注册的过程,这样才会被jvm在编译时加载,代码生成部分最终生成的文件代码长下面这个样子,可以结合注释对着体会下,还是很方便的,有了javapoet之后
import java.lang.String;public final class HelloWorld { public static String hello1() { return "Hello"; } public static String hello2() { return "Java"; } public static String hello3() { return "Poet!"; } }
ok,代码方面准备完毕。
接下来我们为app module添加依赖,如下
//必须使用annotationProcessor,而不是implementation//annotationProcessor修饰的,最终不会打包到apk中,可理解为在编译时执行的annotationProcessor project(':javapoet')implementation project(':libannotation')
然后我们手动编译一下项目,build -> make project
,然后我们来到熟悉的MainActivity,首先使用我们的自定义注解标注MainActivity,要标在class上面,因为我们自定义的注解作用范围是Type,然后用一个TextView简单显示一下注入的HelloWorld类的几个静态方法,如下
@HelloAnnotationpublic class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tv=findViewById(R.id.tv); String str1=""; str1+=HelloWorld.hello1()+" "; str1+=HelloWorld.hello2()+" "; str1+=HelloWorld.hello3(); tv.setText(str1); } }
编译运行,看到Hello Java Poet字样即表示成功!
然后我们上面提到,除了使用Google开源库auto实现注册注解处理器外,还可以使用手动配置的方式,手动配置的方式如下
在自定义注解处理器的module中,这里也就是javapoet的module中,在main目录下创建出resources目录,然后在resources目录下创建出META-INF目录,然后在META-INF目录下创建出services目录,然后在services目录下创建一个名为javax.annotation.processing.Processor
的文件,在该文件中声明我们的注解处理器:
com.aiiage.javapoet.HelloProcessor;
结语
这样我们就完成了一个最简单的依赖注入的实现,掌握其原理后,我们就不难明白类似ButterKnife、GreenDao是怎么实现的了,不过我们这个只是一个简单的功能,不得不感叹要生成那么多的代码,这得是一个多么心细和考验耐心的活啊!
相关推荐:
php生成Android客户端扫描可登录的二维码,android客户端
以上就是Android(Java)代码手动实现依赖注入--技术详解的详细内容,更多请关注其它相关文章!
上一篇: java定时器实例
下一篇: 解析Java中关于接口的作用