Несколько аннотаций одного типа на одном элементе?

91

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

public class Dupe {
    public @interface Foo {
      String bar();
    }

    @Foo(bar="one")
    @Foo(bar="two")
    public void haha() {}
}

При компиляции вышеуказанного javac жалуется на дублирующуюся аннотацию:

макс @ апсайт: ~ / работа / рассвет $ javac Dupe.java 
Dupe.java:5: повторяющаяся аннотация

Разве нельзя повторять подобные аннотации? Говоря педантично, разве два приведенных выше экземпляра @Foo не отличаются из-за того, что их содержимое отличается?

Если это невозможно, каковы возможные обходные пути?

ОБНОВЛЕНИЕ: меня попросили описать мой вариант использования. Вот оно.

Я создаю синтаксический сахарный механизм для «сопоставления» POJO с хранилищами документов, такими как MongoDB. Я хочу разрешить указывать индексы в виде аннотаций для геттеров или сеттеров. Вот надуманный пример:

public class Employee {
    private List<Project> projects;

    @Index(expr = "project.client_id")
    @Index(expr = "project.start_date")
    public List<Project> getProjects() { return projects; }
}

Очевидно, я хочу иметь возможность быстро находить экземпляры Employee по различным свойствам Project. Я могу либо дважды указать @Index с разными значениями expr (), либо воспользоваться подходом, указанным в принятом ответе. Несмотря на то, что Hibernate делает это, и это не считается взломом, я думаю, что все же имеет смысл хотя бы разрешить наличие нескольких аннотаций одного типа для одного элемента.

Макс А.
источник
1
Есть попытка ослабить это повторяющееся правило, чтобы разрешить вашу программу на Java 7. Не могли бы вы описать свой вариант использования?
notnoop
Я отредактировал свой вопрос, указав, почему я хочу это сделать. Спасибо.
Макс А.
В CDI может быть удобно предоставить компоненту для нескольких квалификаторов. Например, я только что попытался повторно использовать bean-компонент в двух местах, квалифицировав его "@Produces @PackageName (" test1 ") @PackageName (" test2 ")"
Ричард Корфилд
Далее: ответ ниже не решает эту проблему, потому что CDI будет рассматривать композит как один квалификатор.
Ричард Корфилд
@MaxA. Пожалуйста, измените ваше согласие с «зеленой галочкой» на правильный ответ от mernst. В Java 8 добавлена ​​возможность повторять аннотации.
Базиль Бурк,

Ответы:

140

Использование двух или более аннотаций одного типа не допускается. Однако вы можете сделать что-то вроде этого:

public @interface Foos {
    Foo[] value();
}

@Foos({@Foo(bar="one"), @Foo(bar="two")})
public void haha() {}

Однако вам понадобится специальная обработка аннотации Foos в коде.

кстати, я использовал это 2 часа назад для решения той же проблемы :)

сфуссенеггер
источник
2
Можно ли это сделать и в Groovy?
Excel20,
5
@ Excel20 Да. Однако вам придется использовать квадратные скобки, например @Foos([@Foo(bar="one"), @Foo(bar="two")]). См. Groovy.codehaus.org/Annotations+with+Groovy
sfussenegger
Немного поздно, но хотите указать совет, который может обработать список Foo внутри Foos? В настоящий момент я пытаюсь обработать результат метода, но, хотя Foos перехватывается, совет Foo никогда не вводится
Стелиос Куссурис
Обновление: в Java 8 этот ответ устарел. См. Правильный современный ответ mernst. В Java 8 добавлена ​​возможность повторять аннотации.
Базиль Бурк,
65

Повторение аннотаций в Java 8

В Java 8 (выпущенной в марте 2014 г.) можно писать повторяющиеся / повторяющиеся аннотации.

См. Учебник « Повторяющиеся аннотации» .

См. Спецификацию JEP 120: Повторяющиеся аннотации .

Mernst
источник
1
Официальный документ docs.oracle.com/javase/tutorial/java/annotations/repeating.html
Андерсон,
21

Помимо других упомянутых способов, в Java8 есть еще один менее подробный способ:

@Target(ElementType.TYPE)
@Repeatable(FooContainer.class)
@Retention(RetentionPolicy.RUNTIME)
@interface Foo {
    String value();

}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface FooContainer {
        Foo[] value();
        }

@Foo("1") @Foo("2") @Foo("3")
class Example{

}

Пример по умолчанию получает FooContainerв виде аннотации

    Arrays.stream(Example.class.getDeclaredAnnotations()).forEach(System.out::println);
    System.out.println(Example.class.getAnnotation(FooContainer.class));

Оба приведенных выше текста:

@ com.FooContainer (значение = [@ com.Foo (значение = 1), @ com.Foo (значение = 2), @ com.Foo (значение = 3)])

@ com.FooContainer (значение = [@ com.Foo (значение = 1), @ com.Foo (значение = 2), @ com.Foo (значение = 3)])

Jatin
источник
Стоит отметить, что имя метода / поля в FooContainer должно иметь строгое имя «value ()». В противном случае Foo не будет компилироваться.
Tomasz Mularczyk
... также содержащий тип аннотации (FooContainer) не может быть применим к большему количеству типов, чем повторяемый тип аннотации (Foo).
Tomasz Mularczyk
16

http://docs.oracle.com/javase/tutorial/java/annotations/repeating.html

Начиная с Java8, вы можете описывать повторяющиеся аннотации:

@Repeatable(FooValues.class)
public @interface Foo {
    String bar();
}

public @interface FooValues {
    Foo[] value();
}

Обратите внимание, valueэто обязательное поле для списка значений.

Теперь вы можете использовать повторяющиеся аннотации вместо заполнения массива:

@Foo(bar="one")
@Foo(bar="two")
public void haha() {}
Сергей Пикалев
источник
12

По словам Сфуссенеггера, это невозможно.

Обычное решение - создать "множественную" аннотацию , которая обрабатывает массив предыдущей аннотации. Обычно он называется так же с суффиксом 's'.

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


В зависимости от ваших потребностей, может быть лучше разрешить предыдущей аннотации обрабатывать несколько значений .

Пример:

    public @interface Foo {
      String[] bars();
    }
KLE
источник
Я знал, что это было очень знакомо. Спасибо, что освежили мою память.
Макс А.
Теперь это возможно с Java 8.
Anis
4

объединение других ответов в простейшую форму ... аннотацию с простым списком значений ...

@Foos({"one","two"})
private String awk;

//...

public @interface Foos{
    String[] value();
}
матовый1616
источник
3

Если у вас есть только 1 параметр «bar», вы можете назвать его «значение». В этом случае вам вообще не придется писать имя параметра, когда вы используете его следующим образом:

@Foos({@Foo("one"), @Foo("two")})
public void haha() {}

немного короче и аккуратнее, имхо ..

Голвиг
источник
Верный момент, но как это пытается ответить на вопрос OP?
Мордехай
@MouseEvent, вы правы, я думаю, что это было больше улучшением главного ответа от sfussenegger и, следовательно, больше его в комментариях. Но ответ все равно устарел из-за повторяющихся аннотаций ...
Голвиг
0

В текущей версии Java я смог решить эту проблему с помощью следующей аннотации:

@Foo({"one", "two"})
Эдуардо Александр де Соуза
источник
1
какая версия? jdk 8,9,10,11?
Gaurav