Иногда, как разработчик Android, вы можете столкнуться с задачей, которую можно было бы снять с помощью какого-нибудь странного словарного определения скуки. Примером может быть преобразование объекта в JSON - это что-то достаточно простое, чтобы вы могли делать это во сне, но достаточно длинное, чтобы на самом деле занять разумную часть вашего времени, если вы хотите сделать это для нескольких разных классов. . Вы могли бы использовать что-то вроде Gson, но что, если это перебор или вам не нужны накладные расходы, связанные с выполнением этого динамически? Но, с другой стороны, записывать каждое поле - отстой. Кто хочет это делать?

Никто! Хорошо, что вы можете использовать генерацию кода, чтобы сделать все это за вас.

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

Так как же, скажем, сделать процессор аннотаций для генерации конвертера JSON для класса?

Что ж, к счастью для вас, в этом нет необходимости. Существуют обработчики аннотаций, которые уже могут это сделать! На самом деле их много, поэтому нет особого смысла писать одну для этой статьи. Есть даже действительно крутые штуки, такие как Lombok, программа, которая выполняет несколько злобных хаков, чтобы реально манипулировать уже существующим кодом, и вы можете использовать это в сочетании с чем-то вроде Джексона, чтобы легко внедрить методы прямо в класс.

Но если оставить в стороне повсеместность, это определенно подходящий кандидат для обработки аннотаций - как правило, вы хотите сделать что-то роботизированное, предсказуемое и утомительное и сделать это полностью автоматизированным. И вы мне поверите, в мире Android этого много. Не каждый обработчик аннотаций должен быть таким широким и сложным, как Dagger 2, Android Annotations или вышеупомянутый Lombok. Даже небольшие, тривиальные, надоедливые задачи можно решить навсегда, затратив час. А когда вы можете поместить аннотацию в поле для генерации десяти строк кода, как только вы воспользуетесь ею несколько раз, вы начнете видеть, насколько эффективным может быть правильное использование генерации кода.

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

Давай поразмышляем, что нам понадобится. Сразу бросается в глаза следующее: модуль процессора для нашего процессора аннотаций, какой-то модуль API или библиотеки (чтобы мы не перетаскивали кучу зависимостей из нашего процессор в любые проекты, в которых мы хотим использовать наш процессор), и наш вышеупомянутый пример, чтобы показать людям, как его использовать. Имея это в виду, это моя типичная установка для такого рода вещей:

Как видите, у нас есть три модуля. Они изобретательно названы app, lib и processor. Последние два являются чистыми модулями библиотеки Java, поскольку мы не можем использовать модуль библиотеки Android в модуле библиотеки Java, но в этом случае у нас все в порядке - мы не используем какой-либо компонент Android framework. В противном случае нам нужно было бы создать другой модуль, содержащий специфичные для Android части библиотеки, а затем включить в него наш модуль lib в качестве зависимости.

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

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

Этот файл будет находиться под адресом и именем META-INF/services/javax.annotation.processing.Processor и будет выглядеть примерно так:

fully.qualified.name.of.ProcessorOne
fully.qualified.name.of.ProcessorTwo
and.etcetera.ad.nauseam.for.all.the.Processors

И это обеспечит эти три процессора аннотаций внутри вашего модуля.

В качестве альтернативы, и это то, что я предпочитаю, вы можете просто добавить эту небольшую библиотеку

compile 'com.google.auto.service:auto-service:1.0-rc2'

в зависимости build.gradle вашего процессорного модуля. Если он жалуется на то, что не может найти зависимость, вам может потребоваться добавить repositories раздел и вставить jcenter() в него. Затем создайте класс с именем типа AnnotationsProcessor и поместите поверх него аннотацию @AutoService, а для параметра значения укажите Processor.class. Возможно, вам потребуется импортировать его из java.annotation.processing.

Это автоматически создаст для вас файл META-INF /…. И когда это будет сделано, мы можем начать писать. Вам нужно сделать так, чтобы класс, в который вы только что добавили аннотацию AutoService, расширял класс с именем AbstractProcessor:

@AutoService(Processor.class) 
public class AnnotationsProcessor extends AbstractProcessor {
     //process() method goes here
}

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

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
  return false;
}

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

Теперь мы готовы написать правильный код.

Однако у нас пока нет аннотаций! Итак, давайте исправим это, добавив @ComparableField аннотацию. Мы поместили его в модуль lib, чтобы люди, которые хотят использовать вашу библиотеку, могли получить к нему доступ.

Для справки вот моя реализация:

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface ComparableField {

  GreaterThan greaterThan() default GreaterThan.IS_POSITIVE;

  enum GreaterThan {
    IS_NEGATIVE,
    IS_POSITIVE
  }
}

Перечисление GreaterThan будет для определения того, будет ли x > y +1 или -1. По умолчанию x > y возвращает +1, но мы не хотим заставлять это делать. Следовательно, есть возможность изменить это поведение.

Теперь поместите compile project (path: ':lib') в зависимости нашего процессора. Таким образом мы действительно сможем их обработать. Ваш build.gradle для вашего процессорного модуля должен выглядеть следующим образом:

apply plugin: 'java'

repositories {
  jcenter()
}
dependencies {
  compile fileTree(dir: 'libs', include: ['*.jar'])
  compile 'com.google.auto.service:auto-service:1.0-rc2'

  compile project(path: ':lib')
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

А теперь, снова переходя к AnnotationsProcessor.java, нам нужно предоставить ему список аннотаций, которые он может обработать. Вы можете сделать это двумя способами - указав список в аннотации @SupportedAnnotationType или заменив getSupportedAnnotationTypes(). Я лично предпочитаю второй, так что давайте сделаем это. Но на самом деле это вопрос предпочтений. Если вы хотите, вы можете вместо этого просто дать несколько полных имен классов этой аннотации в верхней части класса.

@Override public Set<String> getSupportedAnnotationTypes() {
  Set<String> set = new HashSet<>();
  set.add(ComparableField.class.getCanonicalName());
  return set;
}

Мой обработчик аннотаций в целом выглядит так:

@AutoService(Processor.class) public class AnnotationsProcessor extends AbstractProcessor {

  @Override public Set<String> getSupportedAnnotationTypes() {
    Set<String> set = new HashSet<>();
    set.add(ComparableField.class.getCanonicalName());
    return set;
  }

  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    return false;
  }
}

Довольно хиленький, да? На самом деле это полностью работоспособный процессор аннотаций, который абсолютно ничего не делает!

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

О нас

В jtribe мы с гордостью создаем программное обеспечение для iOS, Android и Интернета и увлечены тем, что делаем. Мы работаем с платформами iOS и Android с первого дня и являемся одной из самых опытных команд мобильных разработчиков в Австралии. Мы измеряем успех по тому влиянию, которое мы оказываем, и, имея более шести миллионов конечных пользователей, мы знаем, что наша работа значима. Это продолжает оставаться нашей движущей силой.