パッケージjava.lang.classfile.components
java.lang.classfile.components
は、JavaプラットフォームのプレビューAPIです。
java.lang.classfile
PREVIEWライブラリ上に構築された特定のコンポーネント、変換およびツールを提供します。
java.lang.classfile.components
パッケージには、最小限の労力で非常に複雑なタスクを構成するのに役立つ特定の変換コンポーネントおよびユーティリティ・クラスが含まれています。
ClassPrinter
PREVIEW
ClassPrinter
PREVIEWは、ClassModel
PREVIEW、FieldModel
PREVIEW、MethodModel
PREVIEWまたはCodeModel
PREVIEWを、JSON、XMLまたはYAML形式の人間が読める構造化テキストに、またはトラバース可能なノードと印刷可能なノードのツリーにシームレスにエクスポートするヘルパー・クラスです。
ClassPrinter
PREVIEWの主な目的は、デバッグ、例外処理およびロギングの目的で人間が読めるクラス情報を提供することです。 また、印刷クラスは、自動オフライン処理をサポートするために標準形式に準拠しています。
最も頻繁なユース・ケースは、単にクラスを出力することです。
ClassPrinter.toJson(classModel, ClassPrinter.Verbosity.TRACE_ALL, System.out::print);
ClassPrinter
PREVIEWを使用すると、単純な印刷可能ノードのツリーを横断してカスタム・プリンタをフックできます:
void customPrint(ClassModel classModel) {
print(ClassPrinter.toTree(classModel, ClassPrinter.Verbosity.TRACE_ALL));
}
void print(ClassPrinter.Node node) {
switch (node) {
case ClassPrinter.MapNode mn -> {
// print map header
mn.values().forEach(this::print);
}
case ClassPrinter.ListNode ln -> {
// print list header
ln.forEach(this::print);
}
case ClassPrinter.LeafNode n -> {
// print leaf node
}
}
}
ClassPrinter
PREVIEWのもう1つのユースケースは、自動テストの記述を簡略化することです:
@Test
void printNodesInTest(ClassModel classModel) {
var classNode = ClassPrinter.toTree(classModel, ClassPrinter.Verbosity.TRACE_ALL);
assertContains(classNode, "method name", "myFooMethod");
assertContains(classNode, "field name", "myBarField");
assertContains(classNode, "inner class", "MyInnerFooClass");
}
void assertContains(ClassPrinter.Node node, ConstantDesc key, ConstantDesc value) {
if (!node.walk().anyMatch(n -> n instanceof ClassPrinter.LeafNode ln
&& ln.name().equals(key)
&& ln.value().equals(value))) {
node.toYaml(System.out::print);
throw new AssertionError("expected %s: %s".formatted(key, value));
}
}
ClassRemapper
PREVIEW
ClassRemapperは、指定されたマップまたはマップ関数に従って、ClassTransform
PREVIEW、FieldTransform
PREVIEW、MethodTransform
PREVIEWおよびCodeTransform
PREVIEWですべてのクラス参照を任意の形式で深く再マッピングします。
再マッピングは、スーパークラス、インタフェース、あらゆる種類の記述子およびシグネチャ、クラスを参照するすべての属性((すべてのタイプの注釈を含む))およびクラスを参照するすべての命令に適用されます。
プリミティブ型および配列は、マッピングの対象ではなく、マッピングのターゲットとして許可されません。
参照型の配列は、常に分解され、ベース参照型としてマップされ、配列に構成されます。
単一クラスの再マッピングの例:
var classRemapper = ClassRemapper.of(
Map.of(CD_Foo, CD_Bar));
var cc = ClassFile.of();
for (var classModel : allMyClasses) {
byte[] newBytes = classRemapper.remapClass(cc, classModel);
}
特定のパッケージのすべてのクラスの再マッピング:
var classRemapper = ClassRemapper.of(cd ->
ClassDesc.ofDescriptor(cd.descriptorString().replace("Lcom/oldpackage/", "Lcom/newpackage/")));
var cc = ClassFile.of();
for (var classModel : allMyClasses) {
byte[] newBytes = classRemapper.remapClass(cc, classModel);
}
CodeLocalsShifter
PREVIEW
CodeLocalsShifter
PREVIEWは、コード・インジェクション中の競合を回避するために、ローカルを新しく割り当てられた位置に移動するCodeTransform
PREVIEWです。 受信側またはメソッド引数スロットを指すローカルは、決してシフトされません。 メソッド引数以外を指すすべてのローカル・オブジェクトは、出現順に再索引付けされます。
すべてのメソッドのすべてのロケールをシフトするコード変換のサンプル:
byte[] newBytes = ClassFile.of().transform(
classModel,
(classBuilder, classElement) -> {
if (classElement instanceof MethodModel method)
classBuilder.transformMethod(method,
MethodTransform.transformingCode(
CodeLocalsShifter.of(method.flags(), method.methodTypeSymbol())));
else
classBuilder.accept(classElement);
});
CodeRelabeler
PREVIEW
CodeRelabeler
PREVIEWは、変換されたコード内のすべてのLabel
PREVIEWを新しいインスタンスで置き換えるCodeTransform
PREVIEWです。 すべてのLabelTarget
PREVIEW命令が適宜調整されます。 ラベルが変更されたコード・グラフは元のものと同じです。
CodeRelabeler
PREVIEWの主な目的は、同じコード・ブロックを繰り返しインジェクションすることです。 同じコード・ブロックの繰返しインジェクションにラベルを付け直す必要があるため、Label
PREVIEWの各インスタンスはターゲット・バイトコードに1回のみバインドされます。
すべてのメソッドのラベルを変更するサンプル変換:
byte[] newBytes = ClassFile.of().transform(
classModel,
ClassTransform.transformingMethodBodies(
CodeTransform.ofStateful(CodeRelabeler::of)));
クラス計測のサンプル
次のスニペットは、完全に機能するクラス・インストゥルメント変換へのClassRemapper
PREVIEW、CodeLocalsShifter
PREVIEWおよびCodeRelabeler
PREVIEWのサンプル構成です:
byte[] classInstrumentation(ClassModel target, ClassModel instrumentor, Predicate<MethodModel> instrumentedMethodsFilter) {
var instrumentorCodeMap = instrumentor.methods().stream()
.filter(instrumentedMethodsFilter)
.collect(Collectors.toMap(mm -> mm.methodName().stringValue() + mm.methodType().stringValue(), mm -> mm.code().orElseThrow()));
var targetFieldNames = target.fields().stream().map(f -> f.fieldName().stringValue()).collect(Collectors.toSet());
var targetMethods = target.methods().stream().map(m -> m.methodName().stringValue() + m.methodType().stringValue()).collect(Collectors.toSet());
var instrumentorClassRemapper = ClassRemapper.of(Map.of(instrumentor.thisClass().asSymbol(), target.thisClass().asSymbol()));
return ClassFile.of().transform(target,
ClassTransform.transformingMethods(
instrumentedMethodsFilter,
(mb, me) -> {
if (me instanceof CodeModel targetCodeModel) {
var mm = targetCodeModel.parent().get();
//instrumented methods code is taken from instrumentor
mb.transformCode(instrumentorCodeMap.get(mm.methodName().stringValue() + mm.methodType().stringValue()),
//all references to the instrumentor class are remapped to target class
instrumentorClassRemapper.asCodeTransform()
.andThen((codeBuilder, instrumentorCodeElement) -> {
//all invocations of target methods from instrumentor are inlined
if (instrumentorCodeElement instanceof InvokeInstruction inv
&& target.thisClass().asInternalName().equals(inv.owner().asInternalName())
&& mm.methodName().stringValue().equals(inv.name().stringValue())
&& mm.methodType().stringValue().equals(inv.type().stringValue())) {
//store stacked method parameters into locals
var storeStack = new ArrayDeque<StoreInstruction>();
int slot = 0;
if (!mm.flags().has(AccessFlag.STATIC))
storeStack.push(StoreInstruction.of(TypeKind.ReferenceType, slot++));
for (var pt : mm.methodTypeSymbol().parameterList()) {
var tk = TypeKind.from(pt);
storeStack.push(StoreInstruction.of(tk, slot));
slot += tk.slotSize();
}
storeStack.forEach(codeBuilder::with);
//inlined target locals must be shifted based on the actual instrumentor locals
codeBuilder.block(inlinedBlockBuilder -> inlinedBlockBuilder
.transform(targetCodeModel, CodeLocalsShifter.of(mm.flags(), mm.methodTypeSymbol())
.andThen(CodeRelabeler.of())
.andThen((innerBuilder, shiftedTargetCode) -> {
//returns must be replaced with jump to the end of the inlined method
if (shiftedTargetCode instanceof ReturnInstruction)
innerBuilder.goto_(inlinedBlockBuilder.breakLabel());
else
innerBuilder.with(shiftedTargetCode);
})));
} else
codeBuilder.with(instrumentorCodeElement);
}));
} else
mb.with(me);
})
.andThen(ClassTransform.endHandler(clb ->
//remaining instrumentor fields and methods are injected at the end
clb.transform(instrumentor,
ClassTransform.dropping(cle ->
!(cle instanceof FieldModel fm
&& !targetFieldNames.contains(fm.fieldName().stringValue()))
&& !(cle instanceof MethodModel mm
&& !ConstantDescs.INIT_NAME.equals(mm.methodName().stringValue())
&& !targetMethods.contains(mm.methodName().stringValue() + mm.methodType().stringValue())))
//and instrumentor class references remapped to target class
.andThen(instrumentorClassRemapper)))));
}
- 導入されたバージョン:
- 22
-
クラス説明Preview.クラス・ファイルとその要素のプリンタ。Preview.単一の印刷可能な値を保持するリーフ・ノード。Preview.ネストされたノードの
List
を保持するツリー・ノード。Preview.ネストされたノードのMap
を保持するツリー・ノード。Preview.名前付き、トラバース可能および印刷可能なノードの親。Preview.印刷またはエクスポートする詳細のレベル。Preview.ClassRemapper
は、指定されたマップまたはマップ関数に従って、ClassTransform
PREVIEW、FieldTransform
PREVIEW、MethodTransform
PREVIEWおよびCodeTransform
PREVIEWですべてのクラス参照を任意の形式で深く再マッピングします。Preview.CodeLocalsShifter
PREVIEWは、コード・インジェクション中の競合を回避するために、ローカルを新しく割り当てられた位置に移動するCodeTransform
PREVIEWです。Preview.コード・リラベラは、変換されたコード内のすべてのLabel
PREVIEWを新しいインスタンスで置き換えるCodeTransform
PREVIEWです。Preview.CodeStackTracker
PREVIEWは、CodeTransform
PREVIEWトラッキング・スタック・コンテンツであり、最大スタック・サイズを計算します。
java.lang.classfile.components
を使用できます。