среда, 27 апреля 2011 г.

java анотации. Что это и с чем их едят?

Это штуки, которые мы можем встретить в коде. Они начинаются из символа @.


Многие Java API требуют написания большого количества рутинного кода, другие Java API требуют присутсвия рядом с классами "сайд-файлов".
Так вот эти штуки помогают компилятору явы (javac) или утилите apt(Annotation Processing Tool), генерить этот код автоматически вместо того, чтобы его писать руками.

Вот примеры применения:
1) Написание JAX-RPC web-сервисов требует, чтобы были определены и интерфейсы и их реализации. Это может сделать платформа автоматически если возле методов, которые будут вызываться удаленно, ставить аннотации.
2) JavaBeans должны сопровождаться классами BeanInfo. А EJB дескриптором развертования. Продуктивнее и более безопасно в плане ошибок, чтобы эти файлы генерировались на основании аннотаций в коде автоматически.


Есть и много других полезных старых анотаций:
  • @transient -- ставится возле параметров класса, который не нужно серилизировать.
  • @depracated -- ставится возле методов, которые лучше не использовать.
В верисии java 5 появились возможность анотаций общего назначения (такназываемые метаданные обьекта). Эта возможность реализуется обьявлением типа анотации, декларацией самой анотации, АПИ для чтения анотаций, инструмент для работы с анотациями(apt).
Анотации очень похожы на javadoc, они даже дополняют их, но между ними есть четкое отличие. Вторые используются только для автоматической генерации документаций. А первые влият на работу программы (хотя не через саму семантику программы, а влияя на интрументарий jdk и на библиотеки).

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

Чтобы создать собственную аннотацию, мы должны ее описать, и сделать для нее собственный процессор, а также указать этот процессор в файле архива META-INF/services/javax.annotation.processing.Processor, в этом файле указываются все кастомные процессоры(пакетный путь на их классы через перенос строки).

Для примера собственной аннотации придумаем себе задачу и решим ее. Этот пример взят из http://www.javaspecialists.eu/archive/Issue167.html.
Допустим нам нужно в определенных классах обеспечить, чтобы они имели полюбому публичный контсруктор без аргументом, при этом выявить такие классы мы должны не тестируя откомпилированное приложение, а на этапе компиляции(по-этому вопрос с исключениями сразу отпадает).

Первое опишем аннотацию:

package eu.javaspecialists.tools.apt;

import java.lang.annotation.*;

@Inherited
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface NoArgsConstructor {
}

Как видим в описании аннотации уже используются готовые аннотации) Разберем их:
@Inherited - означает, что наследники класса, к которому применена аннотация, наследывают и аннотацию тоже.
@Documented - означает, что в документацию javadoc будет заносить эту аннотацию.
@Retention(RetentionPolicy.SOURCE) - означает, что аннотация будет доступна только на этапе компиляции(или запуска apt), в момент работы приложения аннотация не доступна.
@Target(ElementType.TYP) - означает, что эта аннотация применима только к классам.

Теперь пришло время создать файл процессора для нашей аннотации:
package eu.javaspecialists.tools.apt;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
import javax.lang.model.util.SimpleTypeVisitor6;
import javax.tools.Diagnostic;
import java.util.Set;

@SupportedAnnotationTypes(
    "eu.javaspecialists.tools.apt.NoArgsConstructor")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class NoArgsConstructorProcessor extends AbstractProcessor {
  public boolean process(Setextends TypeElement> annotations,
                         RoundEnvironment env) {
    for (TypeElement type : annotations) {
      processNoArgsConstructorClasses(env, type);
    }
    return true;
  }

  private void processNoArgsConstructorClasses(
      RoundEnvironment env, TypeElement type) {
    for (Element element : env.getElementsAnnotatedWith(type)) {
      processClass(element);
    }
  }

  private void processClass(Element element) {
    if (!doesClassContainNoArgsConstructor(element)) {
      processingEnv.getMessager().printMessage(
          Diagnostic.Kind.ERROR,
          "Class " + element + " needs a No-Args Constructor");
    }
  }

  private boolean doesClassContainNoArgsConstructor(Element el) {
    for (Element subelement : el.getEnclosedElements()) {
      if (subelement.getKind() == ElementKind.CONSTRUCTOR &&
          subelement.getModifiers().contains(Modifier.PUBLIC)) {
        TypeMirror mirror = subelement.asType();
        if (mirror.accept(noArgsVisitor, null)) return true;
      }
    }
    return false;
  }

  private static final TypeVisitor noArgsVisitor =
      new SimpleTypeVisitor6() {
        public Boolean visitExecutable(ExecutableType t, Void v) {
          return t.getParameterTypes().isEmpty();
        }
      };
}

Запакуем наши созданные файлы для аннотации в архивчик apttools.jar, при этом не забудем в нем создать файл META-INF/services/javax.annotation.processing.Processor, а внем вписать строку:

eu.javaspecialists.tools.apt.NoArgsConstructorProcessor

А теперь создадим код для тестирования нашей аннотации:
import eu.javaspecialists.tools.apt.NoArgsConstructor;

@NoArgsConstructor
public abstract class NoArgsSuperClass {
  public NoArgsSuperClass() {
  }
}

// Passes
public class PublicNoArgsConstructor extends NoArgsSuperClass {
  public PublicNoArgsConstructor() {
  }
}

// Passes
public class DefaultConstructor extends NoArgsSuperClass {
}

// Passes
public class SeveralConstructors extends NoArgsSuperClass {
  public SeveralConstructors(String as) {
  }

  public SeveralConstructors(int ai) {
  }

  public SeveralConstructors() {
  }
}

// Fails
public class NonPublicConstructor extends NoArgsSuperClass {
  NonPublicConstructor() {
  }
}

// Fails
public class WrongConstructor extends NoArgsSuperClass {
  public WrongConstructor(String aString) {
  }
}

И попрообуем его откомпилировать с учетом наших аннотаций:
javac -classpath .;apttools.jar -processorpath apttools.jar *.java

В результате получим следующий результат в консоли:
error: Class NonPublicConstructor needs a No-Args Constructor
error: Class WrongConstructor needs a No-Args Constructor
2 errors

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

Отправить комментарий