2008-10-15 19 views
121

que quiero hacer algo como esto:En tiempo de ejecución, se encuentran todas las clases en una aplicación Java que se extienden una clase base

List<Animal> animals = new ArrayList<Animal>(); 

for(Class c: list_of_all_classes_available_to_my_app()) 
    if (c is Animal) 
     animals.add(new c()); 

lo tanto, quiero mirar a todas las clases en el universo de mi solicitud, y cuando encuentro uno que desciende de Animal, quiero crear un nuevo objeto de ese tipo y agregarlo a la lista. Esto me permite agregar funcionalidad sin tener que actualizar una lista de cosas. Puedo evitar lo siguiente:

List<Animal> animals = new ArrayList<Animal>(); 
animals.add(new Dog()); 
animals.add(new Cat()); 
animals.add(new Donkey()); 
... 

Con el enfoque anterior, simplemente puede crear una nueva clase de animal que se extiende y va a ser recogido de forma automática.

ACTUALIZACIÓN: 10/16/2008 9 a.m. hora estándar del Pacífico:

Esta cuestión ha generado una gran cantidad de grandes respuestas - gracias. A partir de las respuestas y mi investigación, descubrí que lo que realmente quiero hacer simplemente no es posible en Java. Existen enfoques, como el mecanismo ServiceLoader de ddimitrov, que pueden funcionar, pero son muy pesados ​​para lo que yo quiero, y creo que simplemente transfiero el problema del código de Java a un archivo de configuración externo.

Otra forma de expresar lo que quiero: una función estática en mi clase Animal encuentra e instaura todas las clases que heredan de Animal, sin ninguna configuración/codificación adicional. Si tengo que configurar, también podría instanciarlos en la clase Animal de todos modos. Entiendo que debido a que un programa Java es solo una federación suelta de archivos .class, esa es la forma en que es.

Curiosamente, parece que es fairly trivial en C#.

+1

Después de buscar durante un tiempo, parece que esto es una tuerca difícil de descifrar en Java. Aquí hay un hilo que tiene alguna información: http://forums.sun.com/thread.jspa?threadID=341935&start=15 Las implementaciones son más de las que necesito, supongo que me quedaré con el segunda implementación por ahora. – JohnnyLambada

+0

póngalo como respuesta y deje que los lectores lo voten – VonC

+3

El enlace para hacer esto en C# no muestra nada especial. Un ensamblado C# es equivalente al archivo Java JAR. Es una colección de archivos de clase compilados (y recursos). El enlace muestra cómo obtener las clases de un solo ensamblaje. Puedes hacerlo casi tan fácilmente con Java. El problema para usted es que necesita revisar todos los archivos JAR y los verdaderos archivos sueltos (directorios); tendrías que hacer lo mismo con .NET (buscar una RUTA de algún tipo o varios tipos). –

Respuesta

126

utilizo org.reflections:

Reflections reflections = new Reflections("com.mycompany");  
Set<Class<? extends MyInterface>> classes = reflections.getSubTypesOf(MyInterface.class); 
+5

Gracias por el enlace sobre org.reflections. Erróneamente pensé que esto sería tan fácil como lo es en el mundo de .Net y esta respuesta me ha ahorrado mucho tiempo. – akmad

+0

¡Gracias por este útil enlace al gran paquete "org.reflections"! Con eso finalmente he encontrado una solución práctica y ordenada para mi problema. – Hartmut

+2

¿Hay alguna manera de obtener todas las reflexiones, es decir, sin tener en cuenta un paquete específico, pero cargando todas las clases disponibles? –

2

Java carga dinámicamente las clases, por lo que su universo de clases sería solo aquellas que ya se han cargado (y aún no se han descargado). Quizás pueda hacer algo con un cargador de clases personalizado que pueda verificar los supertipos de cada clase cargada. No creo que haya una API para consultar el conjunto de clases cargadas.

5

Desafortunadamente, esto no es del todo posible ya que ClassLoader no le dirá qué clases están disponibles. Puede, sin embargo, conseguir bastante cerca de hacer algo como esto:

for (String classpathEntry : System.getProperty("java.class.path").split(System.getProperty("path.separator"))) { 
    if (classpathEntry.endsWith(".jar")) { 
     File jar = new File(classpathEntry); 

     JarInputStream is = new JarInputStream(new FileInputStream(jar)); 

     JarEntry entry; 
     while((entry = is.getNextJarEntry()) != null) { 
      if(entry.getName().endsWith(".class")) { 
       // Class.forName(entry.getName()) and check 
       // for implementation of the interface 
      } 
     } 
    } 
} 

Editar: johnstok es correcto (en los comentarios) que esto sólo funciona para las aplicaciones Java autónomas, y no funcionarán bajo un servidor de aplicaciones .

+0

Esto no funciona bien cuando las clases se cargan por otros medios, por ejemplo, en un contenedor web o J2EE. – johnstok

+0

Probablemente podría hacer un mejor trabajo, incluso debajo de un contenedor, si consultó las rutas (a menudo 'URL []' pero no siempre así que no es posible) de la jerarquía de ClassLoader. A menudo, sin embargo, debe tener permiso, ya que generalmente tendría un 'SecurityManager' cargado dentro de la JVM. –

+0

¡Funcionó como un amuleto para mí! Temía que esto sería demasiado pesado y lento, pasando por todas las clases en el classpath, pero en realidad limité los archivos que revisé a los que contenían "-ejb-" u otros nombres de archivo reconocibles, y todavía es 100 veces más rápido que el inicio un glassfish incrustado o EJBContainer. En mi situación particular, luego analicé las clases buscando anotaciones "sin estado", "con estado" y "Singleton", y si las veía, agregué el nombre JNDI asignado a mi MockInitialContextFactory. ¡Gracias de nuevo! – cs94njw

0

Este es un problema difícil y tendrá que averiguar esta información mediante el análisis estático, no está disponible fácilmente en tiempo de ejecución. Básicamente, obtenga el classpath de su aplicación y escanee las clases disponibles y lea la información del bytecode de una clase de la que hereda. Tenga en cuenta que una clase Dog puede no heredar directamente de Animal pero puede heredar de Pet, que a su vez hereda de Animal, por lo que deberá realizar un seguimiento de esa jerarquía.

2

Gracias a todos los que respondieron esta pregunta.

Parece que es un hueso duro de roer. Terminé rindiéndome y creando una matriz estática y getter en mi clase base.

public abstract class Animal{ 
    private static Animal[] animals= null; 
    public static Animal[] getAnimals(){ 
     if (animals==null){ 
      animals = new Animal[]{ 
       new Dog(), 
       new Cat(), 
       new Lion() 
      }; 
     } 
     return animals; 
    } 
} 

Parece que Java simplemente no está configurado para la auto-descubrimiento de la manera en que C# es. Supongo que el problema es que ya que una aplicación Java es solo una colección de.archivos de clase en un directorio/archivo jar en alguna parte, el tiempo de ejecución no sabe acerca de una clase hasta que se haga referencia a ella. En ese momento el cargador lo carga, lo que intento hacer es descubrirlo antes de hacer referencia a él, lo que no es posible sin tener que salir al sistema de archivos y mirar.

Siempre me gusta el código que puede descubrirse a sí mismo en lugar de tener que contarlo, pero esto también funciona.

¡Gracias nuevamente!

+1

Java está configurado para el autodescubrimiento. Solo necesitas hacer un poco más de trabajo. Ver mi respuesta para más detalles. –

0

Una forma es hacer que las clases utilizan un inicializadores estáticos ... No creo que éstos son heredadas (no va a funcionar si son):

public class Dog extends Animal{ 

static 
{ 
    Animal a = new Dog(); 
    //add a to the List 
} 

Se requiere que se agregue este código para todas las clases involucradas. Pero evita tener un gran bucle feo en alguna parte, probando cada clase buscando niños de Animal.

+3

Esto no funciona en mi caso. Como la clase no se referencia en ninguna parte, nunca se carga, por lo que nunca se llama al inicializador estático. Parece que se llama a un inicializador estático antes que a cualquier otro cuando se carga la clase, pero en mi caso la clase nunca se carga. – JohnnyLambada

7

Piense en esto desde un punto de vista orientado al aspecto; lo que realmente quieres hacer es conocer todas las clases en tiempo de ejecución que HAN extendido la clase Animal. (Creo que es una descripción un poco más precisa de su problema que su título, de lo contrario, no creo que tenga una pregunta en tiempo de ejecución)

Entonces, lo que creo que quiere es crear un constructor de su clase base (Animal) que agrega a su matriz estática (prefiero ArrayLists, yo mismo, pero a cada uno de ellos) el tipo de clase actual que se está instanciando.

Así, aproximadamente;

public abstract class Animal 
    { 
    private static ArrayList<Class> instantiatedDerivedTypes; 
    public Animal() { 
     Class derivedClass = this.getClass(); 
     if (!instantiatedDerivedClass.contains(derivedClass)) { 
      instantiatedDerivedClass.Add(derivedClass); 
     } 
    } 

Por supuesto, se necesita un constructor estático en el animal para inicializar instantiatedDerivedClass ... Creo que esto va a hacer lo que es probable que desee. Tenga en cuenta que esto depende de la ruta de ejecución; si tiene una clase de Perro que deriva de Animal que nunca se invoca, no la tendrá en su lista de Animal Class.

+3

Sin mejora en el código existente de OP. –

32

La forma Java de hacer lo que quiera es usar el mecanismo ServiceLoader.

También muchas personas obtienen su propio rollo al tener un archivo en una ubicación classpath conocida (es decir /META-INF/services/myplugin.properties) y luego usar ClassLoader.getResources() para enumerar todos los archivos con este nombre de todos los jar. Esto permite que cada jar exporte sus propios proveedores y usted puede instanciarlos por reflejo usando Class.forName()

+1

Para generar fácilmente el archivo de servicios META-INF basado en las anotaciones de las clases, puede consultar: http://metainf-services.kohsuke.org/ o https://code.google.com/p/spi/ – elek

+0

Bleah . Simplemente agrega un nivel de complejidad, pero no elimina la necesidad de crear una clase y luego registrarla en una tierra muy lejana. –

+0

Consulte la respuesta a continuación con 26 votos ascendentes, mucho mejor – SobiborTreblinka

0

Resolví este problema de manera muy elegante usando anotaciones de nivel de paquete y luego haciendo esa anotación tengo como argumento una lista de clases.

Find Java classes implementing an interface

implementaciones sólo hay que crear un package-info.java y poner la anotación con la magia en la lista de clases que quieran apoyar.

2

Usted podría utilizar ResolverUtil (raw source) de la Stripes Framework
si necesita algo sencillo y rápido sin refactorización el código existente.

Aquí hay un ejemplo simple de no haber cargado cualquiera de las clases:

package test; 

import java.util.Set; 
import net.sourceforge.stripes.util.ResolverUtil; 

public class BaseClassTest { 
    public static void main(String[] args) throws Exception { 
     ResolverUtil<Animal> resolver = new ResolverUtil<Animal>(); 
     resolver.findImplementations(Animal.class, "test"); 
     Set<Class<? extends Animal>> classes = resolver.getClasses(); 

     for (Class<? extends Animal> clazz : classes) { 
      System.out.println(clazz); 
     } 
    } 
} 

class Animal {} 
class Dog extends Animal {} 
class Cat extends Animal {} 
class Donkey extends Animal {} 

Esto también funciona en un servidor de aplicaciones, así, ya que es donde se diseñó para trabajar;)

El código hace básicamente lo siguiente:

  • iterar sobre todos los recursos en el paquete (s) se especifica
  • mantener sólo º recursos electrónicos que terminan en .class
  • carga las clases utilizando ClassLoader#loadClass(String fullyQualifiedName)
  • Comprueba si Animal.class.isAssignableFrom(loadedClass);
1

Usando OpenPojo puede hacer lo siguiente:

String package = "com.mycompany"; 
List<Animal> animals = new ArrayList<Animal>(); 

for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(package, Animal.class, null) { 
    animals.add((Animal) InstanceFactory.getInstance(pojoClass)); 
} 
Cuestiones relacionadas