Una forma de hacer esto sería escribir un agente de Java utilizando la instrumentation API. Esto le permitiría registrar la carga de clases por parte de la 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
El inconveniente es que hay que cargar el agente cuando se inicia la JVM:
java -javaagent:myagent.jar ....etcetera
Solo una nota al margen: recuerde usar el nombre canónico package.subpackage.ClassName cuando llame al método. Esta respuesta no demuestra este requisito ya que la clase no está en un paquete. –
Gracias por agregar esa nota. Si bien necesitas el paquete, es el "nombre binario" que se requiere. El nombre canónico de la clase que se muestra en el ejemplo es "TestLoaded.ClassToTest", que difiere del nombre binario ... Editaré la respuesta para clarificar/vincular. –
Gracias @spdenne y @Aleksi. Intenté hacer esto pero falló, aunque no estoy seguro de por qué. Tu código anterior funciona para mí. Como nota al margen, es posible que desee comprobar la respuesta de McDowell sobre la instrumentación. Muchas gracias. –