В Java можно узнать, был ли уже загружен класс?

Можно ли узнать, загружен ли класс Java, не пытаясь его загрузить? Class.forName пытается загрузить класс, но мне не нужен этот побочный эффект. Есть ли другой способ?

(Я не хочу переопределять загрузчик классов. Я ищу относительно простой метод.)


person Hosam Aly    schedule 27.01.2009    source источник


Ответы (5)


(Спасибо Алекси) Этот код:

public class TestLoaded {
     public static void main(String[] args) throws Exception {
          java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod("findLoadedClass", new Class[] { String.class });
          m.setAccessible(true);
          ClassLoader cl = ClassLoader.getSystemClassLoader();
          Object test1 = m.invoke(cl, "TestLoaded$ClassToTest");
          System.out.println(test1 != null);
          ClassToTest.reportLoaded();
          Object test2 = m.invoke(cl, "TestLoaded$ClassToTest");
          System.out.println(test2 != null);
     }
     static class ClassToTest {
          static {
               System.out.println("Loading " + ClassToTest.class.getName());
          }
          static void reportLoaded() {
               System.out.println("Loaded");
          }
     }
}

Производит:

false
Loading TestLoaded$ClassToTest
Loaded
true

Обратите внимание, что примеры классов не находятся в пакете. Требуется полное бинарное имя.

Пример двоичного имени: "java.security.KeyStore$Builder$FileBuilder$1".

person Stephen Denne    schedule 27.01.2009
comment
Просто примечание: не забудьте использовать каноническое имя package.subpackage.ClassName при вызове метода. Этот ответ не демонстрирует это требование, поскольку класс не находится в пакете. - person Aleksi Yrttiaho; 27.01.2009
comment
Спасибо за добавление этого примечания. Хотя вам и нужен пакет, требуется именно двоичное имя. Каноническое имя класса, показанного в примере, TestLoaded.ClassToTest, которое отличается от двоичного имени... Я отредактирую ответ, чтобы уточнить/связать. - person Stephen Denne; 27.01.2009
comment
Спасибо @spdenne и @Aleksi. Я пытался сделать это, но это не удалось, хотя я не уверен, почему. Ваш код выше работает для меня. В качестве примечания вы можете проверить ответ Макдауэлла об инструментах. Большое спасибо. - person Hosam Aly; 27.01.2009
comment
Thread.currentThread().getClassLoader() следует использовать для получения текущего загрузчика классов. не все классы загружаются системным загрузчиком классов. - person Martin Kersten; 05.10.2015
comment
Это перестанет работать с системой модулей платформы Java без недопустимого доступа. - person Luna; 04.08.2017

Вы можете использовать findLoadedClass(String) в ClassLoader. Он возвращает null, если класс не загружен.

person Aleksi Yrttiaho    schedule 27.01.2009
comment
К сожалению, findLoadedClasses защищен, а это означает, что вам придется создать подкласс ClassLoader, чтобы получить к нему доступ. - person staffan; 27.01.2009
comment
Можно ли вызвать этот метод через отражение? (Подавление проверок безопасности) - person Hosam Aly; 27.01.2009

Один из способов сделать это — написать агент Java, используя ссылку инструментальный API. Это позволит вам записывать загрузку классов JVM.

public class ClassLoadedAgent implements ClassFileTransformer {

    private static ClassLoadedAgent AGENT = null;

    /** Agent "main" equivalent */
    public static void premain(String agentArguments,
            Instrumentation instrumentation) {
        AGENT = new ClassLoadedAgent();
        for (Class<?> clazz : instrumentation.getAllLoadedClasses()) {
            AGENT.add(clazz);
        }
        instrumentation.addTransformer(AGENT);
    }

    private final Map<ClassLoader, Set<String>> classMap = new WeakHashMap<ClassLoader, Set<String>>();

    private void add(Class<?> clazz) {
        add(clazz.getClassLoader(), clazz.getName());
    }

