JacoDB adalah perpustakaan Java murni yang memungkinkan Anda mendapatkan informasi tentang Java bytecode di luar proses JVM dan menyimpannya dalam database. Sementara Reflection Java memungkinkan untuk memeriksa kode saat runtime, JacoDB melakukan hal yang sama untuk bytecode yang disimpan dalam sistem file.
JacoDB menggunakan kerangka kerja ASM untuk membaca dan parsing Java bytecode.
Informasi tentang kelas, hierarki, anotasi, metode, bidang, dan penggunaannya disimpan dalam database SQLite-baik dalam memori atau gigih. Data yang ada dapat digunakan kembali di antara restart. Mengakses penyimpanan persisten dari beberapa proses secara bersamaan tidak didukung.
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 " )atau
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 memiliki dua level: yang mewakili dalam sistem file ( bytecode dan kelas ) dan yang muncul saat runtime ( tipe ).
class : kelas dengan metode, bidang, dll. Kedua level terhubung ke JcClasspath . Anda tidak dapat memodifikasi kelas yang diambil dari bytecode murni. Jenis dapat dibangun secara manual dengan substitusi generik.
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 ());
}Kotlin
val database = jacodb {
useProcessJavaRuntime()
}
val clazz = database.classpath().findClassOrNull( " java.lang.String " )Contoh yang lebih kompleks:
Jawa
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 ();
}
}Kotlin
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()
} Kelas bisa berada di lingkungan yang tidak lengkap (yaitu kelas super, antarmuka, tipe pengembalian atau parameter metode tidak ditemukan di classpath) maka API akan melempar NoClassInClasspathException saat runtime. Untuk memperbaiki fitur instalasi UnknownClasses ini ke Classpath selama pembuatan.
Database dapat menonton perubahan sistem file di latar belakang dan menyegarkan file jar secara eksplisit:
Jawa
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.Kotlin
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.Jenis dapat direpresentasikan sebagai salah satu dari
Ini mewakili perilaku runtime sesuai dengan substitusi parameter dalam tipe generik yang diberikan:
Jawa
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`
}Kotlin
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`
} Fitur dapat digunakan untuk seluruh JcDatabase atau JcClasspath tertentu
Fitur Basis Data untuk mencari penggunaan bidang dan metode.
val database = jacodb {
install( Usages )
}Lihat lebih banyak
Kelas ejek yang tidak diketahui. Ini adalah cara rahmat untuk menangani classpaths yang tidak lengkap
val database = jacodb {}
val classpath = database.classpath( listOf ( UnknownClasses ))
val clazz = classpath.findClass( " UnknownClasses " ) // will return `JcClassOrInterface` instanceLihat lebih banyak
Hirarki sedikit lebih cepat tetapi mengkonsumsi lebih banyak memori RAM:
val database = jacodb {
install( InMemoryHierarchy )
}Lihat lebih banyak
Contoh JcClassOrInterface , JcMethod , dan JcClasspath aman dan tidak berubah.
JcClasspath mewakili snapshot kelas independen, yang tidak dapat dimodifikasi karena dibuat. Menghapus atau memodifikasi file pustaka tidak mempengaruhi struktur instance JcClasspath . Metode JcClasspath#close merilis semua snapshot dan membersihkan data yang ada jika beberapa perpustakaan sudah ketinggalan zaman.
Jawa
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
}Kotlin
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 Jika ada permintaan untuk instance JcClasspath yang berisi perpustakaan, yang belum diindeks, proses pengindeksan dipicu dan instance baru dari set JcClasspath dikembalikan.
Jawa
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
}Kotlin
val database = jacodb {
loadByteCode( listOf (lib1))
persistent( " " )
}
val cp = database.classpath( listOf (buildDir)) // database will automatically process buildDir JacoDB aman-utas. Jika seseorang meminta instance JcClasspath saat memuat file jar dari utas lain, JcClasspath hanya dapat mewakili keadaan konsisten dari jar-file yang dimuat. Ini adalah file jar muatan yang muncul di JcClasspath . Harap dicatat: Tidak ada jaminan bahwa semua file jar, yang dikirimkan untuk memuat, akan benar-benar dimuat.
Jawa
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 ();
}
}Kotlin
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))
}
}Pemuatan bytecode terdiri dari dua langkah:
JcFeature , dll.) Contoh JacoDB atau JcClasspath dikembalikan tepat setelah langkah pertama dilakukan. Anda mengambil representasi akhir kelas selama langkah kedua. Ada kemungkinan bahwa file .class mengalami perubahan pada saat tertentu antara langkah pertama dan yang kedua, dan representasi kelas dipengaruhi sesuai.
Berikut adalah hasil tolok ukur dan migrasi jelaga.