2008-11-03 5 views
198

Каков наилучший способ поиска всего пути к классу для аннотированного класса?Сканирование аннотаций Java во время выполнения

Я занимаюсь библиотекой, и я хочу разрешить пользователям аннотировать их классы, поэтому при запуске веб-приложения мне нужно отсканировать весь путь к определенной аннотации.

Знаете ли вы библиотеку или средство Java для этого?

Редактировать: Я думаю о чем-то вроде новой функциональности для веб-служб Java EE 5 или EJB. Вы комментируете свой класс с помощью @WebService или @EJB, и система находит эти классы во время загрузки, чтобы они были доступны удаленно.

ответ

163

Применение org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

API

Поставщик компонент, который сканирует путь к классам от базового пакета. Затем он применяет исключение и включает фильтры к полученным классам для поиска кандидатов.

ClassPathScanningCandidateComponentProvider scanner = 
new ClassPathScanningCandidateComponentProvider(<DO_YOU_WANT_TO_USE_DEFALT_FILTER>); 

scanner.addIncludeFilter(new AnnotationTypeFilter(<TYPE_YOUR_ANNOTATION_HERE>.class)); 

for (BeanDefinition bd : scanner.findCandidateComponents(<TYPE_YOUR_BASE_PACKAGE_HERE>)) 
    System.out.println(bd.getBeanClassName()); 
+4

Спасибо за информацию. Вы также знаете, как сканировать путь к классам, чьи поля имеют пользовательскую аннотацию? – Javatar 2012-12-12 14:10:40

+6

@Javatar Используйте API отражения Java. .class.getFields() Для каждого поля вызовите getAnnotation () – 2013-01-21 02:59:57

+0

@ArthurRonaldFDGarcia, как я могу сканировать `sub package` так? – CycDemo 2014-01-30 08:30:42

1

Java не имеет «Discovery». Единственный способ, которым я знаю, - это проверить каталог, в котором должны находиться файлы .class, проанализировать имена и использовать их. Ужасно уродливый, может быть, в наши дни есть лучший пакет - я не смотрел через несколько лет.

Обычно эта проблема была решена путем включения файла свойств или файла .xml с именами классов в нем.

Мне было бы интересно услышать лучший ответ.

3

Немного офтальмологический, но весна также делает что-то подобное, используя <context:component-scan>, в котором вы могли бы изучить исходный код?

Весна обеспечивает возможность автоматического обнаружения «стереотипных» классов [...]. Чтобы автоматически определить эти классы и зарегистрировать соответствующие компоненты, необходимо включить элемент [context: component-scan].

1

API-интерфейс Classloader не имеет метода «перечисления», поскольку загрузка класса является активностью «по требованию» - обычно у вас есть тысячи классов в вашем пути к классам, только часть из которых когда-либо будет (только rt.jar составляет 48 МБ!).

Итак, даже если вы можете перечислить все классы, это будет очень много времени и памяти.

Простой подход - перечислить соответствующие классы в установочном файле (xml или что-то, что вам нравится); если вы хотите сделать это автоматически, ограничьте себя одним JAR или одним классом.

2

Я не уверен, поможет вам это или нет, но вы можете заглянуть в проект открытия apache commons-discovery.

discovery project

10

Используйте ServiceLoader или implement your own, если вы не в Java 6.

Возможно, процессор аннотаций может произвести необходимые файлы в META-INF/услуги во время компиляции.

6

Попробуйте Scannotation.

Его можно использовать для поиска в каталоге классов или в каталоге вашего веб-приложения для конкретных аннотаций.

+0

-1 Кажется, что эта библиотека устарела, она использует очень старую версию javassist, которая не может читать файлы классов java 8. Вместо этого используйте «отражения» (см. Ответ Джонатана). – 2015-10-15 19:32:22

133

И еще одно решение Google reflections.

Краткий обзор:

  • Spring решением является способ пойти, если вы используете Spring. В противном случае это большая зависимость.
  • Использование ASM непосредственно немного громоздко.
  • Использование Java Assist напрямую также неудобно.
  • Annovention - очень легкий и удобный. Пока нет интеграции maven.
  • Google размышляет в коллекциях Google. Индексирует все, а затем очень быстро.
17

Если вы хотите действительно легкий вес (без зависимостей, простой API, 15 кб баночка файл) и очень быстро решение, взгляните на annotation-detector на https://github.com/rmuller/infomas-asl

Отказ от ответственности: Я автор.

13

Вы можете использовать API Java Plug-end Annotation Processing API для записи обработчика аннотаций, который будет выполняться во время процесса компиляции и будет собирать все аннотированные классы и создавать индексный файл для использования во время выполнения.

Это самый быстрый способ сделать аннотированное обнаружение класса, потому что вам не нужно сканировать ваш путь к классам во время выполнения, что обычно происходит очень медленно. Также этот подход работает с любым загрузчиком классов и не только с помощью URLClassLoaders, обычно поддерживаемых сканерами времени выполнения.

Вышеупомянутый механизм уже реализован в библиотеке ClassIndex.

Чтобы использовать его, аннотируйте свою аннотацию с помощью метаобъекта @IndexAnnotated. Это создаст во время компиляции индексный файл: META-INF/annotations/com/test/YourCustomAnnotation, содержащий список всех аннотированных классов. Вы можете Гости могут воспользоваться индексом во время выполнения, выполнив:

ClassIndex.getAnnotated(com.test.YourCustomAnnotation.class) 
1

