2009-05-14 12 views
44

tengo un simple anotación de marcador para los métodos (similar al primer ejemplo en el artículo 35 en Effective Java (2ª ed)):¿Cómo encontrar métodos anotados en un paquete dado?

/** 
* Marker annotation for methods that are called from installer's 
* validation scripts etc. 
*/ 
@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.METHOD) 
public @interface InstallerMethod { 
} 

Luego, en un paquete determinado (por ejemplo com.acme.installer), que tiene unos pocos subpaquetes que contienen unas 20 clases, me gustaría encontrar todos los métodos que están anotados con él. (Porque me gustaría hacer algunos controles con respecto a todos los métodos anotados en una prueba unitaria).

¿Qué (si existe) es la forma más fácil de hacer esto? Preferiblemente sin agregar nuevas bibliotecas o marcos de terceros.

Editar: para aclarar, obviamente method.isAnnotationPresent(InstallerMethod.class) será la forma de comprobar si un método tiene la anotación, pero este problema incluye la búsqueda de todos los métodos.

+0

Comprobar http://stackoverflow.com/questions/520328/can-you-find-all-classes-in-a-package- using-reflection –

+2

Hmm, sí, supongo que esto se debe a esa pregunta (que pregunté en febrero). : P Pero mientras que la respuesta correcta era "no, no se puede hacer usando reflexión", aquí la pregunta es * cómo * encontrar métodos anotados más fácilmente, usando cualquier medio ... – Jonik

+2

Porque la parte difícil aquí no es realmente relacionado con las anotaciones (pero encontrando clases del paquete), esta pregunta no resultó tan relevante como pensaba. :/Oh, bueno, quizás esto podría ayudar a alguien a preguntarse sobre lo mismo ... – Jonik

Respuesta

47

Si desea implementar por sí mismo, estos métodos se encuentran todas las clases de un paquete determinado:

/** 
* Scans all classes accessible from the context class loader which belong 
* to the given package and subpackages. 
* 
* @param packageName 
*   The base package 
* @return The classes 
* @throws ClassNotFoundException 
* @throws IOException 
*/ 
private Iterable<Class> getClasses(String packageName) throws ClassNotFoundException, IOException 
{ 
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 
    String path = packageName.replace('.', '/'); 
    Enumeration<URL> resources = classLoader.getResources(path); 
    List<File> dirs = new ArrayList<File>(); 
    while (resources.hasMoreElements()) 
    { 
     URL resource = resources.nextElement(); 
     URI uri = new URI(resource.toString()); 
     dirs.add(new File(uri.getPath())); 
    } 
    List<Class> classes = new ArrayList<Class>(); 
    for (File directory : dirs) 
    { 
     classes.addAll(findClasses(directory, packageName)); 
    } 

    return classes; 
} 

/** 
* Recursive method used to find all classes in a given directory and 
* subdirs. 
* 
* @param directory 
*   The base directory 
* @param packageName 
*   The package name for classes found inside the base directory 
* @return The classes 
* @throws ClassNotFoundException 
*/ 
private List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException 
{ 
    List<Class> classes = new ArrayList<Class>(); 
    if (!directory.exists()) 
    { 
     return classes; 
    } 
    File[] files = directory.listFiles(); 
    for (File file : files) 
    { 
     if (file.isDirectory()) 
     { 
      classes.addAll(findClasses(file, packageName + "." + file.getName())); 
     } 
     else if (file.getName().endsWith(".class")) 
     { 
      classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6))); 
     } 
    } 
    return classes; 
} 

a continuación, puedes filtrar en esas clases con la anotación dada:

for (Method method : testClass.getMethods()) 
{ 
    if (method.isAnnotationPresent(InstallerMethod.class)) 
    { 
     // do something 
    } 
} 
+0

¡Gracias! Usando esto, pude hacer lo que quería. Es prácticamente código, pero tal vez simplemente no sea una manera más simple (sin libretas externas), con el cargador de clases trabajando de la forma en que lo hace. – Jonik

+10

Interesantemente similar a http://snippets.dzone.com/posts/show/4831 –

+4

Gracias por compartir. Usé este código como base para mi propia solución, aunque otros deberían tener en cuenta que es necesario más trabajo si estás buscando clases en un archivo JAR. –

1

Si está contento de usar Spring, eso hace algo en esta línea usando su contexto: funcionalidad de exploración de componentes, donde Spring escanea para clases anotadas en un paquete dado. Debajo de las portadas, es bastante horrible, e implica hurgar en el sistema de archivos y en los archivos JAR buscando clases en el paquete.

Incluso si no puede usar Spring directamente, eche un vistazo a su código fuente para darle algunas ideas.

Ciertamente, la API de reflexión de Java no sirve para nada, específicamente no proporciona un medio para obtener todas las clases en un paquete.

+0

Gracias.Hmm, dices "Escaneos de primavera para clases anotadas": ¿puedes usarlo en este caso en el que estamos buscando * métodos * anotados, incluso en clases sin anotaciones? O, como alternativa, si implementa un método como getClasses (String packageName) a partir de la respuesta de Parkr haciendo uso de Spring, ¿podría hacerse mucho más simple? – Jonik

+0

El código de Spring busca todas las clases en ese paquete y luego busca anotaciones de nivel de clase y método. Puede hacer que sea más simple en ciertos entornos, pero no tanto. – skaffman

34

Probablemente deberías echar un vistazo a la fuente abierta Reflections library. Con ella se puede lograr fácilmente lo que desee con pocas líneas de código:

Reflections reflections = new Reflections( 
    new ConfigurationBuilder().setUrls( 
    ClasspathHelper.forPackage("com.acme.installer")).setScanners(
    new MethodAnnotationsScanner())); 
Set<Method> methods = reflections.getMethodsAnnotatedWith(InstallerMethod.class); 
+3

¿Estás seguro de que esto funciona? Pruébelo, no creo que lo haga. Tuve que configurar el escáner en el constructor de la clase Reflections para que funcione, como este: 'Reflections Reflections = \t \t \t new Reflections (new ConfigurationBuilder(). SetUrls (ClasspathHelper.forPackage (" com.acme.installer ")) .setScanners ( \t \t \t \t new MethodAnnotationsScanner())); ' – javamonkey79

+1

Ah, tienes razón, mi fragmento original no funcionó. ¡Gracias por hacérmelo saber! –

Cuestiones relacionadas