Подтвердить что ты не робот

Как протестировать вызов 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(), это также будет очень полезно.

4b9b3361

Ответ 1

Class.forName() использует класс-загрузчик класса, из которого он вызван (например, в вашем случае класс, содержащий метод test()). Таким образом, если вы используете его в другой среде, это вызовет проблему.

UPDATE Этот классLoader будет использоваться в Class.forName(), который загрузил ваш класс Test. И это может быть решением: это может быть класс-загрузчик, определенный Eclipse, который имеет доступ к вашему классу, поэтому он будет загружать его. Несмотря на то, что у его родительских (или корневых) загрузчиков классов есть явное правило запретить загрузку этого класса.

Я по-прежнему рекомендую создать класс-оболочку для этого экземпляра. Вы должны загрузить этот класс с помощью CustomTestClassLoader, тогда вы можете использовать Class.forName() в этом классе.