JacoDB เป็นไลบรารี Java บริสุทธิ์ที่ให้คุณได้รับข้อมูลเกี่ยวกับ Java bytecode นอกกระบวนการ JVM และเก็บไว้ในฐานข้อมูล ในขณะที่ Java Reflection ทำให้สามารถตรวจสอบรหัสที่รันไทม์ได้ JacoDB ก็ทำเช่นเดียวกันกับ bytecode ที่เก็บไว้ในระบบไฟล์
JacoDB ใช้ ASM Framework สำหรับการอ่านและแยกวิเคราะห์จาวาไบต์
ข้อมูลเกี่ยวกับคลาสลำดับชั้นคำอธิบายประกอบวิธีการฟิลด์และการใช้งานของพวกเขาจะถูกเก็บไว้ในฐานข้อมูล 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 มีสองระดับ: หนึ่งที่เป็นตัวแทนในระบบไฟล์ ( bytecode และ คลาส ) และระดับที่ปรากฏในรันไทม์ ( ประเภท )
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 ());
}Kotlin
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 ();
}
}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()
} ชั้นเรียนอาจอยู่ในสภาพแวดล้อมที่ไม่สมบูรณ์ (เช่น Super Class, อินเตอร์เฟส, ประเภทการส่งคืนหรือพารามิเตอร์ของวิธีการไม่พบใน classPath) จากนั้น API จะโยน 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.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.ประเภท สามารถแสดงเป็นหนึ่งใน
มันแสดงถึงพฤติกรรมรันไทม์ตามการแทนที่พารามิเตอร์ในประเภททั่วไปที่กำหนด:
ชวา
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`
} คุณสมบัติสามารถใช้สำหรับ JcDatabase ทั้งหมดหรือ JcClasspath โดยเฉพาะ
คุณลักษณะฐานข้อมูลสำหรับการค้นหาประเพณีของฟิลด์และวิธีการ
val database = jacodb {
install( Usages )
}ดูเพิ่มเติม
การเยาะเย้ยชั้นเรียนที่ไม่รู้จัก เป็นวิธีที่สง่างามในการจัดการกับ ClassPaths ที่ไม่สมบูรณ์
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 จะปล่อยสแน็ปช็อตทั้งหมดและทำความสะอาดข้อมูลที่คงอยู่หากไลบรารีบางแห่งล้าสมัย
ชวา
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 หากมีการร้องขออินสแตนซ์ 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
}Kotlin
val database = jacodb {
loadByteCode( listOf (lib1))
persistent( " " )
}
val cp = database.classpath( listOf (buildDir)) // database will automatically process buildDir JacoDB เป็นเธรดที่ปลอดภัย หากมีการร้องขออินสแตนซ์ JcClasspath ในขณะที่โหลดไฟล์ jar จากเธรดอื่น JcClasspath สามารถแสดงเพียงสถานะที่สอดคล้องกันของไฟล์ jar-files ที่ถูกโหลด มันเป็นไฟล์ jar ที่โหลดอย่างสมบูรณ์ที่ปรากฏใน JcClasspath โปรดทราบ: ไม่มีการรับประกันว่าไฟล์ jar ทั้งหมดที่ส่งมาสำหรับการโหลดจะถูกโหลดจริง
ชวา
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))
}
}การโหลด Bytecode ประกอบด้วยสองขั้นตอน:
JcFeature ฯลฯ ) อินสแตนซ์ของ JacoDB หรือ JcClasspath จะถูกส่งกลับทันทีหลังจากดำเนินการขั้นตอนแรก คุณเรียกคืนการเป็นตัวแทนขั้นสุดท้ายของ ชั้นเรียน ในระหว่างขั้นตอนที่สอง เป็นไปได้ว่าไฟล์ .class ได้รับการเปลี่ยนแปลงในบางช่วงเวลาระหว่างขั้นตอนแรกและที่สองและการแสดง คลาส จะได้รับผลกระทบตามลำดับ
นี่คือผลการวัดประสิทธิภาพและการโยกย้ายเขม่า