Главное что нужно знать об аннотациях — они ничего не делают! Аннотации представляют собой простые метки в коде и больше ничего. Когда кто-то говорит «Аннотация @DoSomething делает блаблабла» это фактически означает, что кто-то где-то вызывает код, который находит типы с этой аннотацией и делает блаблабла.
Зачем нужны аннотации? Они позволяют разработчику декларировать дополнительные свойства кода, который он пишет. Говоря простыми словами — когда разработчик пишет над определением метода @Test это означает две вещи:
- Разработчик говорит себе и всему миру: «Этот метод не предназначен для использования в коде, этот метод реализует какую-то проверку и должен вызываться только во время исполнения тестов»
- Тестовый фреймворк осматривает все доступные ему методы в тестовых классах и запускает все методы, которые имеют аннотацию @Test
Другой хороший пример пользы от аннотаций был в статье, рассказывающей о разрешении конфликтов при внедрении зависимостей: некоторой аннотацией (можно считать, что именем) помечался как внедряемый класс, так и переменные, в которые он внедрялся.
Подготовка
Нам понадобится пустой maven проект с JUnit и Hamcrest:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>4.12</junit.version>
<hamcrest.version>1.3</hamcrest.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
|
И класс приветствия:
1
2
3
4
5
|
public class Greeter {
public String greet(String target) {
return "Hello, " + target;
}
}
|
1
2
3
4
5
6
7
8
9
|
public class GreeterTest {
@Test
public void testGreet() throws Exception {
Greeter testedObject = new Greeter();
assertThat(testedObject.greet("TEST"), is("Hello, TEST"));
}
}
|
Собственная аннотация
Сделать аннотацию самому не сложно — аннотация состоит из имени, свойств и конфигурации:
1
2
3
4
5
|
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GreeterTarget {
String value() default "world";
}
|
@Target(ElementType.FIELD) говорит, что эта аннотация может быть применена только к полям классов. Аннотации можно применять к пакетам, классам и их конструкторам, методам и их аргументам, переменным и так далее. @Retention(RetentionPolicy.RUNTIME) делает аннотацию доступной во время исполнения, а не удаляет её во время компиляции.
@interface говорит, что это аннотация, а не обычный интерфейс. Внутри аннотации могут быть определены её свойства, но их определение несколько необычно: во первых они выглядят как методы, во вторых у них может быть значение по умолчанию.Использование аннотации очевидно:
1
2
3
4
5
6
7
8
|
@GreeterTarget
private static Greeter world = new Greeter();
@GreeterTarget(value = "annotations")
private static Greeter annotations = new Greeter();
@GreeterTarget("Java")
private static Greeter java = new Greeter();
|
В первом случае значением свойства value будет значение по умолчанию: «world», во втором «annotations», а в третьем … таки да, «Java». У свойства по имено value() если удобное соглашение, именно в него попадает значение аннотации по умолчанию.
Использовать аннотацию гораздо сложнее. Так как аннотации относятся к коду, а не к данным программы, то для доступа к ним приходиться использовать reflection:
1
2
3
|
Field worldField = Main.class.getDeclaredField("world");
GreeterTarget worldTarget = worldField.getAnnotation(GreeterTarget.class);
System.out.println(world.greet(worldTarget.value()));
|
Вначале надо получить аннотируемый объект, потом получить из него аннотацию и, наконец, взять у аннотации её значения. Другой пример показывает, что никакой другой связи между кодом и аннотациями нет:
1
2
3
|
Field javaField = Main.class.getDeclaredField("java");
GreeterTarget javaTarget = javaField.getAnnotation(GreeterTarget.class);
System.out.println(world.greet(javaTarget.value()));
|
Код примера доступен на github