JacoDB ist eine reine Java -Bibliothek, mit der Sie Informationen über Java -Bytecode außerhalb des JVM -Prozesses erhalten und in einer Datenbank speichern können. Während Java Reflection es ermöglicht, den Code zur Laufzeit zu inspizieren, tut JacoDB dasselbe für Bytecode, das in einem Dateisystem gespeichert ist.
JacoDB verwendet ASM Framework zum Lesen und Parsen von Java -Bytecode.
Informationen zu Klassen, Hierarchien, Anmerkungen, Methoden, Feldern und ihrer Verwendung werden in der SQLite-Datenbank gespeichert-entweder in Memory oder persistent. Anhaltende Daten können zwischen Neustarts wiederverwendet werden. Der Zugriff auf den gleichzeitigen Speicher auf die anhaltende Speicherung aus mehreren Prozessen wird nicht unterstützt.
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 " )oder
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 hat zwei Ebenen: die im Dateisystem ( Bytecode und Klassen ) und das, die zur Laufzeit ( Typen ) erscheint.
class darstellen: Klasse mit Methoden, Feldern usw. Beide Ebenen sind mit JcClasspath verbunden. Sie können keine Klassen ändern, die aus reinem Bytecode abgerufen werden. Typen können manuell durch Generika -Substitution konstruiert werden.
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 " )Komplexere Beispiele:
Java
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()
} Die Klasse könnte in unvollständiger Umgebung sein (dh Super -Klasse, Schnittstelle, Rückgabetyp oder Methodenparameter ist nicht im Klassenpfad). Dann wirft API zur Laufzeit NoClassInClasspathException . So beheben Sie diese Installation UnknownClasses Klassen in Klassenpfad während der Erstellung.
Die Datenbank kann nach Dateisystemänderungen im Hintergrund achten und die JAR-Files explizit aktualisieren:
Java
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.Typ kann als einer von dargestellt werden
Es repräsentiert das Laufzeitverhalten gemäß der Parametersubstitution im angegebenen generischen Typ:
Java
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`
} Funktionen können für die ganze JcDatabase oder ein bestimmtes JcClasspath verwendet werden
Datenbankfunktion zum Durchsuchen von Feldern und Methoden.
val database = jacodb {
install( Usages )
}Sehen Sie mehr
Mocks Unbekannte Klassen. Es ist eine Gnade, unvollständige Klassenpfade zu behandeln
val database = jacodb {}
val classpath = database.classpath( listOf ( UnknownClasses ))
val clazz = classpath.findClass( " UnknownClasses " ) // will return `JcClassOrInterface` instanceSehen Sie mehr
Etwas schneller Hierarchie, aber mehr RAM -Speicher verbrauchen:
val database = jacodb {
install( InMemoryHierarchy )
}Sehen Sie mehr
Die Instanzen von JcClassOrInterface , JcMethod und JcClasspath sind threadssicher und unveränderlich.
JcClasspath stellt eine unabhängige Schnappschuss von Klassen dar, die nicht geändert werden kann, da er erstellt wird. Das Entfernen oder Ändern von Bibliotheksdateien wirkt sich nicht auf JcClasspath -Instanzstruktur aus. Die JcClasspath#close -Methode veröffentlicht alle Schnappschüsse und säubert die anhaltenden Daten, wenn einige Bibliotheken veraltet sind.
Java
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 Wenn eine Anfrage nach einer JcClasspath -Instanz mit den noch nicht indizierten Bibliotheken vorliegt, wird der Indexierungsprozess ausgelöst und die neue Instanz des JcClasspath -Sets zurückgegeben.
Java
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 ist Thread-Safe. Wenn man JcClasspath Instanz beim Laden von Jar-Files aus einem anderen Thread anfordert, kann JcClasspath nur einen konsistenten Zustand der zu geladenen Jar-Files darstellen. Es ist die vollständig beladene Jar-Datei, die in JcClasspath erscheint. Bitte beachten Sie: Es gibt keine Garantie dafür, dass alle für das Laden eingereichten Jar-Files tatsächlich geladen werden.
Java
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))
}
}Die Bytecode -Belastung besteht aus zwei Schritten:
JcFeature Implementierungen usw.) JacoDB oder JcClasspath -Instanzen werden direkt nach dem ersten Schritt zurückgegeben. Sie holen die endgültige Darstellung von Klassen im zweiten Schritt ab. Es ist möglich, dass die .class -Dateien zwischen dem ersten und dem zweiten und der Klassendarstellung entsprechend geändert werden.
Hier finden Sie Benchmarks -Ergebnisse und Rußmigration.