Как написать свою Аннотацию в Java?

Довольно таки часто можно заметить @Annotation в проектах Java, но не все и не всегда задумываются, что это и как ними работать, в этом уроке я более подробно объясню, что такое аннотации и как с ними работать к тому же напишем свою аннотацию.

Шаг 1. Теория

Аннотация(@Annotation) — специальная форма метаданных, которая может быть добавлена в исходный код.

Аннотации используются для анализа кода, компиляции или выполнения. Аннотированы могут быть пакеты, классы, методы, переменные и параметры.

Например всем известная библиотека JUnit использует аннотации для проведения модульного тестирования:

@Test
public void test1(){
    // Тестирование
}

@Ignore
@Test
public void test2(){
    // Тестирование
}

где @Test и @Ignore — аннотации.

Аннотация выполняет следующие функции:

1) дает необходимую информацию для компилятора;

2) дает информацию различным инструментам для генерации другого кода, конфигураций и т. д.;

3) может использоваться во время работы кода;

Самая часто встречаемая аннотация, которую встречал любой программист, даже начинающий это @Override:

@Override
public String toString(){
    return "devcolibri.com";
}

Шаг 2. Как создать свою Аннотацию.

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

Написать свою аннотацию не так сложно, как могло бы казаться.

Все что нам нужно для создание своей аннотации, это создать файл About.java назвать его можно как угодно, но от этого названия будет зависеть имя вашей аннотации, если мы назвали его About.java то аннотация будет выглядеть так @About.

Напишем в созданном файле About.java следующий код:

public @interface About{
    String info() default "";
}

как вы видите на месте где обычно пишут class или interface у нас написано @interface.

По просту структура практически та же, что и у интерфейсов, только пишется @interface.

@interface — указывает на то, что это аннотация

default — говорит про то, что метод по умолчанию будет возвращать определённое значение.

Вот и все аннотация готова теперь ею можно пользоваться, но есть одно НО, аннотацию можно сконфигурировать.

Шаг 3. Конфигурации для аннотации.

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

Для того чтобы ограничить использование аннотации её нужно проаннотировать :)

Для этого существует аннотация @Target.

@Target(ElementType.PACKAGE) — только для пакетов;

@Target(ElementType.TYPE) — только для классов;

@Target(ElementType.CONSTRUCTOR) — только для конструкторов;

@Target(ElementType.METHOD) — только для методов;

@Target(ElementType.FIELD) — только для атрибутов(переменных) класса;

@Target(ElementType.PARAMATER) — только для параметров метода;

@Target(ElementType.LOCAL_VARIABLE) — только для локальных переменных.

В случае если вы хотите, что бы ваша аннотация использовалась больше чем для одного типа параметров, то можно указать @Target следующим образом:

@Target({ ElementType.PARAMETER, ElementType.LOCAL_VARIABLE })

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

Шаг 4. Реализация

Вы наверное уже заметили что написанная нами аннотация About — это практически интерфейс который ничего не реализовывает, по этому нам нужно указать что же должно происходить с аннотированным фрагментом кода.

Как вам уже известно Аннотация это всего лишь маркер который выделяет что-то, и по этому маркеру мы можем легко найти фрагмент кода и что-то сделать.

Давайте реализуем всем известный framework JUnit.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    Class expected();
}

как вы уже заметили у наc появилась новая конфигурация для нашей аннотации @Retention

@Retention — эта аннотация позволит позволит сохранять нашу аннотацию JVM во время выполнения, что даст возможность использовать отображение(reflection).

Теперь мы должны для нашей аннотации написать класс анализатор. Этот класс будет анализировать наши аннотации и выполнять некоторые действия, связанные с аннотируемыми параметрами.

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

Что должен делать анализатор? Он использует Java отражения для доступа к аннотируемым данным.

Пример анализатора для @Test:

public class TestAnnotationAnalyzer {
    public void parse(Class<?> clazz) throws Exception {
        Method[] methods = clazz.getMethods();
        int pass = 0;
        int fail = 0;

        for (Method method : methods) {
            if (method.isAnnotationPresent(Test.class)) {
                try {
                    method.invoke(null);
                    pass++;
                } catch (Exception e) {
                    fail++;
                }
            }
        }
    }
}

Обратите внимание что в строке 10 мы вызываем аннотируемый метод.

Это все. Анализатор готов к использованию, но постойте, мы не реализовали атрибуты аннотации. Эта часть немного сложнее. Потому что вы не можете напрямую обращаться к этим атрибутам.

public class TestAnnotationAnalyzer {
    public void analyz(Class<?> clazz) throws Exception {
        Method[] methods = clazz.getMethods();
        int pass = 0;
        int fail = 0;

        for (Method method : methods) {
            if (method.isAnnotationPresent(Test.class)) {
                // Получаем доступ к атрибутам
                Test test = method.getAnnotation(Test.class);
                Class expected = test.expected();
                try {
                    method.invoke(null);
                    pass++;
                } catch (Exception e) {
                    if (Exception.class != expected) {
                        fail++;
                    } else {
                        pass++;
                    }
                }
            }
        }
    }
}

В строке 10 мы получаем доступ к атрибуту аннотации и дальше в строке 11 получаем значение атрибута аннотации, в нашем случае это значение типа Class так как expected — это ожидаемая ошибка и мы будем получать exception.

Ну и в строке 16 мы проверяем ожидаемый Exception с полученным.

Шаг 5. Использование

Давайте теперь используем нашу аннотацию:

public class Demo {
    public static void main(String [] args) {
        TestAnnotationAnalyzer analyzer = new TestAnnotationAnalyzer();
        analyzer.analyz(MyTest.class);
    }
}

Внимание!

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

P.S. Спасибо за внимание, оставляйте комментарии, предлагайте варианты лучше или же поправьте меня если что-то не так.

Урок создан: 15 апреля 2013 | Просмотров: 30466 | Автор: Александр Барчук | Правила перепечатки


Добавить комментарий

Добавить комментарий

Ваш e-mail не будет опубликован.

Комментарии:

  • 10 июля 2013 в 22:54

    Ang

    Для полноты статьи, просьба описать RetentionPolicy.

  • 26 июля 2013 в 14:38

    Vitaly

    Автору спасибо за статью! Выложено понятно и доходчиво!
    В продолжении материала не могли бы Вы пояснить
    1) Как реализовать возможность задания и обработки нескольких ожидаемых исключений, а не только ArrayIndexOutOfBoundsException.class для кода ниже
    class TestAnnotation {
    @TMarker(expected = ArrayIndexOutOfBoundsException.class)
    public void method1() {
    int[] j = new int[0];
    j[1] = 2;
    }
    }
    2) как реализовать подобную конструкцию @Target({ ElementType.PARAMETER, ElementType.LOCAL_VARIABLE })
    (не в прямом смысле Target, а свою). Как обработать этот безымянный массив {…}