2009-05-14 2 views
44

У меня есть простой маркер аннотацию для методов (аналогично первому примеру, в пункте 35 в Эффективное Java (2-е изд)):Как найти аннотированные методы в заданном пакете?

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

Затем, в данном пакете (скажем com.acme.installer), который имеет несколько подпакетов, содержащих около 20 классов, я бы хотел найти все методы, аннотированные с ним. (Потому что я хотел бы сделать некоторые проверки относительно всех аннотированных методов в модульном тесте.)

Что (если есть) - это самый простой способ сделать это? Предпочтительно без добавления новых сторонних библиотек или фреймворков.

Редактировать: уточнить, очевидно, method.isAnnotationPresent(InstallerMethod.class) будет способ проверить, есть ли метод в аннотации, но эта проблема включает в себя поиск всех методов.

+0

Проверить http://stackoverflow.com/questions/520328/can-you-find-all-classes-in-a-package- используя отражение –

+2

Хм, да, я думаю, это возвращается к этому вопросу (который я спросил в феврале). : P Но в то время как правильный ответ был «нет, это невозможно сделать с помощью отражения», здесь вопрос * как * найти наиболее удобные методы, используя любые средства ... – Jonik

+2

Потому что сложная часть здесь не совсем связанный с аннотациями (но поиск классов из пакета), этот вопрос не оказался столь значимым, как я думал. :/Ну ладно, возможно, это все равно поможет кому-то задаться вопросом о том же ... – Jonik

ответ

47

Если вы хотите реализовать его самостоятельно, эти методы найдут все классы в данном пакете:

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

Тогда вы можете просто фильтровать по этим классам с данной аннотацией:

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

Спасибо! Используя это, я смог сделать то, что хотел. Это довольно много кода, но, возможно, просто не простой способ (без внешних libs), с загрузчиком классов, который работает так, как он. – Jonik

+10

Интересно похоже на http://snippets.dzone.com/posts/show/4831 –

+4

Спасибо за обмен. Я использовал этот код в качестве основы для своего собственного решения, хотя другие должны учитывать, что больше работы необходимо, если вы ищете классы в JAR-файле. –

1

Если вы счастливы использовать Spring, то это делает что-то в этих строках, используя его контекст: функциональность компонентного сканирования, где Spring сканирует аннотированные классы в данном пакете. Под обложками это довольно жутко, и вовлекает в себя файловую систему и файлы JAR, которые ищут классы в пакете.

Даже если вы не можете использовать Spring напрямую, просмотр исходного кода может дать вам некоторые идеи.

Конечно, Java APA отражения здесь бесполезно, он специально не предоставляет средства для получения всех классов в пакете.

+0

Спасибо.Хм, вы говорите «Весеннее сканирование для аннотированных классов» - можете ли вы использовать его в этом случае, когда мы ищем аннотированные * методы *, даже в классах без аннотаций? Или, альтернативно, если бы вы использовали метод getClasses (String packageName) из ответа Parkr с использованием Spring, можно ли его сделать намного проще? – Jonik

+0

Код Spring ищет все классы в этом пакете, а затем ищет аннотации уровня класса и метода. Вы можете сделать это проще в определенных средах, но не настолько. – skaffman

34

Возможно, вам стоит взглянуть на исходный код Reflections library. С его помощью вы можете легко добиться того, что вы хотите с несколькими строками кода:

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

Вы уверены, что это работает? Попробуйте, я не думаю, что это так. Мне пришлось установить сканер в конструктор класса Reflections, чтобы он работал, например: «Reflections reflections = \t \t \t новые отражения (новый ConfigurationBuilder(). SetUrls (ClasspathHelper.forPackage (« com.acme.installer »)) .setScanners ( \t \t \t \t новый MethodAnnotationsScanner())); ' – javamonkey79

+1

О, вы правы, мой оригинальный фрагмент не работал. Спасибо, что дали мне знать! –