    private void add(ClassLoader loader, String className) {
        synchronized (classMap) {
            System.out.println("loaded: " + className);
            Set<String> set = classMap.get(loader);
            if (set == null) {
                set = new HashSet<String>();
                classMap.put(loader, set);
            }
            set.add(className);
        }
    }

    private boolean isLoaded(String className, ClassLoader loader) {
        synchronized (classMap) {
            Set<String> set = classMap.get(loader);
            if (set == null) {
                return false;
            }
            return set.contains(className);
        }
    }

    @Override
    public byte[] transform(ClassLoader loader, String className,
            Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
            byte[] classfileBuffer) throws IllegalClassFormatException {
        add(loader, className);
        return classfileBuffer;
    }

    public static boolean isClassLoaded(String className, ClassLoader loader) {
        if (AGENT == null) {
            throw new IllegalStateException("Agent not initialized");
        }
        if (loader == null || className == null) {
            throw new IllegalArgumentException();
        }
        while (loader != null) {
            if (AGENT.isLoaded(className, loader)) {
                return true;
            }
            loader = loader.getParent();
        }
        return false;
    }

}

META-INF/MANIFEST.MF:

Manifest-Version: 1.0 
Premain-Class: myinstrument.ClassLoadedAgent

Недостатком является то, что вам нужно загрузить агент при запуске JVM:

java -javaagent:myagent.jar ....etcetera
person McDowell    schedule 27.01.2009
comment
Спасибо! Я впервые знакомлюсь с инструментальным API. Так какой способ посоветуете использовать? Инструментарий или отражение над ClassLoader.findLoadedClass? - person Hosam Aly; 27.01.2009
comment
Используйте тот, который лучше всего подходит для вашего приложения. Я бы не ожидал, что они сработают в 100% случаев. - person McDowell; 28.01.2009
comment
Можете ли вы объяснить, что означает myinstrument в файле манифеста? - person user489041; 12.09.2016

Если вы контролируете источник классов, для которых вас интересует, загружены они или нет (в чем я сомневаюсь, но вы не указываете в своем вопросе), вы можете зарегистрируйте свою нагрузку в статическом инициализаторе.

public class TestLoaded {
    public static boolean loaded = false;
    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println(loaded);
        ClassToTest.reportLoaded();
        System.out.println(loaded);
    }
    static class ClassToTest {
        static {
            System.out.println("Loading");
            TestLoaded.loaded = true;
        }
        static void reportLoaded() {
            System.out.println("Loaded");
        }
    }
}

Выход:

false
Loading
Loaded
true
person Stephen Denne    schedule 27.01.2009
comment
Спасибо. Это действительно хороший способ использовать для моих собственных классов. Но я надеялся найти что-то более общее, если я не контролирую классы. - person Hosam Aly; 27.01.2009
comment
Вы путаете загрузку класса и инициализацию. Когда вы делаете что-то вроде Object o = ClassToTest.class;, класс загружается, но не инициализируется. Я думаю, ОП все равно; просто хотел разобраться. - person maaartinus; 10.09.2014

Недавно у меня была похожая проблема, когда я подозревал, что классы загружались (предположительно с помощью -classpath или чего-то подобного) моими пользователями, которые конфликтовали с классами, которые я позже загружал в свой собственный загрузчик классов.

Попробовав несколько вещей, упомянутых здесь, мне показалось, что следующее помогло мне. Я не уверен, что это работает для всех обстоятельств, это может работать только для классов java, загруженных из файлов jar.

InputStream is = getResourceAsStream(name);

Где name — это путь к файлу класса, например com/blah/blah/blah/foo.class.

getResourceAsStream возвращал null, когда класс не был загружен в мой загрузчик классов или системный загрузчик классов, и возвращал ненулевое значение, когда класс уже был загружен.

person Eric Van Bezooijen    schedule 02.10.2012
comment
Это проверяет, находится ли ресурс класса на пути к классу (хотя начальный / отсутствует). Он не проверяет, загружен ли этот класс текущим загрузчиком классов. - person Lukas Eder; 20.11.2014