开启成长之旅!这是我参加「日新方案 2 月更文应战」的第 2 天,点击查看活动概况
前文提到注解依照Retention能够取值能够分为SOURCE,CLASS,RUNTIME三类,在界说注解完结后,能够结合APT进行注解解析,读取到注解相关信息后进行一些查看和设置。
接下来咱们完结一个单例注解来润饰单例类,当开发人员写的单例类不符合编码规范时,在编译过程中抛出反常。咱们都知道,单例类应该具有以下特点:
- 结构器私有
- 具有public static润饰的getInstance办法
翻开Android Studio,新建SingletonAnnotationDemo工程,随后在该工程中进行注解的界说和APT的开发,一般情况下注解和与之相关的APT都会以单独的Module声明在项目中,下面咱们开端实践吧。
singleton-annotation 注解模块
新建singleton-annotation Java模块
翻开新建的SingletonAnnotationDemo项目,在右上角切换至Project视图,如下图所示:
切换完结后,在项目称号上右键单击,在弹出的菜单中依此挑选new->Module,如下图所示:
挑选Module条目后,弹出如下对话框,依次操作如下图所示:
其间符号1标明咱们创立的是Java或者Kotlin模块,符号2方位填写模块称号,这儿输入singleton-annotation,符号3方位输入打算创立的类名,这儿填写Singleton,符号4方位用于挑选模块言语类型,这儿挑选java即可。
至此创立singleton-annotation模块完结,等待Android Studio构建完结即可。
新建Singleton注解
翻开新建的singleton-annotation模块,进入Singleton.java文件中将其修改为注解,如上文描绘,该注解运转在编译期,故Retention为SOURCE,作用在类上,故其Target取值为TYPE,完好代码如下:
package com.poseidon.singleton_annotation;
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 Singleton {
}
依靠singleton-annotation模块
在app模块增加对singleton-annotation模块的依靠,操作办法有两种:
-
手动增加singleton-annotation依靠
翻开app模块的build.gradle文件,在其内部手动增加依靠,如下所示:
dependencies { ... // 增加singleton-annotation模块依靠 implementation project(path: ':singleton-annotation') }
随后从头同步项目即可
-
运用AS菜单增加singleton-annotation依靠
在app模块右键挑选Open Module Settings,在随后弹出的弹窗中增加singleton-annotation模块,操作指导如下图所示:
挑选Open Module Settings后弹框如下图所示,挑选dependencies,代表依靠管理,随后在右侧的Module列表中挑选你要操作的模块,这儿挑选app,最后点击挑选app模块后,其右侧依靠列表中的加号,挑选Module Dependency,代表增加模块依靠
Module Dependency:模块依靠,一般用于增加项目中的其他模块作为依靠
Library Dependency:库依靠,一般用于增加上传到maven,google,jcenter等方位的开源库
JAR/AAR Dependency:一般用于增加本地已有的jar或aar文件作为依靠时运用
挑选增加模块依靠后,弹出窗体如下图所示:
在上图中符号1的方位勾选要增加的模块,在2的方位挑选依靠办法,随后点击OK等待同步完结即可。
singleton-processor 注解处理模块
与创立singleton-annotation模块相同,以相同的办法创立一个名为singleton-processor的模块,其内部有一个Processor类,创立完结后,项目模块如下图所示(Processor类为创立模块时输入的类名,AS主动生成的):
增加注解处理器声明
将Processor.java类作为咱们的注解处理器类,为了Android Studio能识别到该类,咱们需要对该类进行声明,通常有两种声明办法:
-
手动声明
手动声明的主要完结办法是在main目录下创立resources/META-INF/services目录,在该目录下创立javax.annotation.processing.Processor文件,其内容如下所示:
com.poseidon.singleton_processor.Processor
能够看到其内部写的是注解处理器类完好途径(包名+类名),当有多个注解处理器类时,能够写多行,每次放置一条注解处理器信息即可
-
凭借AutoService库主动声明
除了手动声明外,咱们能够凭借auto-service库进行注解处理器声明,其自身也是依靠注解完结,在singleton-processor模块的build.gradle中增加auto-service库依靠,如下所示:
dependencies { implementation 'com.google.auto.service:auto-service:1.0' annotationProcessor 'com.google.auto.service:auto-service:1.0' }
依靠增加完结后,运用@AutoService注解润饰咱们的注解处理器类,代码如下:
@AutoService(Processor.class) public class Processor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { return false; } }
随后运转该项目,能够看到在singleton-processor模块的build目录中主动生成了META-INF相关的目录,如下图所示:
其间javax.annotation.processing.Processor文件内容和咱们手动增加时的内容共同。
当然也能够参考上文在Library Dependency窗口增加auto-service依靠,咱们能够自行探究下
依靠singleton-processor模块
与依靠singleton-annotation模块时办法类似,因为singleton-processor模块是注解处理模块,故依靠办法应运用annotationProcessor,在app模块的build.gradle文件的dependencies块中增加代码如下:
annotationProcessor project(path: ':singleton-processor')
至此咱们现已完结了新增模块的依靠以及注解的声明,接下来咱们来看看注解处理器的完结。
注解处理器代码完结
在前文中咱们现已将singleton-processor模块的Processor类声明为注解处理器,接下来咱们来看下如何在注解处理器中处理咱们的@Singleton注解,并对运用该注解的单例类完结查看。
自界说注解处理器一般承继自AbstractProcessor,AbstractProcessor是一个抽象类,其父类是Processor,在类编译成.class文件前,遍历整个项目里的一切代码,在获取到对应注解后,回调注解处理器的process办法,以便对注解进行处理。
当承继AbstractProcessor时,咱们一般重写下列函数:
函数称号 | 函数说明 |
---|---|
void init(ProcessingEnvironment processingEnv) | 初始化处理器环境,这儿能够缓存处理器环境,在process中产生反常等,能够打断经过缓存的变量打断编译履行 |
boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) | 处理办法,类或成员等的注释,并回来该处理器的处理成果。 假如回来true ,则标明注解被当时处理器处理,并且不会要求后续处理器持续处理; 假如回来false ,则表明未处理传入的注解,持续传递给后续处理器处理. RoundEnvironment参数用于查找运用了指定注解的元素,这儿的元素有多种,办法,成员,类等,和ElementType取值规模共同 |
Set getSupportedAnnotationTypes() | 获取注解处理器要处理的注解类型,假如在注解处理器类上运用了@SupportedAnnotationTypes注解润饰,则这儿回来的Set应和注解取值共同 |
SourceVersion getSupportedSourceVersion() | 注解处理器支持的Java版别,假如在注解处理器类上运用了@SupportedSourceVersion注解润饰,则这儿回来的取值应该和注解取值共同 |
下面咱们依照上述描绘重写Processor代码如下:
@AutoService(Processor.class)
public class Processor extends AbstractProcessor {
// 注解处理器运转环境
private ProcessingEnvironment mProcessingEnvironment;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mProcessingEnvironment = processingEnv;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}
@Override
public SourceVersion getSupportedSourceVersion() {
// 支持到最新的java版别
return SourceVersion.latestSupported();
}
}
因为该处理器主要处理的是@Singleton注解,故getSupportedAnnotationTypes完结如下(singleton-processor模块依靠singleton-annotation模块):
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> hashSet = new HashSet<>();
// 增加注解类的完好称号到HashSet中
hashSet.add(Singleton.class.getCanonicalName());
return hashSet;
}
随后咱们来看下process函数的完结,process内部逻辑完结一般分为三步:
- 获取代码中被运用该注解的一切元素,这儿的元素指的是组成程序的元素,可能是程序包,类自身、类的变量、办法等
- 挑选符合要求的元素,依据注解的运用场景挑选第一步中得到的一切元素,比方Singleton这个注解作用于类,就从第一步的成果中挑选出一切的类元素
- 遍历挑选出的元素,依照预设规则进行查看
依照上述步骤完结的Singleton注解处理器的process函数如下所示:
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 1.经过RoundEnvironment查找一切运用了Singleton注解的Element
// 2.随后经过ElementFilter获取该元素里面的一切类元素
// 3.遍历一切的类元素,针对自己重视的办法字段进行处理
for (TypeElement typeElement: ElementFilter.typesIn(roundEnvironment.getElementsAnnotatedWith(Singleton.class))) {
// 查看结构函数
if (!checkPrivateConstructor(typeElement)) {
return false;
}
// 查看getInstance办法
if (!checkGetInstanceMethod(typeElement)) {
return false;
}
}
return true;
}
ElementFilter.typesIn就是用来挑选查找出来的成果中的类元素,在ElementFilter类内部界说了五个元素组,如下所示:
- CONSTRUCTOR_KIND:结构器元素组
- FIELD_KINDS:成员变量元素组
- METHOD_KIND:办法元素组
- PACKAGE_KIND:包元素组
- MODULE_KIND:模块元素组
- TYPE_KINDS:类元素组
其间类元素组包括的最多,包括CLASS,ENUM,INTERFACE等
checkPrivateConstructor
public boolean checkPrivateConstructor(TypeElement typeElement) {
// 经过typeElement.getEnclosedElements()获取在此类或接口中直接声明的字段,办法等元素,随后运用ElementFilter.constructorsIn挑选出结构办法
List<ExecutableElement> constructors = ElementFilter.constructorsIn(typeElement.getEnclosedElements());
for (ExecutableElement constructor : constructors) {
// 判断结构办法是否是Private润饰的
if (constructor.getModifiers().isEmpty() || !constructor.getModifiers().contains(Modifier.PRIVATE)) {
mProcessingEnvironment.getMessager().printMessage(Diagnostic.Kind.ERROR, "constructor of a singleton class must be private", constructor);
return false;
}
}
return true;
}
checkPrivateConstructor完结逻辑如上,代码比较简单,不做赘述。
checkGetInstanceMethod
public boolean checkGetInstanceMethod(TypeElement typeElement) {
// 经过ElementFilter.constructorsIn挑选出该类中声明的一切办法
List<ExecutableElement> methods = ElementFilter.methodsIn(typeElement.getEnclosedElements());
for (ExecutableElement method : methods) {
System.out.println(TAG+method.getSimpleName());
// 查看办法称号
if (method.getSimpleName().contentEquals("getInstance")) {
// 查看办法回来类型
if (mProcessingEnvironment.getTypeUtils().isSameType(method.getReturnType(), typeElement.asType())) {
// 查看办法润饰符
if (!method.getModifiers().contains(Modifier.PUBLIC)) {
mProcessingEnvironment.getMessager().printMessage(Diagnostic.Kind.ERROR, "getInstance method should have a public modifier", method);
return false;
}
if (!method.getModifiers().contains(Modifier.STATIC)) {
mProcessingEnvironment.getMessager().printMessage(Diagnostic.Kind.ERROR, "getInstance method should have a static modifier", method);
return false;
}
}
}
}
return true;
}
checkGetInstanceMethod完结逻辑如上,能够看出当不满足咱们预设条件时会经过printMessage向外抛出反常,中断编译履行。
运用Singleton注解,查看注解处理器效果
在app模块中增加SingleTest.java并运用注解,代码如下:
@Singleton
public class SingletonTest {
private SingletonTest(){}
private static SingletonTest getInstance(){
return new SingletonTest();
}
}
能够看到该代码存在问题,咱们要求getInstance办法要用public static润饰,这儿运用的是private,运转程序,看咱们的注解处理器是否能发现该问题并打断程序履行,运转成果如下图:
能够看到程序确实停止运转,并抛出了编译时反常,至此咱们自界说编译时注解的操作就学习完了。
扩展
在注解运用办法一节中,咱们提到编译时注解即Retention=RetentionPolicy.SOURCE的注解仅在源码中保存,接下来咱们验证一下,反编译前文中经过注解处理器查看正常运转的apk,找到SingletonTest类,能够看到在其字节码文件中确实不存在注解代码,如下图所示: