2009-01-27 22 views

Respuesta

36

(Gracias 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"); 
      } 
    } 
} 

Produce:

false 
Loading TestLoaded$ClassToTest 
Loaded 
true 

Tenga en cuenta que las clases de ejemplo no están en un paquete. Se requiere binary name completo.

Un ejemplo de un nombre binario es "java.security.KeyStore$Builder$FileBuilder$1"

+1

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. –

+3

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. –

+0

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. –

25

Usted puede utilizar el método findLoadedClass(String) de cargador de clases. Devuelve null si la clase no está cargada.

+1

Desafortunadamente, findLoadedClasses está protegido, lo que significa que tendrá que subclase ClassLoader para poder acceder a él. – staffan

+0

¿Es posible llamar a este método por reflexión? (Supressing cheques de seguridad) –

+1

sí, lo es, puede hacerlo con la reflexión – Elbek

5

Si tiene el control de la fuente de las clases que le interesan, estén cargadas o no (lo cual dudo, pero no dice en su pregunta), entonces podría registrarse su carga en un 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"); 
     } 
    } 
} 

de salida:

false 
Loading 
Loaded 
true 
+0

Gracias. Esta es realmente una buena manera de usar para mis propias clases. Pero esperaba encontrar algo más genérico, en caso de que no controlara las clases. –

+3

Está confundiendo la carga e inicialización de clases. Cuando haces algo como 'Object o = ClassToTest.class;', la clase se carga pero no se inicializa. Supongo que a OP no le importa; solo quería aclararlo. – maaartinus

7

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 
+0

¡Gracias! Es la primera vez que me introduzco a la API de instrumentación. Entonces, ¿qué forma aconsejarías usar? Instrumentación o reflexión sobre ClassLoader.findLoadedClass? –

+0

Utilice el que mejor se adapte a su aplicación. No esperaría tampoco trabajar en el 100% de las situaciones. – McDowell

+0

¿Puedes explicar qué significa 'myinstrument' en el archivo de manifiesto? – user489041

2

tuve un problema similar recientemente, donde sospechaba que las clases se estaban cargando (presumiblemente a través de -classpath o algo similar) por mis usuarios que entraban en conflicto con las clases que estaba cargando más adelante en mi propio cargador de clases.

Después de probar algunas de las cosas mencionadas aquí, lo siguiente pareció ser el truco para mí. No estoy seguro de si funciona para cualquier circunstancia, es posible que solo funcione para clases Java cargadas desde archivos jar.

InputStream is = getResourceAsStream(name); 

Dónde name es la ruta de acceso al archivo de clase como com/blah/blah/blah/foo.class.

getResourceAsStream devolvió null cuando la clase no se había cargado en mi cargador de clases, o el cargador de clases del sistema, y ​​devolvió el valor no nulo cuando la clase ya se había cargado.

+2

Esto comprueba si el recurso de clase está en la ruta de clase (aunque falta '/' inicial). No verifica si esa clase es cargada por el cargador de clases actual –

Cuestiones relacionadas