Как протестировать вызов Class.forName в Java-коде?
Я недавно общался с ClassLoaders в java, пытаясь проверить код, который использует динамическую загрузку классов (используя Class.forName(String name)
) с пользовательским ClassLoader
.
У меня есть собственный пользовательский ClassLoader
, который должен быть настроен для того, чтобы бросать ClassNotFoundException
при попытке загрузить данный класс.
public class CustomTestClassLoader extends ClassLoader {
private static String[] notAllowed = new String[]{};
public static void setNotAllowed(String... nonAllowedClassNames) {
notAllowed = nonAllowedClassNames;
}
public static String[] getNotAllowed() {
return notAllowed;
}
public CustomTestClassLoader(ClassLoader parent){super(parent);}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
for (String s : notAllowed) {
if (name.equals(s)) {
throw new ClassNotFoundException("Loading this class is not allowed for testing purposes.");
}
}
if(name.startsWith("java") || name.startsWith("sun") || getClass().getName().equals(name)) {
return getParent().loadClass(name);
}
Class<?> gotOne = super.findLoadedClass(name);
if (gotOne != null) {
return gotOne;
}
Class<?> c;
InputStream in = getParent().getResourceAsStream(name.replace('.', '/')+".class");
if (in == null) {
throw new ClassNotFoundException("Couldn't locate the classfile: "+name);
}
try {
byte[] classData = readBytes(in);
c = defineClass(name, classData, 0, classData.length);
} catch(IOException e) {
throw new ClassNotFoundException("Couldn't read the class data.", e);
} finally {
try {
in.close();
} catch (IOException e) {/* not much we can do at this point */}
}
if (resolve) {
resolveClass(c);
}
return c;
}
private byte[] readBytes(InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[4194304];
int read = in.read(buffer);
while (read != -1) {
out.write(buffer, 0, read);
read = in.read(buffer);
}
out.close();
return out.toByteArray();
}
}
Я использую -Djava.system.class.loader=com.classloadertest.test.CustomTestClassLoader
для установки этого ClassLoader
по умолчанию ClassLoader
.
Я надеялся, что смогу сделать ClassNotFoundException
, отказавшись от определенных имен классов, используя CustomTestClassLoader.setNotAllowed(String...)
.
Однако он работает только для ClassLoader.loadClass
, а не для Class.forName
:
public void test() {
ClassLoader loader = this.getClass().getClassLoader();
CustomTestClassLoader custom = (CustomTestClassLoader)loader;
CustomTestClassLoader.setNotAllowed(NAME);
for (String s : custom.getNotAllowed())
System.out.println("notAllowed: "+s);
try {
System.out.println(Class.forName(NAME));
} catch (ClassNotFoundException e) {
System.out.println("forName(String) failed");
}
try {
System.out.println(Class.forName(NAME,false,custom));
} catch (ClassNotFoundException e) {
System.out.println("forName(String,boolean,ClassLoader) failed");
}
try {
System.out.println(custom.loadClass(NAME));
} catch (ClassNotFoundException e) {
System.out.println("ClassLoader.loadClass failed");
}
}
Теперь я ожидал, что все три блока try будут терпеть неудачу, поскольку в документации Class.forName
говорится, что он использует ClassLoader
вызывающего (который должен быть настраиваемым/загрузчиком в этом тесте).
Однако завершается только последний блок try. Вот результат, который я получаю:
notAllowed: com.classloadertest.test.Test
class com.classloadertest.test.Test
class com.classloadertest.test.Test
ClassLoader.loadClass failed
Действительно ли Class.forName
использует ClassLoader
? И если да, то какие методы?
Кажется, он использует собственный вызов, поэтому я понятия не имею, что он делает под обложками.
Конечно, если кто-нибудь знает какие-либо альтернативные способы тестирования вызова Class.forName()
, это также будет очень полезно.
Ответы
Ответ 1
Class.forName()
использует класс-загрузчик класса, из которого он вызван (например, в вашем случае класс, содержащий метод test()
). Таким образом, если вы используете его в другой среде, это вызовет проблему.
UPDATE Этот классLoader будет использоваться в Class.forName()
, который загрузил ваш класс Test
. И это может быть решением: это может быть класс-загрузчик, определенный Eclipse, который имеет доступ к вашему классу, поэтому он будет загружать его. Несмотря на то, что у его родительских (или корневых) загрузчиков классов есть явное правило запретить загрузку этого класса.
Я по-прежнему рекомендую создать класс-оболочку для этого экземпляра. Вы должны загрузить этот класс с помощью CustomTestClassLoader
, тогда вы можете использовать Class.forName()
в этом классе.