Искаженное имя класса пакета Java при загрузке исходного кода во время выполнения
У меня есть компилятор времени выполнения Java следующим образом:
public class Compiler {
private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
private final Map<String, String> source = new HashMap<String, String>();
private final MemoryFileManager manager = new MemoryFileManager(this.compiler);
public void add(String classname, String fileContent) {
add(Collections.singletonMap(classname, fileContent));
}
public void add(Map<String, String> map) {
source.putAll(map);
}
public void compile() {
List<Source> list = new ArrayList<Source>();
for (Map.Entry<String, String> entry : source.entrySet()) {
list.add(new Source(entry.getKey(), JavaFileObject.Kind.SOURCE, entry.getValue()));
}
this.compiler.getTask(null, this.manager, null, null, null, list).call();
}
public byte[] getByteCode(String name) {
return this.manager.map.get(name).toByteArray();
}
}
И сопровождающий пример простого Classloader:
public class SimpleClassLoader extends ClassLoader {
private String classname;
private byte[] byteCode;
public SimpleClassLoader(String classname, byte[] byteCode) {
super(SimpleClassLoader.class.getClassLoader());
this.classname = classname;
this.byteCode = byteCode;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if(name.equals(classname)){
return defineClass(name, byteCode, 0, byteCode.length);
}
return super.findClass(name);
}
}
При компиляции и загрузке класса имя пакета результирующего класса объединяется в имя класса:
Compiler compiler = new Compiler();
String className = "example.test.TestClass";
String source = "package example.test; public class TestClass{}";
compiler.add(className, source);
compiler.compile();
byte[] byteCode = compiler.getByteCode(className);
Class<?> aClass = Class.forName(className, true, new SimpleClassLoader(className, byteCode));
System.out.println("Package: " + aClass.getPackage()); // Should be "example.text"
System.out.println("Name: " + aClass.getSimpleName()); // Should be "TestClass"
Результат этого неожиданно следующий:
Package: null
Name: TestClass
Куда я здесь не так?
2 ответа
example.test
пакет не определен и должен был быть инициализирован вызовом definePackage()
в методе компиляции:
public void compile() {
List<Source> list = new ArrayList<Source>();
for (Map.Entry<PackageClass, String> entry : source.entrySet()) {
list.add(new Source(entry.getKey().getCanonicalName(), JavaFileObject.Kind.SOURCE, entry.getValue()));
if(getPackage(entry.getKey().getPackage()) == null) {
definePackage(entry.getKey().getPackage(), null, null, null, null, null, null, null);
}
}
this.compiler.getTask(null, this.manager, null, null, null, list).call();
}
Результат aClass.getName()
верно. Если вы хотите, чтобы программа возвратила "TestClass", вы должны использовать aClass.getSimpleName()
,
Я проверяю исходный код java.lang.Class.getPackage()
, Это вызовет Package.getPackage(this)
которые вызывают class.getClassLoader().getPackage("example.test")
в твоем случае. Поскольку SimpleClassLoader
имеет метод переопределения protected Class findClass(String name)
Вы также должны переопределить метод protected Package getPackage(String name)
и вернуть объект, который вы исключили.