如何把一个类上面的注解转移到另外一个类
一个比较奇葩的需求,因为controller层的request对象(使用hibernate validate)换成proto对象,但是proto的校验功能protoc-gen-validate不完善,考虑把之前 在controller层的校验注解挪到service层的对象上面,手动操作工作量过大,故有此工具类
依赖包: com.github.javaparser:javaparser-symbol-solver-core:3.25.2
原理
- 使用 javaparser 解析 source 对象和 target 对象源码
- 将 source 对象和 target 对象的字段和对应的注解做映射
- 比较字段名字,将 source 对象的注解加到 target 对象上
- 重新生成 target 对象源码,写入文件完事
kotlin
import com.github.javaparser.JavaParser
import com.github.javaparser.ast.CompilationUnit
import com.github.javaparser.ast.body.FieldDeclaration
import java.io.File
import java.io.FileWriter
import kotlin.reflect.KClass
/**
* 把 Java A类的注解 移到 Java B类
* @author c
*/
fun getSourceCode(clazz: KClass<*>): CompilationUnit {
val sourceFile = File(cls2Path(clazz.java))
val compilationUnit = JavaParser().parse(sourceFile)
return compilationUnit.result.orElseThrow { Exception("JavaParse Error") }
}
fun cls2Path(clazz: Class<*>): String {
val classLoader = ClassLoader.getSystemClassLoader()
// C:\code\v2x-device\matrix-device-client\src\main\java\com\sensetime\iag\v2x\device\sensor\vo\request\SensorNewRequest.java
val res: String = clazz.`package`.name.replace('.', '/')
return classLoader.getResource("$res/${clazz.simpleName}.class")
?.file
?.replace("target/classes", "src/main/java")
?.replace(".class", ".java")
?: throw Exception("parse path error")
}
fun clsAnnoMoving(clsSource: KClass<*>, clsTarget: KClass<*>): String {
val sourceCodeCls = getSourceCode(clsSource)
val needImport = sourceCodeCls.imports.filter {
it.toString().let { iStr ->
iStr.contains("validat")
|| iStr.contains("NotBlankMessageConstant")
|| iStr.contains("ProtocolVersionValidate")
}
}
val fAnnoMap = sourceCodeCls.types?.get(0)?.let { td ->
td.members
.filterIsInstance<FieldDeclaration>()
.associate {
it.variables.first.get().name to it.annotations.filter { anno ->
!anno.toString().contains("ApiModelProperty")
}
}
}
val tarCodeCls = getSourceCode(clsTarget)
tarCodeCls.types?.get(0)?.let { td ->
td.members
.filterIsInstance<FieldDeclaration>()
.forEach { bd ->
val needAddAnno = fAnnoMap?.get(bd.variables.first.get().name)
needAddAnno?.filter { !bd.annotations.contains(it) }
?.forEach { bd.addAnnotation(it) }
}
}
needImport.forEach { tarCodeCls.imports.add(it) }
return tarCodeCls.toString()
}
private fun doMovingAnno(
clsA: KClass<*>,
clsB: KClass<*>
): String {
val targetCode = clsAnnoMoving(clsA, clsB)
val targetPath = cls2Path(clsB.java)
FileWriter(targetPath).use { fileWriter -> fileWriter.write(targetCode) }
return targetCode
}
fun main() {
val clsMap = mapOf(
SensorNewRequest::class to VehicleSensor::class,
VehicleNewRequest::class to Vehicle::class
)
clsMap.forEach { (clsSource, clsTarget) -> doMovingAnno(clsSource, clsTarget).let(::println) }
}