JacoDB 、JVMプロセスの外側のJava Bytecodeに関する情報を取得し、データベースに保存できる純粋なJavaライブラリです。 JavaのReflectionにより、実行時にコードを検査することができますが、 JacoDBファイルシステムに保存されているBytecodeについても同じことを行います。
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 " )または
メイベン:
< 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には2つのレベルがあります。ファイルシステム(バイトコードとクラス)とランタイム(タイプ)に表示されるもの(タイプ)です。
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 " )より複雑な例:
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 ();
}
}コトリン
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()
}クラスは不完全な環境(つまり、クラスのスーパークラス、インターフェイス、リターンタイプ、またはクラスパスにはありません)にある可能性があります。その後、APIは実行時にNoClassInClasspathExceptionをスローします。このインストールUnknownClasses機能を作成中にClassPathに修正するには。
データベースは、バックグラウンドでファイルシステムの変更を監視し、jarファイルを明示的に更新できます。
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.コトリン
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.タイプはその1つとして表すことができます
指定された汎用タイプのパラメーター置換に従ってランタイムの動作を表します。
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`
}コトリン
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メソッドは、すべてのスナップショットをリリースし、一部のライブラリが時代遅れになった場合、永続的なデータをクリーンアップします。
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
}コトリン
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セットの新しいインスタンスが返されます。
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
}コトリン
val database = jacodb {
loadByteCode( listOf (lib1))
persistent( " " )
}
val cp = database.classpath( listOf (buildDir)) // database will automatically process buildDir JacoDBはスレッドセーフです。別のスレッドからJARファイルをロードしながらJcClasspathインスタンスを要求する場合、 JcClasspath 、ロードされているJARファイルの一貫した状態のみを表すことができます。 JcClasspathに登場するのは、完全にロードされた瓶ファイルです。注:ロード用に提出されたすべての瓶ファイルが実際にロードされるという保証はありません。
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 ();
}
}コトリン
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の読み込みは、2つのステップで構成されています。
JcFeature実装のセットアップなど) JacoDBまたはJcClasspathインスタンスは、最初のステップが実行された直後に返されます。 2番目のステップでクラスの最終表現を取得します。 .classファイルは、最初のステップから2番目のステップの間にある瞬間に変更を受ける可能性があり、クラスの表現がそれに応じて影響を受ける可能性があります。
ベンチマークの結果とSOOTの移動を次に示します。