Android(Java) 코드 생성 기술의 종속성 주입. 예를 들어 Android를 개발하는 사람들은 Butterknife 및 Greendao와 같은 타사 라이브러리를 사용합니다. 컴파일 중에 코드가 즉시 생성되고 그러면 컴파일되고 생성된 클래스를 사용하여 개발을 지원하고 작업 부하를 줄일 수 있습니다. 예, 코드는 컴파일 중에 생성됩니다. 컴파일로도 코드를 생성할 수 있다는 말은 들어본 적이 없는 것 같습니다. 이 마법 같은 기술을 살펴보겠습니다!
우선 JavaPoet이라는 기술에 대해 어느 정도 알 수 있습니다. , 우리는 소스 코드부터 시작하여 고급 작업을 수행하지 않는 것 같습니다. 총 12개의 클래스만 있으며 대부분은 일부 클래스를 캡슐화하고 쉽게 사용할 수 있는 인터페이스를 제공합니다. , 그리고 그림과 같이 우리가 사용할 수 있는 도구 클래스가 제공됩니다. 해당 소스 코드에는 다음 클래스만 있습니다.
정확히 말하면 그게 아니고 무슨 기능인가요? 실제로는 우리가 말하는
기능의 절반만 완료하며, 캡슐화되어 기본적으로 몇 가지 공통 코드 생성을 제공하므로 간단하고 이해하기 쉬운 코드 생성입니다. 요구 사항에 따라 해당 방법이 개발되어 코드 복잡성이 크게 줄어듭니다. 우리가 원하는 것에 비해 컴파일 중에 생성된 이 하나 적습니다. 어떻게 해야 할까요?
사실 컴파일 중에 몇 가지 작업을 수행해야 합니다. Java는 이미 이 작업을 수행했습니다. 먼저 AbstractProcessor
클래스를 살펴보겠습니다. . 이지만 상관없으니 일단 간단히 살펴보면 javax.annotation에 있는 추상적인
annotation processor으로 이해하면 됩니다. processing.AbstractProcessor; 아래는 컴파일 중에 주석을 스캔하고 처리하는 데 사용되는
public class MyProcessor extends AbstractProcessor{ @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { return false; } }
process라는 추상 메소드를 구현해야 하며 이 메소드의 내용은 컴파일 중에 실행된다는 것을 알 수 있습니다. AbstractProcessor
,这个类可能平常不会用到,但是也没关系,我们简单了解一下它,首先你可以把它理解为一个抽象的注解处理器,它位于javax.annotation.processing.AbstractProcessor;
下面,是用于在编译时扫描和处理注解的类,我们要实现类似ButterKnife这样的功能,首先定义一个自己的注解处理器,然后继承它即可,如下
for (TypeElement element : annotations) { if (element.getQualifiedName().toString().equals(XXXAnnotation.class.getCanonicalName())) { //执行你的逻辑 } }
可以看到我们需要实现一个叫process的抽象方法,这个方法里的内容就会在编译期间执行。
然后,怎么让jvm在编译期间调用我们自己写的这个注解处理器呢,有一个快捷办法就是使用谷歌的开源库auto,然后使用它提供的AutoService注解来实现,另外一种办法就是自己手动去创建指定的文件夹,然后配置我们的注解处理器的路径。
做完上述工作后,在编译时,jvm就会扫描到所有的AbstractProcessor
的实现类,这里也就是MyProcessor
,然后调用实现实现类的process方法,执行相应的操作,然后我们生成代码的工作就可以写在process这里,然后具体的生成代码的方法再借助JavaPoet工具来简化操作。
因为我们生成代码的工具是在编译期间执行的,最后生成的java代码会和普通的java代码一起编译,当做普通的类来使用,所以不会影响到最后程序运行时的性能,最多只是编译速度慢了点,因为要生成额外的类和代码。
至此我们知道了怎么在编译期间生成代码,基本的实现思路也有了,不过还有一个问题存在。
怎么使用注解处理器来处理一个注解,从而实现类似@BindView的效果呢?首先我们当然要自定义一个注解,具体自定义注解不了解的可以去学下,挺简单的,内容不多,由于不是本章重点,所以不作过多说明,当然也可以就在文末的demo链接里下载源码学习,在定义了一个注解之后,我们先看到我们的MyProcessor
注解处理器的 process()
方法的annotations
参数,这个参数就是在编译的时候,用来存储扫描到的所有的非元注解(非元注解也就是自定义注解,元注解一共四个,除了元注解,剩下的就是自定义注解),然后我们遍历这个集合,取到我们自定义注解时,再执行相应的逻辑。具体的代码如下,其中XXXAnnotation就是你的自定义注解的名称
implementation 'com.squareup:javapoet:1.11.1'implementation 'com.google.auto.service:auto-service:1.0-rc4'
然后我们还需要重写AbstractProcessor
类的getSupportedAnnotationTypes()
方法和getSupportedSourceVersion()
方法,getSupportedAnnotationTypes()
方法用来指定该注解处理器是用来处理哪个注解的,getSupportedSourceVersion()
方法用来指定java版本,一般给值为SourceVersion.latestSupported()
그럼 컴파일 중에 작성한 주석 프로세서를 jvm 호출로 만드는 방법은 빠른 방법은 Google의 오픈 소스 라이브러리 auto를 사용한 다음 이를 달성하기 위해 제공되는 AutoService 주석을 사용하는 것입니다. 폴더를 수동으로 만든 다음 주석 프로세서의 경로를 구성합니다.
AbstractProcessor
의 모든 구현 클래스(여기서는 MyProcessor
)를 검색한 다음 프로세스 메서드를 호출합니다. 구현 클래스를 작성하고 해당 작업을 수행합니다. 그러면 프로세스에서 코드 생성 작업을 작성할 수 있으며 JavaPoet 도구를 사용하여 특정 코드 생성 방법을 단순화할 수 있습니다. #🎜🎜#우리의 코드 생성 도구는 #🎜🎜# 컴파일 중에 실행되기 때문에 #🎜🎜# 최종 생성된 Java 코드는 일반 Java 코드와 함께 컴파일되어 일반 클래스로 사용되므로 영향을 미치지 않습니다. 결국, 추가 클래스와 코드를 생성해야 하기 때문에 실행 시 프로그램 성능은 컴파일 속도가 조금 느려질 뿐입니다. #🎜🎜##🎜🎜#지금까지 컴파일 시 코드를 생성하는 방법을 알고 있고 기본적인 구현 아이디어도 가지고 있지만 여전히 문제가 있습니다. #🎜🎜##🎜🎜#@BindView와 유사한 효과를 얻기 위해 주석 프로세서를 사용하여 주석을 처리하는 방법은 무엇입니까? 우선, 물론 특정 사용자 정의 주석을 모르는 경우에는 배울 수 있습니다. 이 장의 초점은 아니기 때문에 내용이 많지 않습니다. 물론, 기사 마지막에 있는 데모 링크에 넣어도 됩니다. 주석을 정의한 후 먼저 주석
을 확인하세요. > MyProcessor
주석 프로세서의 process()
메소드의 매개변수입니다. 이 매개변수는 컴파일 중에 스캔된 모든 비메타 주석을 저장하는 데 사용됩니다(비메타 주석도 사용자 정의 주석입니다). , 메타 주석을 제외하고 총 4개의 메타 주석이 있으며 나머지는 사용자 정의 주석입니다. 그런 다음 이 컬렉션을 순회하고 사용자 정의 주석을 얻으면 해당 논리를 실행합니다. 구체적인 코드는 다음과 같습니다. 여기서 XXXAnnotation은 사용자 정의 주석의 이름입니다. #🎜🎜#@Retention(RetentionPolicy.CLASS)@Target(ElementType.TYPE)public @interface HelloAnnotation {}#🎜🎜# 그런 다음
AbstractProcessor
의 getSupportedAnnotationTypes()
도 다시 작성해야 합니다. code> 클래스 메서드 및 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) 코드는 종속성 주입에 대한 자세한 기술 설명을 수동으로 구현합니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!