Javaのクラスを別のパッケージ名で動的に読み込む
-
04-10-2019 - |
質問
クラスをJavaにロードし、クラスのパッケージ名/標準名を「偽造」することは可能ですか?私はこれを試してみましたが、明らかな方法ですが、「クラス名は一致しない」メッセージが表示されます。 ClassDefNotFoundException
.
私がこれをしている理由は、リフレクションを使用せずに直接使用できるように、デフォルトパッケージに記述されたAPIをロードしようとしているためです。コードは、パッケージとパッケージ名のインポートを表すフォルダー構造でクラスに対してコンパイルします。つまり:
./com/DefaultPackageClass.class
// ...
import com.DefaultPackageClass;
import java.util.Vector;
// ...
私の現在のコードは次のとおりです。
public Class loadClass(String name) throws ClassNotFoundException {
if(!CLASS_NAME.equals(name))
return super.loadClass(name);
try {
URL myUrl = new URL(fileUrl);
URLConnection connection = myUrl.openConnection();
InputStream input = connection.getInputStream();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int data = input.read();
while(data != -1){
buffer.write(data);
data = input.read();
}
input.close();
byte[] classData = buffer.toByteArray();
return defineClass(CLASS_NAME,
classData, 0, classData.length);
} catch (MalformedURLException e) {
throw new UndeclaredThrowableException(e);
} catch (IOException e) {
throw new UndeclaredThrowableException(e);
}
}
解決
ピートが述べたように, 、これは、ASM ByteCodeライブラリを使用して実行できます。実際、そのライブラリは実際には、これらのクラス名の再マッピングを処理するために特にクラスが出荷されます(RemappingClassAdapter
)。このクラスを使用したクラスローダーの例は次のとおりです。
public class MagicClassLoader extends ClassLoader {
private final String defaultPackageName;
public MagicClassLoader(String defaultPackageName) {
super();
this.defaultPackageName = defaultPackageName;
}
public MagicClassLoader(String defaultPackageName, ClassLoader parent) {
super(parent);
this.defaultPackageName = defaultPackageName;
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
byte[] bytecode = ...; // I will leave this part up to you
byte[] remappedBytecode;
try {
remappedBytecode = rewriteDefaultPackageClassNames(bytecode);
} catch (IOException e) {
throw new RuntimeException("Could not rewrite class " + name);
}
return defineClass(name, remappedBytecode, 0, remappedBytecode.length);
}
public byte[] rewriteDefaultPackageClassNames(byte[] bytecode) throws IOException {
ClassReader classReader = new ClassReader(bytecode);
ClassWriter classWriter = new ClassWriter(classReader, 0);
Remapper remapper = new DefaultPackageClassNameRemapper();
classReader.accept(
new RemappingClassAdapter(classWriter, remapper),
0
);
return classWriter.toByteArray();
}
class DefaultPackageClassNameRemapper extends Remapper {
@Override
public String map(String typeName) {
boolean hasPackageName = typeName.indexOf('.') != -1;
if (hasPackageName) {
return typeName;
} else {
return defaultPackageName + "." + typeName;
}
}
}
}
説明するために、2つのクラスを作成しましたが、どちらもデフォルトのパッケージに属します。
public class Customer {
}
と
public class Order {
private Customer customer;
public Order(Customer customer) {
this.customer = customer;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
これがのリストです Order
前 再マッピング:
> javap -private -c Order Compiled from "Order.java" public class Order extends java.lang.Object{ private Customer customer; public Order(Customer); Code: 0: aload_0 1: invokespecial #10; //Method java/lang/Object."":()V 4: aload_0 5: aload_1 6: putfield #13; //Field customer:LCustomer; 9: return public Customer getCustomer(); Code: 0: aload_0 1: getfield #13; //Field customer:LCustomer; 4: areturn public void setCustomer(Customer); Code: 0: aload_0 1: aload_1 2: putfield #13; //Field customer:LCustomer; 5: return }
これがのリストです Order
後 再マッピング(使用 com.mycompany
デフォルトのパッケージとして):
> javap -private -c Order Compiled from "Order.java" public class com.mycompany.Order extends com.mycompany.java.lang.Object{ private com.mycompany.Customer customer; public com.mycompany.Order(com.mycompany.Customer); Code: 0: aload_0 1: invokespecial #30; //Method "com.mycompany.java/lang/Object"."":()V 4: aload_0 5: aload_1 6: putfield #32; //Field customer:Lcom.mycompany.Customer; 9: return public com.mycompany.Customer getCustomer(); Code: 0: aload_0 1: getfield #32; //Field customer:Lcom.mycompany.Customer; 4: areturn public void setCustomer(com.mycompany.Customer); Code: 0: aload_0 1: aload_1 2: putfield #32; //Field customer:Lcom.mycompany.Customer; 5: return }
ご覧のとおり、再マッピングはすべて変更されました Order
への参照 com.mycompany.Order
そしてすべて Customer
への参照 com.mycompany.Customer
.
このクラスローダーは、次のようなすべてのクラスをロードする必要があります。
- デフォルトのパッケージに属します
- デフォルトのパッケージに属する他のクラスを使用します。
他のヒント
何かをノックアップできるはずです ASM, 、ただし、ロード時よりもビルド時に1回パッケージの名前を変更する方が簡単です。
APIをデフォルトのパッケージからよりリーズナブルな場所に移動する方が簡単なのでしょうか?ソースコードにアクセスできないようです。パッケージがクラスファイルにエンコードされているかどうかはわかりませんので、APIクラスを単純に移動するだけで試してみる価値があります。それ以外の場合は、JadのようなJavaの逆コンパイラーが通常良い仕事をしているので、分解されたソースのパッケージ名を変更して再びコンパイルすることができます。