搜索
首页Javajava教程Android(Java)代码手动实现依赖注入--技术详解

Android(Java)代码生成技术中的依赖注入,相信大家在平常的开发中这个词没少听说过吧,比如做安卓开发的,使用的Butterknife、Greendao等等第三方库,都是使用的一种叫做编译期代码即时生成的技术,然后我们可以利用编译生成的类来辅助我们的开发,减少我们的工作量,这个技术听上去感觉挺高大上的,编译期间代码生成,这该怎么做到啊,好像从来没有从哪听说编译还能生成代码的,下面让我们来看看这门神奇的技术!

编译期代码生成原理

首先在这之前,我们可能或多或少了解到一个叫JavaPoet的技术,首先我们从它开始,我们来到它的源码,你会发现它好像并没有做什么高深的事情,总共才十几个类,而且大多数只是做了一些类的封装,提供一些接口方便使用,然后还提供了一个工具类供我们使用,如图,它的源码仅仅只有下面几个类而已

20180724093300756.png这时候就迷了,难道编译期生成代码不是这个库帮我们完成的吗?
准确的说确实不是的,那它的功能是什么呢?它其实只是完成了我们所说的一半的功能,就是代码生成,而且是简单易懂的代码生成,因为它做了封装,对一些常用的代码生成需求基本都提供了相应的方法,大大减少了代码复杂度。那和我们想要的需求来比,少了一个编译期间生成,那这个怎么做呢?

其实要实现编译期间做一些事情,这个工作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
20180724130425777.png然后在module对应的build.gradle下,加入下面两个依赖

implementation &#39;com.squareup:javapoet:1.11.1&#39;implementation &#39;com.google.auto.service:auto-service:1.0-rc4&#39;

第一个依赖是javaPoet的依赖,第二个是Google开源库auto的依赖
 上面提到让jvm加载我们自己的注解处理器有两种方式,这里我们先试一下用谷歌的这个开源库

这个module用来编写我们的注解处理器的实现类,先放着,待会再写。
继续新建一个java module,我这里取名为libannotation,然后在里面新建一个自定义注解,内容如下

@Retention(RetentionPolicy.CLASS)@Target(ElementType.TYPE)public @interface HelloAnnotation {}

@Retention(RetentionPolicy.CLASS)表示我们定义的这个注解会留存在class字节码文件中 ,但是在运行时是没有的,@Target(ElementType.TYPE) 表示我们的这个注解作用对象是类,然后我们看下整个目录的样子,
2018072414402081.png

TestGenerator是一个我写的测试JavaPoet的测试类,可以忽略

现在我们开始写代码,首先在javapoet的build.gradle中加入libannotation  module的依赖,如下

implementation project(path: &#39;:libannotation&#39;)

然后在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(&#39;:javapoet&#39;)implementation project(&#39;:libannotation&#39;)

然后我们手动编译一下项目,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客户端

视频:Java视频教程_免费Java教程在线学习

以上是Android(Java)代码手动实现依赖注入--技术详解的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
为什么Java是开发跨平台桌面应用程序的流行选择?为什么Java是开发跨平台桌面应用程序的流行选择?Apr 25, 2025 am 12:23 AM

javaispopularforcross-platformdesktopapplicationsduetoits“ writeonce,runanywhere”哲学。1)itusesbytbytybytecebytecodethatrunsonanyjvm-platform.2)librarieslikeslikeslikeswingingandjavafxhelpcreatenative-lookingenative-lookinguisis.3)

讨论可能需要在Java中编写平台特定代码的情况。讨论可能需要在Java中编写平台特定代码的情况。Apr 25, 2025 am 12:22 AM

在Java中编写平台特定代码的原因包括访问特定操作系统功能、与特定硬件交互和优化性能。1)使用JNA或JNI访问Windows注册表;2)通过JNI与Linux特定硬件驱动程序交互;3)通过JNI使用Metal优化macOS上的游戏性能。尽管如此,编写平台特定代码会影响代码的可移植性、增加复杂性、可能带来性能开销和安全风险。

与平台独立性相关的Java开发的未来趋势是什么?与平台独立性相关的Java开发的未来趋势是什么?Apr 25, 2025 am 12:12 AM

Java将通过云原生应用、多平台部署和跨语言互操作进一步提升平台独立性。1)云原生应用将使用GraalVM和Quarkus提升启动速度。2)Java将扩展到嵌入式设备、移动设备和量子计算机。3)通过GraalVM,Java将与Python、JavaScript等语言无缝集成,增强跨语言互操作性。

Java的强键入如何有助于平台独立性?Java的强键入如何有助于平台独立性?Apr 25, 2025 am 12:11 AM

Java的强类型系统通过类型安全、统一的类型转换和多态性确保了平台独立性。1)类型安全在编译时进行类型检查,避免运行时错误;2)统一的类型转换规则在所有平台上一致;3)多态性和接口机制使代码在不同平台上行为一致。

说明Java本机界面(JNI)如何损害平台独立性。说明Java本机界面(JNI)如何损害平台独立性。Apr 25, 2025 am 12:07 AM

JNI会破坏Java的平台独立性。1)JNI需要特定平台的本地库,2)本地代码需在目标平台编译和链接,3)不同版本的操作系统或JVM可能需要不同的本地库版本,4)本地代码可能引入安全漏洞或导致程序崩溃。

是否有任何威胁或增强Java平台独立性的新兴技术?是否有任何威胁或增强Java平台独立性的新兴技术?Apr 24, 2025 am 12:11 AM

新兴技术对Java的平台独立性既有威胁也有增强。1)云计算和容器化技术如Docker增强了Java的平台独立性,但需要优化以适应不同云环境。2)WebAssembly通过GraalVM编译Java代码,扩展了其平台独立性,但需与其他语言竞争性能。

JVM的实现是什么,它们都提供了相同的平台独立性?JVM的实现是什么,它们都提供了相同的平台独立性?Apr 24, 2025 am 12:10 AM

不同JVM实现都能提供平台独立性,但表现略有不同。1.OracleHotSpot和OpenJDKJVM在平台独立性上表现相似,但OpenJDK可能需额外配置。2.IBMJ9JVM在特定操作系统上表现优化。3.GraalVM支持多语言,需额外配置。4.AzulZingJVM需特定平台调整。

平台独立性如何降低发展成本和时间?平台独立性如何降低发展成本和时间?Apr 24, 2025 am 12:08 AM

平台独立性通过在多种操作系统上运行同一套代码,降低开发成本和缩短开发时间。具体表现为:1.减少开发时间,只需维护一套代码;2.降低维护成本,统一测试流程;3.快速迭代和团队协作,简化部署过程。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

SublimeText3 英文版

SublimeText3 英文版

推荐:为Win版本,支持代码提示!

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器