JacoDB هي مكتبة Java نقية تتيح لك الحصول على معلومات حول Java Bytecode خارج عملية JVM وتخزينها في قاعدة بيانات. على الرغم من أن Java Reflection يجعل من الممكن فحص التعليمات البرمجية في وقت التشغيل ، فإن JacoDB يفعل نفس الشيء بالنسبة لـ Bytecode المخزنة في نظام الملفات.
يستخدم JacoDB إطار عمل ASM لقراءة وتحليل Java Bytecode.
يتم تخزين المعلومات حول الفصول والتسلسلات الهرمية والشروح والأساليب والحقول واستخداماتها في قاعدة بيانات 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 " )أو
مافن:
< 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 and Classes ) والواحد الذي يظهر في وقت التشغيل ( الأنواع ).
class : فئة مع الأساليب ، الحقول ، إلخ. يتم توصيل كلا المستويين بـ JcClasspath . لا يمكنك تعديل الفصول التي تم استردادها من رمز bytecode النقي. قد يتم بناء الأنواع يدويًا عن طريق استبدال الأدوية الجيلية.
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()
} يمكن أن يكون الفصل في بيئة غير مكتملة (أي فئة فائقة أو واجهة أو نوع الإرجاع أو معلمة الطريقة غير موجودة في ClassPath) ثم سوف يرمي API NoClassInClasspathException في وقت التشغيل. لإصلاح هذا التثبيت ميزة UnknownClasses في classpath أثناء الإنشاء.
يمكن لقاعدة البيانات مشاهدة تغييرات نظام الملفات في الخلفية وتحديث ملفات الجرة بشكل صريح:
جافا
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رؤية المزيد
تسلسل هرمي أسرع قليلاً ولكن يستهلك المزيد من ذاكرة ذاكرة الوصول العشوائي:
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
}كوتلين
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 آمن مؤشر الترابط. إذا طلب أحدهم مثيل JcClasspath أثناء تحميل ملفات الجرة من مؤشر ترابط آخر ، يمكن أن تمثل JcClasspath فقط حالة متسقة من ملفات الجرة التي يتم تحميلها. إنه ملف الجرة المحمّل بالكامل الذي يظهر في JcClasspath . يرجى ملاحظة: ليس هناك ما يضمن أن يتم تحميل جميع ملفات الجرة ، المقدمة للتحميل ، بالفعل.
جافا
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))
}
}يتكون تحميل Bytecode من خطوتين:
JcFeature ، وما إلى ذلك) يتم إرجاع مثيلات JacoDB أو JcClasspath مباشرة بعد إجراء الخطوة الأولى. يمكنك استرداد التمثيل النهائي للفئات خلال الخطوة الثانية. من الممكن أن تخضع ملفات .class لتغييرات في لحظة ما بين الخطوة الأولى والثانية ، ويتأثر تمثيل الفئات وفقًا لذلك.
فيما يلي نتائج المعايير وترحيل السخام.