携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的28天,点击查看活动详情
为了活动小家电,接着上篇搞!
test-processor
test-processor 模块是通过在构建时查找带注释的类来生成代码的模块。
build.gradle 文件如下所示。
apply plugin: 'kotlin'
apply plugin: 'kotlin-kapt'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation project(':datavalidation-annotation')
implementation 'com.squareup:kotlinpoet:1.5.0'
implementation "com.google.auto.service:auto-service:1.0-rc6"
kapt "com.google.auto.service:auto-service:1.0-rc6"
}
sourceCompatibility = "7"
targetCompatibility = "7"
我使用自定义注释引用了 test-annotation 模块。
kotlinpoet 是一个方便自动化 kotlin 文件的库。
auto-service 是一个用于向编译器自动注册注解处理器的库。(如果你想在没有库的情况下这样做,你需要制作一个META-INF文件并配置一些设置,但我没有尝试过)
下面是实际的Processor代码。
@AutoService(Processor::class)
class Processor : AbstractProcessor() {
companion object {
const val KAPT_KOTLIN_GENERATED_OPTION_NAME = "kapt.kotlin.generated"
// 用于文件输出
val fileBuilder =
FileSpec.builder("com.datavalidation.generated", "DataValidationExtension")
}
// 获取支持的注释类型
override fun getSupportedAnnotationTypes(): MutableSet<String> {
return mutableSetOf(
DataValidation::class.java.name,
Nested::class.java.name,
NotNull::class.java.name,
MinLength::class.java.name,
MaxLength::class.java.name,
MinValue::class.java.name,
MaxValue::class.java.name,
Regex::class.java.name
)
}
// 获取支持的源版本
override fun getSupportedSourceVersion(): SourceVersion {
return SourceVersion.latestSupported()
}
// 调用两次或多次
// 主要执行逻辑
override fun process(
annotations: MutableSet<out TypeElement>?,
roundEnv: RoundEnvironment
): Boolean {
// 获取带有 DataValidation 注解的元素
val classElements = roundEnv.getElementsAnnotatedWith(DataValidation::class.java)
if (!checkElementType(ElementKind.CLASS, classElements)) return false
// 进行验证
classElements.forEach { fileBuilder.addFunction(validate(it)) }
fileBuilder.addImport(FieldNameTag::class.java, "")
val kaptKotlinGeneratedDir = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME]
fileBuilder.build().writeTo(File(kaptKotlinGeneratedDir))
return true
}
// 验证函数
// 以下逻辑皆为kotlinpoet 自动生成校验类过程
private fun validate(classElement: Element): FunSpec {
val validateFunSpec = FunSpec.builder("validate")
.receiver(classElement.asType().asTypeName())
.returns(ValidationResult::class)
.addStatement("val result = %T()", ValidationResult::class.java)
val fieldElement = classElement.enclosedElements
fieldElement.forEach {
val nonNull = it.getAnnotation(NotNull::class.java)
val minLength = it.getAnnotation(MinLength::class.java)
val maxLength = it.getAnnotation(MaxLength::class.java)
val minValue = it.getAnnotation(MinValue::class.java)
val maxValue = it.getAnnotation(MaxValue::class.java)
val regex = it.getAnnotation(Regex::class.java)
val nested = it.getAnnotation(Nested::class.java)
nonNull?.let { annotation ->
validateFunSpec.addComment("NonNull Check")
validateFunSpec.beginControlFlow("if(${it.simpleName} == null)")
validateFunSpec.addStatement("result.isValid = false")
validateFunSpec.addStatement("result.invalidFieldNameTags.add(${createFieldNameTag(it.simpleName, annotation.tag)})")
validateFunSpec.endControlFlow()
}
minLength?.let { annotation ->
validateFunSpec.addComment("Minimum Length Check")
validateFunSpec.beginControlFlow("if(${it.simpleName} == null || ${it.simpleName}.length < ${annotation.length})")
validateFunSpec.addStatement("result.isValid = false")
validateFunSpec.addStatement("result.invalidFieldNameTags.add(${createFieldNameTag(it.simpleName, annotation.tag)})")
validateFunSpec.endControlFlow()
}
maxLength?.let { annotation ->
validateFunSpec.addComment("Maximum Length Check")
validateFunSpec.beginControlFlow("if(${it.simpleName} == null || ${it.simpleName}.length > ${annotation.length})")
validateFunSpec.addStatement("result.isValid = false")
validateFunSpec.addStatement("result.invalidFieldNameTags.add(${createFieldNameTag(it.simpleName, annotation.tag)})")
validateFunSpec.endControlFlow()
}
minValue?.let { anno ->
validateFunSpec.addComment("Minimum Value Check")
validateFunSpec.beginControlFlow("if(${it.simpleName} == null || ${it.simpleName}.toLong() < ${anno.value})")
validateFunSpec.addStatement("result.isValid = false")
validateFunSpec.addStatement("result.invalidFieldNameTags.add(${createFieldNameTag(it.simpleName, anno.tag)})")
validateFunSpec.endControlFlow()
}
maxValue?.let { anno ->
validateFunSpec.addComment("Minimum Value Check")
validateFunSpec.beginControlFlow("if(${it.simpleName} == null || ${it.simpleName}.toLong() > ${anno.value})")
validateFunSpec.addStatement("result.isValid = false")
validateFunSpec.addStatement("result.invalidFieldNameTags.add(${createFieldNameTag(it.simpleName, anno.tag)})")
validateFunSpec.endControlFlow()
}
regex?.let { anno ->
validateFunSpec.addComment("Regex Match Check")
validateFunSpec.beginControlFlow("if(${it.simpleName} == null || !%S.toRegex().matches(${it.simpleName}))", anno.regex)
validateFunSpec.addStatement("result.isValid = false")
validateFunSpec.addStatement("result.invalidFieldNameTags.add(${createFieldNameTag(it.simpleName, anno.tag)})")
validateFunSpec.endControlFlow()
}
nested?.let { _ ->
validateFunSpec.addComment("Nested Check")
validateFunSpec.beginControlFlow("if(${it.simpleName} != null)")
validateFunSpec.addStatement("val nestedValidation = ${it.simpleName}.validate()")
validateFunSpec.beginControlFlow("if(!nestedValidation.isValid)")
validateFunSpec.addStatement("result.isValid = false")
validateFunSpec.addStatement("result.invalidFieldNameTags.addAll(nestedValidation.invalidFieldNameTags)")
validateFunSpec.endControlFlow()
validateFunSpec.endControlFlow()
}
}
return validateFunSpec.addStatement("return result").build()
}
private fun checkElementType(kind: ElementKind, elements: Set<Element>): Boolean {
if (elements.isEmpty()) return false
elements.forEach {
if (it.kind != kind) {
printMessage(
Diagnostic.Kind.ERROR, "Only ${kind.name} Are Supported", it
)
return false
}
}
return true
}
private fun printMessage(kind: Diagnostic.Kind, message: String, element: Element) {
processingEnv.messager.printMessage(kind, message, element)
}
private fun createFieldNameTag(fieldName: Name, tag: String): String {
return "FieldNameTag("$fieldName", "$tag")"
}
}
首先,我继承了 AbstractProcessor 并覆盖了所需的方法。
@AutoService(Processor::class) 是上面提到的自动服务库的一个函数,它只用一个注解自动向编译器注册一个处理器。
kapt.kotlin.generated如果您创建的app\build\generated\source\kaptKotlin文件的键替换为 ,则指示保存位置。
getSupportedAnnotationTypes()在方法中,它告诉相应的处理器要处理的注释。
getSupportedSourceVersion()该方法告诉您要支持哪个版本的源代码。
process()是创建代码的逻辑所在。
您可以通过 RoundEnvironment 提取所需的元素。
总流量为
- 使用 DataValidation 提取类元素并
fun class.validate()我为要创建的文件中的每个元素编写了函数。