Google Reflections, кажется, гораздо быстрее, чем весной. Нашел этот запрос функции, который ссылается на эту разницу: http://www.opensaga.org/jira/browse/OS-738

Это повод использовать Размышления, поскольку время запуска моего приложения действительно важно во время разработки. Размышления также очень удобны для использования в моем случае использования (найдите всех исполнителей интерфейса).

28

Я только что выпустил быстрый и легкий классный сканер классов (git repo here), который не вызывает загрузчик классов для загрузки классов в пути к классам для определения подклассов, суперклассов, аннотаций и т. Д., Но, скорее, читает бинарные заголовки classfile напрямую (вдохновил но проще, чем сканер маршрутов rmueller, связанный в другом комментарии).

Сканер класса маршрута может находить классы в пути к классам, которые расширяют данный суперкласс, которые реализуют данный интерфейс или имеют определенную аннотацию класса, и могут находить файлы в пути к классам любого типа, путь которого соответствует заданному регулярному выражению ,

Вот пример использования:

new FastClasspathScanner(new String[] 
     { "com.xyz.widget", "com.xyz.gizmo" }) // Whitelisted package prefixes 
    .matchSubclassesOf(DBModel.class, 
     // c is a subclass of DBModel 
     c -> System.out.println("Found subclass of DBModel: " + c.getName())) 
    .matchClassesImplementing(Runnable.class, 
     // c is a class that implements Runnable 
     c -> System.out.println("Found Runnable: " + c.getName())) 
    .matchClassesWithAnnotation(RestHandler.class, 
     // c is a class annotated with @RestHandler 
     c -> System.out.println("Found RestHandler annotation on class: " 
       + c.getName())) 
    .matchFilenamePattern("^template/.*\\.html", 
     // templatePath is a path on the classpath that matches the above pattern; 
     // inputStream is a stream opened on the file or zipfile entry. 
     // No need to close inputStream before exiting, it is closed by caller. 
     (templatePath, inputStream) -> { 
      try { 
       String template = IOUtils.toString(inputStream, "UTF-8"); 
       System.out.println("Found template: " + absolutePath 
         + " (size " + template.length() + ")"); 
      } catch (IOException e) { 
       throw new RuntimeException(e); 
      } 
     }) 
    .scan(); // Actually perform the scan 

Сканер также записывает последнюю последнее изменение метки времени любого файла или каталога столкнулся, и вы можете увидеть, что если последняя последнее изменение временной метка увеличилась (с указанием что-то на пути к классам обновлено) по телефону:

boolean classpathContentsModified = 
    fastClassPathScanner.classpathContentsModifiedSinceScan(); 

Это может быть использовано для того, чтобы динамический класс перегрузка, если что-то на пути к классам обновляются, например, для поддержки горячей замены классов обработчика маршрута в веб сервер. Вышеупомянутый вызов в несколько раз быстрее, чем исходный вызов функции scan(), так как нужно проверять только временные метки изменения.

2

С весной вы также можете просто написать следующее с помощью класса AnnotationUtils. т.е .:

Class<?> clazz = AnnotationUtils.findAnnotationDeclaringClass(Target.class, null); 

Для получения более подробной информации и всех различных методов проверки официальных документов: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/annotation/AnnotationUtils.html

1

ли это слишком поздно, чтобы ответить. я бы сказал, его лучше ехать библиотеки как ClassPathScanningCandidateComponentProvider или как Scannotations

Но даже после того, как кто-то хочет попробовать руки на него загрузчик, я написал некоторые на моем собственном, чтобы напечатать аннотации из классов в пакете :

public class ElementScanner { 

public void scanElements(){ 
    try { 
    //Get the package name from configuration file 
    String packageName = readConfig(); 

    //Load the classLoader which loads this class. 
    ClassLoader classLoader = getClass().getClassLoader(); 

    //Change the package structure to directory structure 
    String packagePath = packageName.replace('.', '/'); 
    URL urls = classLoader.getResource(packagePath); 

    //Get all the class files in the specified URL Path. 
    File folder = new File(urls.getPath()); 
    File[] classes = folder.listFiles(); 

    int size = classes.length; 
    List<Class<?>> classList = new ArrayList<Class<?>>(); 

    for(int i=0;i<size;i++){ 
     int index = classes[i].getName().indexOf("."); 
     String className = classes[i].getName().substring(0, index); 
     String classNamePath = packageName+"."+className; 
     Class<?> repoClass; 
     repoClass = Class.forName(classNamePath); 
     Annotation[] annotations = repoClass.getAnnotations(); 
     for(int j =0;j<annotations.length;j++){ 
      System.out.println("Annotation in class "+repoClass.getName()+ " is "+annotations[j].annotationType().getName()); 
     } 
     classList.add(repoClass); 
    } 
    } catch (ClassNotFoundException e) { 
     e.printStackTrace(); 
    } 
} 

/** 
* Unmarshall the configuration file 
* @return 
*/ 
public String readConfig(){ 
    try{ 
     URL url = getClass().getClassLoader().getResource("WEB-INF/config.xml"); 
     JAXBContext jContext = JAXBContext.newInstance(RepositoryConfig.class); 
     Unmarshaller um = jContext.createUnmarshaller(); 
     RepositoryConfig rc = (RepositoryConfig) um.unmarshal(new File(url.getFile())); 
     return rc.getRepository().getPackageName(); 
     }catch(Exception e){ 
      e.printStackTrace(); 
     } 
    return null; 

} 
} 

И в файле конфигурации вы помещаете имя пакета и освобождаете его класс.