Apache Commons BCEL에서 visit class 파일의 바이트 코드

Visitor 그래픽 스캔Apache Commons BCELclass 파일의 코드를 찾으려고 했지만 의외로 샘플 코드를 찾지 못해 남겼습니다.

샘플 코드


샘플 코드는 Kotlin 1.4로 작성되었습니다.다음 세 가지 파일을 소개합니다.
  • build.gradle.kts
  • ExampleVisitor.kt
  • Main.kt
  • build.gralde.kts


    Gradle의 dependencies에 추가Apache Commons BCEL.버전 6.5.0을 사용했습니다.
    dependencies {
        implementation("org.apache.bcel:bcel:6.5.0")
    }
    

    ExampleVisitor.kt


    Visitor를 만들려면 커넥터org.apache.bcel.classfile.Visitor를 설치합니다.빈 방법으로 이 인터페이스org.apache.bcel.classfile.EmptyVisitor를 실현한 반을 준비했기 때문에 이 EmptyVisitor를 계승하는 것은 매우 쉽다.
    import org.apache.bcel.classfile.*
    import org.apache.bcel.generic.ConstantPoolGen
    import org.apache.bcel.generic.InstructionList
    import org.apache.bcel.generic.InvokeInstruction
    
    class ExampleVisitor : EmptyVisitor() {
    
        override fun visitJavaClass(jc: JavaClass) {
            println("Class: ${jc.className}")
        }
    
        override fun visitField(field: Field) {
            println("Field: ${field.name} ${field.signature}")
        }
    
        override fun visitMethod(method: Method) {
            println("Method: ${method.name}${method.signature}")
        }
    
        override fun visitCode(code: Code) {
            println("Code:")
            // メソッドの中身を表すCodeより下の階層は、Visitorのメソッドとしては用意されていないようなので、InstructionListを使ってアクセスする。
            val instructions = InstructionList(code.code)
            // 名前を取得する処理ではConstantPoolGenオブジェクトが必要になる。
            val constantPoolGen = ConstantPoolGen(code.constantPool)
            instructions.forEach { instructionHandle ->
                when (val instruction = instructionHandle.instruction) {
                    // instructionの型に応じて処理を分岐する。
                    is InvokeInstruction -> {
                        // 例: メソッド呼び出しの場合
                        println("  ${instruction.name}: ${instruction.getReferenceType(constantPoolGen)}.${instruction.getMethodName(constantPoolGen)}${instruction.getSignature(constantPoolGen)}")
                    }
                    else -> {
                        // その他の場合
                        println("  ${instruction.name}")
                    }
                }
            }
        }
    
        override fun visitInnerClasses(innerClasses: InnerClasses) {
            innerClasses.innerClasses.forEach { innerClass ->
                println("InnerClass: ${innerClass.toString(innerClasses.constantPool)}")
            }
        }
    }
    

    Main.kt


    이 Visitor를 사용해 실제로 계층에 오른 것은org.apache.bcel.classfile.DescendingVisitor이다.org.apache.bcel.classfile.ClassParser 해석된 자바클래스와 Visitor 실례를 사용하여DescendingVisitor 실례를 만들고visit 방법으로 스캔합니다.
    import org.apache.bcel.classfile.ClassParser
    import org.apache.bcel.classfile.DescendingVisitor
    
    fun main() {
        val classParser = ClassParser("/path/to/build/classes/kotlin/main/target/TargetKt.class")
        val javaClass = classParser.parse()
        val descendingVisitor = DescendingVisitor(javaClass, ExampleVisitor())
        descendingVisitor.visit()
    }
    

    대상 파일


    예를 들어visit 이하 파일Target.kt의 구축 결과의class 파일TargetKt.class.이 샘플 코드는 JDBC URL을 소스 코드에 쓰는 것을 추천하지 않습니다.
    package target
    
    import org.jdbi.v3.core.Jdbi
    import org.jdbi.v3.core.kotlin.mapTo
    import java.time.LocalDate
    
    fun main() {
        val jdbcUrl = "jdbc:postgresql://localhost:5432/dvdrental?user=postgres&password="
        val jdbi = Jdbi.create(jdbcUrl).installPlugins()
    
        jdbi.useHandle<Exception> { handle ->
            val sql = "SELECT * FROM customer"
            val result = handle.createQuery(sql)
                    .mapTo<Result>()
                    .list()
            println(result)
        }
    }
    
    data class Result(
        val customerId: Int,
        val storeId: Int,
        val firstName: String,
        val lastName: String,
        val createDate: LocalDate,
    )
    

    실행 결과


    실행 결과는 다음과 같습니다.Kotlin에 정의된main 함수는 하나뿐이지만 TargetKt 클래스에는 두 가지 다른 서명 방법이 있습니다.
    실행 결과의 마지막 줄부터 jdbi.useHandle 매개 변수에서 지정한 λ식은 InnerClass로 다른 클래스 파일에 존재합니다.같은 파일에 정의된 데이터classs의Result도 다른 클래스 파일에 있으며 이 실행 결과에 나타나지 않습니다.
    Class: target.TargetKt
    Method: main()V
    Code:
      ldc
      astore_0
      aload_0
      invokestatic: org.jdbi.v3.core.Jdbi.create(Ljava/lang/String;)Lorg/jdbi/v3/core/Jdbi;
      invokevirtual: org.jdbi.v3.core.Jdbi.installPlugins()Lorg/jdbi/v3/core/Jdbi;
      astore_1
      aload_1
      getstatic
      checkcast
      invokevirtual: org.jdbi.v3.core.Jdbi.useHandle(Lorg/jdbi/v3/core/HandleConsumer;)V
      return
    Method: main([Ljava/lang/String;)V
    Code:
      invokestatic: target.TargetKt.main()V
      return
    InnerClass:   static final (anonymous)=class target.TargetKt$main$1
    

    최후


    설치 후에야 비로소 class 파일의 바이트 코드에 존재하는 등급 구조가 유한하고 Visitor 모드를 사용하는 장점이 많지 않다는 것을 알게 되었다.하지만 참고 가치가 있을 수 있으니 남겨두세요.

    참고 자료

  • Apache Commons BCEL™ – The BCEL API
  • Apache BCEL로 클래스 파일 정보 얻기 - 프로그램 일기
  • 좋은 웹페이지 즐겨찾기