JacoDB 는 JVM 프로세스 외부의 Java Bytecode에 대한 정보를 얻고 데이터베이스에 저장할 수있는 순수한 Java 라이브러리입니다. Java Reflection 사용하면 런타임에 코드를 검사 할 수 있지만 JacoDB 파일 시스템에 저장된 바이트 코드에 대해 동일하게 수행합니다.
JacoDB Java Bytecode를 읽고 파싱하기 위해 ASM 프레임 워크를 사용합니다.
클래스, 계층 구조, 주석, 메소드, 필드 및 사용법에 대한 정보는 메모리 또는 지속적인 SQLITE 데이터베이스에 저장됩니다. 지속 된 데이터는 재시작 사이에 재사용 될 수 있습니다. 여러 프로세스에서 영구 스토리지에 동시에 액세스하는 것은 지원되지 않습니다.
Gradle :
implementation(group = " org.jacodb " , name = " jacodb-api " , version = " 1.2.0 " )
implementation(group = " org.jacodb " , name = " jacodb-core " , version = " 1.2.0 " )
implementation(group = " org.jacodb " , name = " jacodb-analysis " , version = " 1.2.0 " )또는
Maven :
< dependencies >
< dependency >
< groupId >org.jacodb</ groupId >
< artifactId >jacodb-core</ artifactId >
< verison >1.2.0</ verison >
</ dependency >
< dependency >
< groupId >org.jacodb</ groupId >
< artifactId >jacodb-api</ artifactId >
< verison >1.2.0</ verison >
</ dependency >
< dependency >
< groupId >org.jacodb</ groupId >
< artifactId >jacodb-analysis</ artifactId >
< verison >1.2.0</ verison >
</ dependency >
</ dependencies >API에는 파일 시스템 ( 바이 테코 코드 및 클래스 )을 나타내는 두 가지 레벨과 런타임 ( 유형 )에 나타납니다.
class 파일의 데이터를 나타냅니다 : 메소드, 필드 등의 클래스. 두 레벨 모두 JcClasspath 에 연결됩니다. 순수한 바이트 코드에서 검색된 클래스를 수정할 수 없습니다. 유형은 제네릭 대체에 의해 수동으로 구성 될 수 있습니다.
public static void getStringFields () throws Exception {
JcDatabase database = JacoDB . async ( new JcSettings (). useProcessJavaRuntime ()). get ();
JcClassOrInterface clazz = database . asyncClasspath ( emptyList ()). get (). findClassOrNull ( "java.lang.String" );
System . out . println ( clazz . getDeclaredFields ());
}코 틀린
val database = jacodb {
useProcessJavaRuntime()
}
val clazz = database.classpath().findClassOrNull( " java.lang.String " )더 복잡한 예 :
자바
class Example {
public static MethodNode findNormalDistribution () throws Exception {
File commonsMath32 = new File ( "commons-math3-3.2.jar" );
File commonsMath36 = new File ( "commons-math3-3.6.1.jar" );
File buildDir = new File ( "my-project/build/classes/java/main" );
JcDatabase database = JacoDB . async (
new JcSettings ()
. useProcessJavaRuntime ()
. persistent ( "/tmp/compilation-db/" + System . currentTimeMillis ()) // persist data
). get ();
// Let's load these three bytecode locations
database . asyncLoad ( Arrays . asList ( commonsMath32 , commonsMath36 , buildDir ));
// This method just refreshes the libraries inside the database. If there are any changes in libs then
// the database updates data with the new results.
database . asyncLoad ( Collections . singletonList ( buildDir ));
// Let's assume that we want to get bytecode info only for `commons-math3` version 3.2.
JcClassOrInterface jcClass = database . asyncClasspath ( Arrays . asList ( commonsMath32 , buildDir ))
. get (). findClassOrNull ( "org.apache.commons.math3.distribution.NormalDistribution" );
System . out . println ( jcClass . getDeclaredMethods (). size ());
System . out . println ( jcClass . getAnnotations (). size ());
System . out . println ( JcClasses . getConstructors ( jcClass ). size ());
// At this point the database read the method bytecode and return the result.
return jcClass . getDeclaredMethods (). get ( 0 ). asmNode ();
}
}코 틀린
suspend fun findNormalDistribution (): MethodNode {
val commonsMath32 = File ( " commons-math3-3.2.jar " )
val commonsMath36 = File ( " commons-math3-3.6.1.jar " )
val buildDir = File ( " my-project/build/classes/java/main " )
val database = jacodb {
useProcessJavaRuntime()
persistent( " /tmp/compilation-db/ ${ System .currentTimeMillis()} " ) // persist data
}
// Let's load these three bytecode locations
database.load( listOf (commonsMath32, commonsMath36, buildDir))
// This method just refreshes the libraries inside the database. If there are any changes in libs then
// the database updates data with the new results.
database.load( listOf (buildDir))
// Let's assume that we want to get bytecode info only for `commons-math3` version 3.2.
val jcClass = database.classpath( listOf (commonsMath32, buildDir))
.findClass( " org.apache.commons.math3.distribution.NormalDistribution " )
println (jcClass.declaredMethods.size)
println (jcClass.constructors.size)
println (jcClass.annotations.size)
// At this point the database read the method bytecode and return the result.
return jcClass.methods[ 0 ].asmNode()
} 클래스는 불완전한 환경에있을 수 있습니다 (즉, 클래스 경로에서는 슈퍼 클래스, 인터페이스, 리턴 유형 또는 메소드의 NoClassInClasspathException 찾을 수 없습니다. 이 설치를 수정하려면 UnknownClasses 기능을 생성하는 동안 ClassPath에 사용합니다.
데이터베이스는 백그라운드에서 파일 시스템 변경을 관찰하고 Jar-Files를 명시 적으로 새로 고칠 수 있습니다.
자바
public static void watchFileSystem () throws Exception {
JcDatabase database = JacoDB . async ( new JcSettings ()
. watchFileSystem ()
. useProcessJavaRuntime ()
. loadByteCode ( Arrays . asList ( lib1 , buildDir ))
. persistent ( "" , false )). get ();
}
// A user rebuilds the buildDir folder.
// The database re-reads the rebuilt directory in the background.코 틀린
val database = jacodb {
watchFileSystem()
useProcessJavaRuntime()
loadByteCode( listOf (lib1, buildDir))
}
// A user rebuilds the buildDir folder.
// The database re-reads the rebuilt directory in the background.유형은 그 중 하나로 표시 될 수 있습니다
주어진 일반 유형의 매개 변수 대체에 따라 런타임 동작을 나타냅니다.
자바
public static class A < T > {
T x = null ;
}
public static class B extends A < String > {
}
public static void typesSubstitution () {
JcClassType b = ( JcClassType ) classpath . findTypeOrNull ( "org.jacodb.examples.JavaReadMeExamples.B" );
JcType xType = b . getFields ()
. stream ()
. filter ( it -> "x" . equals ( it . getName ()))
. findFirst (). get (). getFieldType ();
JcClassType stringType = ( JcClassType ) classpath . findTypeOrNull ( "java.lang.String" );
System . out . println ( xType . equals ( stringType )); // will print `true`
}코 틀린
open class A < T >( val x : T )
class B ( x : String ): A<String>(x)
suspend fun typesSubstitution () {
val db = jacodb {
loadByteCode( listOf ( File ( " all-classpath " )))
}
val classpath = db.classpath( listOf ( File ( " all-classpath " )))
val b = classpath.findClass< B >().toType()
println (b.fields.first { it.name == " x " }.fieldType == classpath.findClass< String >().toType()) // will print `true`
} 전체 JcDatabase 또는 특정 JcClasspath 에 기능을 사용할 수 있습니다.
필드 및 방법의 사용을 검색하기위한 데이터베이스 기능.
val database = jacodb {
install( Usages )
}더보십시오
알 수없는 수업을 조롱합니다. 불완전한 클래스 경로를 처리하는 은혜의 방법입니다
val database = jacodb {}
val classpath = database.classpath( listOf ( UnknownClasses ))
val clazz = classpath.findClass( " UnknownClasses " ) // will return `JcClassOrInterface` instance더보십시오
조금 더 빠른 계층 구조이지만 더 많은 RAM 메모리를 소비합니다.
val database = jacodb {
install( InMemoryHierarchy )
}더보십시오
JcClassOrInterface , JcMethod 및 JcClasspath 의 인스턴스는 스레드 안전하고 불변입니다.
JcClasspath 독립적 인 클래스 스냅 샷을 나타냅니다.이 클래스는 생성되었으므로 수정할 수 없습니다. 라이브러리 파일을 제거하거나 수정해도 JcClasspath 인스턴스 구조에는 영향을 미치지 않습니다. JcClasspath#close Method는 모든 스냅 샷을 출시하고 일부 라이브러리가 구식 인 경우 지속 된 데이터를 정리합니다.
자바
public static void refresh () throws Exception {
JcDatabase database = JacoDB . async (
new JcSettings ()
. watchFileSystem ()
. useProcessJavaRuntime ()
. loadByteCode ( Arrays . asList ( lib1 , buildDir ))
. persistent ( "..." )
). get ();
JcClasspath cp = database . asyncClasspath ( Collections . singletonList ( buildDir )). get ();
database . asyncRefresh (). get (); // does not affect cp classes
JcClasspath cp1 = database . asyncClasspath ( Collections . singletonList ( buildDir )). get (); // will use new version of compiled results in buildDir
}코 틀린
val database = jacodb {
watchFileSystem()
useProcessJavaRuntime()
loadByteCode( listOf (lib1, buildDir))
persistent( " " )
}
val cp = database.classpath( listOf (buildDir))
database.refresh() // does not affect cp classes
val cp1 = database.classpath( listOf (buildDir)) // will use new version of compiled results in buildDir 아직 인덱스되지 않은 라이브러리가 포함 된 JcClasspath 인스턴스에 대한 요청이있는 경우 인덱싱 프로세스가 트리거되고 JcClasspath 세트의 새 인스턴스가 반환됩니다.
자바
public static void autoProcessing () throws Exception {
JcDatabase database = JacoDB . async (
new JcSettings ()
. loadByteCode ( Arrays . asList ( lib1 ))
. persistent ( "..." )
). get ();
JcClasspath cp = database . asyncClasspath ( Collections . singletonList ( buildDir )). get (); // database will automatically process buildDir
}코 틀린
val database = jacodb {
loadByteCode( listOf (lib1))
persistent( " " )
}
val cp = database.classpath( listOf (buildDir)) // database will automatically process buildDir JacoDB 는 스레드-안전합니다. 다른 스레드에서 Jar-Files를로드하는 동안 JcClasspath 인스턴스를 요청하면 JcClasspath JAR-Files의 일관된 상태만을 나타낼 수 있습니다. JcClasspath 에 나타나는 완전히로드 된 Jar-File입니다. 참고 : 로딩을 위해 제출 된 모든 JAR-FILE가 실제로로드 될 것이라는 보장은 없습니다.
자바
class Example {
public static void main ( String [] args ) {
val db = JacoDB . async ( new JcSettings ()). get ();
new Thread (() -> db . asyncLoad ( Arrays . asList ( lib1 , lib2 )). get ()). start ();
new Thread (() -> {
// maybe created when lib2 or both are not loaded into database
// but buildDir will be loaded anyway
var cp = db . asyncClasspath ( buildDir ). get ();
}). start ();
}
}코 틀린
val db = jacodb {
persistent( " " )
}
thread(start = true ) {
runBlocking {
db.load( listOf (lib1, lib2))
}
}
thread(start = true ) {
runBlocking {
// maybe created when lib2 or both are not loaded into database
// but buildDir will be loaded anyway
val cp = db.classpath( listOf (buildDir))
}
}바이트 코드 로딩은 두 단계로 구성됩니다.
JcFeature 구현 설정 등)에서 바이오 코드를 읽습니다. JacoDB 또는 JcClasspath 인스턴스는 첫 번째 단계가 수행 된 직후에 반환됩니다. 두 번째 단계에서 클래스 의 최종 표현을 검색합니다. .class 파일은 첫 번째 단계와 두 번째 단계 사이에 어느 순간에 변경 될 수 있으며, 그에 따라 클래스 표현이 영향을받을 수 있습니다.
다음은 벤치 마크 결과와 그을음 마이그레이션입니다.