안녕하세요.
지난 포스팅에 이어서 이번에는 Annotation을 이용한 코드생성을 진행해보겠습니다.
혹시나 안보셧다면 먼저 보고 이 글을 읽으시길 바랍니다.
https://dog-footprint.tistory.com/49
일단 코드 생성을 위해선 몇가지 개념을 알아야 합니다.
FileSpec : 코드생성 파일을 정의하는 클래스
TypeSpec : 파일생성시 어떤 타입의 파일을 정의하는 지에 대한 클래스(예를 들면 클래스 파일, 인터페이스 파일 등등)
FunSpec : 함수 코드를 생성하기 위한 클래스
PropertySpec : Property 설정을 정의하기 위한 클래스
AnnotationSpec : AnnotationSpec 설정을 정의하기 위한 클래스
ParameterSpec : 함수의 Parameter설정을 정의하기 위한 클래스
Spec클래스들은 각각 파일, 클래스, 함수 등등 정의할때 구분해서 정의하기 편하기 위한 클래스 입니다.
일단 샘플 코드로 어떻게 코드생성을 하는지 알아보겠습니다. MyProessor 클래스 안에 아래 함수를 정의합니다.
private fun makeMyInformationClass(){
val testFileBuilder = FileSpec.builder("","MyInformation") // 파일이름 설정
.addType(TypeSpec.classBuilder("MyInformation") //클래스 이름 설정
.primaryConstructor(FunSpec.constructorBuilder() //클래스 생성자 설정
.addParameter("name", String::class, KModifier.PRIVATE) //생성자에 name Parameter 추가
.addParameter("age", Int::class, KModifier.PRIVATE) //생성자에 age Parameter 추가
.build()) //클래스 생성자 설정 완료
.addProperty(PropertySpec.builder("name", String::class) // 클래스에 name Property 추가
.initializer("name") //초기값 설정(생성자로 받은 name 설정)
.build())
.addProperty(PropertySpec.builder("age", Int::class) // 클래스에 age Property 추가
.initializer("age") //초기값 설정(생성자로 받은 age 설정)
.build())
.addProperty(PropertySpec.builder("job", String::class) // 클래스에 job Property 추가
.initializer("\"programmer\"") //초기값 설정(job = programmer)
.build())
.addFunction(FunSpec.builder("printMyInfo") // 클래스에 printMyInfo 함수 추가
.addStatement("println(%P)", "name : \$name, age : \$age, job : \$job") // print 함수 추가
.build())
.build()).build()
val kaptKotlinGeneratedDir = processingEnv.options["kapt.kotlin.generated"]!!
testFileBuilder.writeTo(File(kaptKotlinGeneratedDir)) //파일 생성
}
주석에서 보시는 것과 같이 각각 파일 이름설정, 생성자 설정, 함수 설정 등등 add 하는 속성마다 Spec클래스들이 쓰인걸 볼 수 있습니다.
이렇게 만들어진 결과는 아래와 같습니다.
public class MyInformation(
private val name: String,
private val age: Int
) {
public val job: String = "programmer"
public fun printMyInfo(): Unit {
println("""name : $name, age : $age, job : $job""")
}
}
여기서 왜 job만 property로 생성되고 name과 age는 생성이 안되었느냐고 생각하실 수있는데
클래스 생성자에
MyInformation(private val name: String) == MyInformation(name: String){
private val name: String = name
}
이기 때문입니다.
이제 어노테이션과 어떻게 같이 사용하는지 알아보겠습니다.
일단 MyProcessor의 process함수를 수정합니다.
override fun process(
annotations: MutableSet<out TypeElement>?,
roundEnv: RoundEnvironment
): Boolean {
val annotatedClasses = roundEnv.getElementsAnnotatedWith(MyAnnotation::class.java)
if(annotatedClasses.isEmpty()) return false
annotatedClasses.forEach {
if(it.kind != ElementKind.CLASS) return false
makeMyInfoPrintClass(it)
}
return true
}
MyAnnotation이 붙은클래스를 찾고 해당 클래스들을 순회하면서 makeMyInfoPrintClass()를 호출하여 클래스파일을 만들도록 합니다.
private fun makeMyInfoPrintClass(element: Element){
val typeMetadata = element.getAnnotation(Metadata::class.java)
val kmClass = typeMetadata.toImmutableKmClass()
val className = ClassInspectorUtil.createClassName(kmClass.name)
val printInfoFunSpec = FunSpec.builder("printInfo").receiver(className)
val myAnnotation = element.getAnnotation(MyAnnotation::class.java)
myAnnotation.run {
printInfoFunSpec.addStatement("println(%P)", "name : ${this.name}, age : ${this.age}, job : ${this.job}")
}
testFileBuilder = FileSpec.builder("","MyInfoPrint").addFunction(printInfoFunSpec.build()).build()
val kaptKotlinGeneratedDir = processingEnv.options["kapt.kotlin.generated"]!!
testFileBuilder.writeTo(File(kaptKotlinGeneratedDir))
}
MyAnnotation이 붙은 클래스의 Extension 함수를 만들도록 정의하면
이렇게 Extension 함수 파일이 생깁니다.
public fun MyInfoTest.printInfo(): Unit {
println("""name : changgyu, age : 30, job : programmer""")
}
이후 메인에서 MyInfoTest()를 생성해서 printInfo()를 찍어보면
I/System.out: name : changgyu, age : 30, job : programmer
결과가 나오게 됩니다.
위 프로젝트는 제 깃허브에 올려두었습니다.
https://github.com/jakchang01/BlogProject/tree/master/CodeGenerate
아래는 kotlinpoet의 doc 사이트입니다.
https://square.github.io/kotlinpoet
'개발 > 안드로이드' 카테고리의 다른 글
[안드로이드] Retrofit은 어떻게 동작할까? (0) | 2023.02.12 |
---|---|
안드로이드 Annotation을 이용한 코드 생성(1) (0) | 2021.08.02 |
안드로이드 include, merge, viewstub (0) | 2021.07.08 |
안드로이드 뷰가 그려지는 과정 (0) | 2021.06.09 |
안드로이드 LiveData setValue 와 postValue (0) | 2021.05.13 |
댓글