Em Java, é possível saber se uma classe já foi carregado?
-
20-08-2019 - |
Pergunta
É possível saber se uma classe Java foi carregado, sem tentar carregá-lo? tentativas Class.forName
carregar a classe, mas eu não quero esse efeito colateral. Existe outra maneira?
(Eu não quero substituir o carregador de classe. Eu estou procurando um método relativamente simples.)
Solução
(Graças a Aleksi) Este código:
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");
}
}
}
Produz:
false Loading TestLoaded$ClassToTest Loaded true
Note que o exemplo classes não estão em um pacote. A nome binário é necessária.
Um exemplo de um nome binário é "java.security.KeyStore$Builder$FileBuilder$1"
Outras dicas
Você pode usar o findLoadedClass (string) método em ClassLoader. Ele retorna nulo se a classe não está carregado.
Uma maneira de fazer isso seria escrever um agente de Java usando o instrumentação API . Isso permitirá que você para gravar o carregamento de aulas pela 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
A desvantagem é que você tem que carregar o agente quando iniciar o JVM:
java -javaagent:myagent.jar ....etcetera
Se você está no controle da fonte das classes para o qual você está interessado em saber se eles são carregados ou não (o que duvido, mas você não estado na sua pergunta), em seguida, você pode registrar sua carga em um inicializador estático.
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");
}
}
}
Output:
false Loading Loaded true
Eu tive um problema semelhante recentemente, onde eu suspeito que as aulas estavam sendo carregados (presumivelmente por meio de -classpath ou algo similar) por meus usuários que entraram em conflito com as classes I estava carregando mais tarde em meu próprio carregador de classe.
Depois de tentar algumas das coisas mencionadas aqui, o seguinte parecia fazer o truque para mim. Eu não tenho certeza se ele funciona para todas as circunstâncias, pode funcionar apenas para classes Java carregados a partir de arquivos jar.
InputStream is = getResourceAsStream(name);
Onde name
é o caminho para o arquivo de classe, como com/blah/blah/blah/foo.class
.
getResourceAsStream
voltou null
quando a classe não tinha sido carregado em meu carregador de classe, ou o carregador de classe do sistema, e voltou não nulo quando a classe já havia sido carregado.