web-dev-qa-db-fra.com

Comment trouver des méthodes annotées dans un package donné?

J'ai une annotation de marqueur simple pour les méthodes (similaire au premier exemple de l'article 35 dans Java efficace (2e éd)):

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

Ensuite, dans un package donné (disons com.acme.installer), Qui a quelques sous-packages contenant une vingtaine de classes, j'aimerais trouver toutes les méthodes qui sont annotées avec lui. (Parce que j'aimerais faire quelques vérifications concernant toutes les méthodes annotées dans un test unitaire.)

Quelle est (le cas échéant) la manière la plus simple de procéder? De préférence sans ajouter de nouvelles bibliothèques ou frameworks tiers.

Edit: pour clarifier, évidemment method.isAnnotationPresent(InstallerMethod.class) sera le moyen de vérifier si une méthode a l'annotation - mais ce problème inclut la recherche de toutes les méthodes.

49
Jonik

Si vous souhaitez l'implémenter vous-même, ces méthodes trouveront toutes les classes dans un package donné:

/**
 * 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;
}

Ensuite, vous pouvez simplement filtrer ces classes avec l'annotation donnée:

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

Vous devriez probablement jeter un œil à l'open source bibliothèque Reflections . Avec lui, vous pouvez facilement réaliser ce que vous voulez avec quelques lignes de code:

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

Si vous êtes heureux d'utiliser Spring, cela fait quelque chose dans ce sens en utilisant son contexte: la fonctionnalité d'analyse des composants, où Spring recherche les classes annotées dans un package donné. Sous les couvertures, c'est assez horrible et implique de fouiller le système de fichiers et les fichiers JAR à la recherche de classes dans le package.

Même si vous ne pouvez pas utiliser Spring directement, consulter son code source peut vous donner quelques idées.

Certes, l'APi Java reflexion n'est pas utile ici, il ne fournit spécifiquement aucun moyen d'obtenir toutes les classes dans un package.

1
skaffman