Назад Зміст Вперед

3.7. Aнотації

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

У наступному коді приведено оголошення анотації. / * Приклад # 20: Багаточленна анотація: RequestForCustomer.java * / package chapt03;
public @interface RequestForCustomer { int level ();
String description ();
String date ();


Ключовому слову interface передує символ @. Такий запис повідомляє компілятору про оголошення анотації. В оголошенні також є три методи-члена: int level (), String description (), String date (). 

Всі анотації містять тільки оголошення методів, додавати тіла цим методам не потрібно, так як їх реалізує сама мова. Крім того, ці методи не можуть містити параметрів, секції throws і діють швидше як поля. Допустимі типи значення, що повертається: базові типи, String, Enum, Class і масив будь-якого з перерахованих вище типів

Всі типи анотацій автоматично розширюють інтерфейс java.lang.annotation.Annotation. У цьому інтерфейсі дані методи: hashCode (), equals () і toString (), певні в типі Object. У ньому також наведено метод annotationType (), який повертає об'єкт типу Class, який представляє викликану анотацію. Після оголошення анотації її можна використовувати для анотування оголошень. Оголошення будь-якого типу може мати анотацію, пов'язану з ним. Наприклад, можна постачати примітками класи, методи, поля, параметри і константи типу enum. Навіть до анотації можна додати анотацію. У всіх випадках анотація передує оголошенню. 

Застосовуючи анотацію, потрібно задавати значення для її методів-членів. Нижче наведено фрагмент, в якому анотація RequestForCustomer супроводжує оголошення методу: @RequestForCustomer (
level = 2,
description = "Enable time",
date = "10/10/2007"
)
public void customerThroughTime () {
// ...


Дана інструкція пов'язана з методом customerThroughTime (). За ім'ям анотації, що починається з символу @, слід взятий у круглі дужки список ініціюючих значень для методів-членів. Для того щоб передати значення методу-члену, імені цього методу присвоюється значення. Таким чином, у наведеному фрагменті рядок "Enable time" присвоюється методу description (), члену анотації типу RequestForCustomer. При цьому в присвоєнні після імені description немає круглих дужок. Коли методу-члену передається значення, яке ініціалізується, використовується тільки ім'я методу. Отже, в даному контексті методи-члени виглядають як поля. / * Приклад # 21: застосування анотації: Request.java * / 
package chapt03; 
import java.lang.reflect.Method; 
public class Request { 
@RequestForCustomer (level = 2, 
description = "Enable time", 
date = "10/10/2007") 
public void customerThroughTime () { 
try { 
Class c = this.getClass (); 
Method m = c.getMethod ( "customerThroughTime"); 
RequestForCustomer ann = 
m. getAnnotation (RequestForCustomer.class); 
// запит анотацій 
System.out.println (ann.level () + "" 
+ ann.description () + "" 
+ ann.date ()); 
} catch (NoSuchMethodException e) { 
System .out.println ( "метод не знайдений"); 


public static void main (String [] args) { 
Request ob = new Request (); 
ob.customerThroughTime (); 


В результаті буде виведено: 2 Enable time 10 / 10/2007 

Якщо анотація оголошується в окремому файлі, то їй необхідно задати правило збереження RUNTIME у вигляді коду, який розміщується перед оголошенням анотації, яке надає максимальну тривалість існування анотації: 
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention (RetentionPolicy.RUNTIME)
// правило збереження 

З правилом SOURCE анотація існує тільки в початковому тексті програми і відкидається під час компіляції. 

Анотація до правила збереження CLASS поміщається в процесі компіляції в файл .class, але недоступна в JVM під час виконання.

Анотація, задана з правилом збереження RUNTIME, поміщається в файл .class в процесі компіляції і доступна в JVM під час виконання. Отже, правило RUNTIME пропонує максимальну тривалість існування анотації. 

Основні типи анотацій: анотація-маркер, одночленна і багаточленна. Анотація-маркер не містить методів-членів. Мета - позначити оголошення. В цьому випадку достатньо присутності анотації. Оскільки у інтерфейсу анотації-маркера немає методів-членів, досить визначити наявність анотації. Public @interface TigerAnnotation {} 
Для перевірки наявності анотації-маркера використовується метод isAnnotationPresent ()

Одночленна анотація містить єдиний метод-член. Для цього типу анотації допускається коротка умовна форма завдання значення для методу-члена. Якщо є тільки один метод-член, то просто вказується його значення при створенні анотації. Ім'я методу-члена вказувати не потрібно. Але для того щоб скористатися короткою формою, слід для методу-члена використовувати ім'я value ()

Багаточленні анотації містять кілька методів-членів. Тому використовується повний синтаксис (ім'я_параметра = значення) для кожного параметра. 

У мові Java визначено сім типів вбудованих анотацій
чотири типи - @Retention, @Documented, @Target і @Inherited - імпортуються з пакета java.lang.annotation. 
Решта три - @Override, @Deprecated і @Suppresswarnings - з пакету java.lang

Анотації отримують все більш широке поширення і активно використовуються в різних технологіях.




Синтаксис
Анотація задається описом відповідного інтерфейсу.
Наприклад так:
import java.lang.annotation.*; 
@Target(value=ElementType.FIELD) 
@Retention(value= RetentionPolicy.RUNTIME) public
 @interface Name { 
String name(); String type() default “string”;
}
Як видно з прикладу вище, анотація визначається описом з ключовим словом interface і може включати в себе кілька полів, які можна задати як обов'язковими, так і необов'язковими. В останньому випадку підставляється default значення поля.


Також з прикладу видно, що саму анотацію можна помітити декількома анотаціями. Розберемося для початку, ніж можна помітити власну анотацію, і навіщо.


Анотація @Retention дозволяє вказати життєвий цикл анотації: буде вона бути присутнім тільки в вихідному коді, в скомпільованому файлі, або вона буде також видно і в процесі виконання. Вибір потрібного типу залежить від того, як ви хочете використовувати анотацію, наприклад, генерувати щось побічне з вихідних кодів, або в процесі виконання стукати до класу через reflection.


Анотація @Target вказує, що саме ми можемо помітити цієї анотацією, це може бути поле, метод, тип і т.д.

Анотація @Documented вказує, що позначені таким чином анотація повинна бути додана в javadoc поля / методу і т.д.
Наприклад, клас, позначений анотацією без @Documented, буде виглядати так:


public class TestClass extends java.lang.Object
extends java.lang.Object
extends java.lang.Object
extends java.lang.Object


А якщо в опис анотації додати @Documented, отримаємо:

@ControlledObject(name="name") 
public class TestClass extends java.lang.Object

Анотація @Inherited позначає анотацію, яка буде успадкована нащадком класу, зазначеного такою анотацією.


Приклади анотацій:

@Inherited @interface PublicAnnotate { } 
@interface PrivateAnnotate { } 
@PublicAnnotate 
@PrivateAnnotate class ParentClass { } class ChildClass extends ParentClass { }
Клас ChildClass успадкує від батьківського класу тільки анотацію PublicAnnotate.


Спробуємо тепер написати робочий приклад з використанням анотацій.
Уявімо собі, що є проект, який на вхід отримує клас, спеціально занотований, щоб проект міг управляти життєвим циклом об'єктів цього класу, і нехай там будуть анотації StartObject, StopObject для опису методів класу, і ControlledObject для опису самого класу. Останньою анотації дамо ще поле name, нехай там зберігається нібито ім'я для пошуку.

Анотації будуть виглядати так:

@Target(value=ElementType.METHOD) 
@Retention(value= RetentionPolicy.RUNTIME) public 
@interface StartObject { } 
@Target(value=ElementType.METHOD) 
@Retention(value= RetentionPolicy.RUNTIME) public 
@interface StopObject { } 
@Target(value=ElementType.TYPE) 
@Retention(value= RetentionPolicy.RUNTIME) public 
@interface ControlledObject { String name(); }

Напишемо модуль, який перевіряє чи підходить клас для завантаження в наш проект чи ні.

Спершу визначимо клас.

@ControlledObject(name="biscuits") public class Cookies { 
@StartObject public void createCookie(){
 //бізнес логіка 
@StopObject public void stopCookie(){
 //бізнес логіка 
}

Для того, щоб працювати з класом, спочатку необхідно завантажити клас в контекст програми. Використовуємо:
Class cl = Class.forName("org.annotate.test.classes.Cookies");


Далі, через механізм reflection ми отримуємо доступ до полів і анотацій класу.
Перевіримо наявність анотованих методів в класі і анотації на самому класі:

if(!cl.isAnnotationPresent(ControlledObject.class)){ 
System.err.println("no annotation"); 
} else { 
System.out.println("class annotated ; name - " + cl.getAnnotation(ControlledObject.class)); 
boolean hasStart=false; 
boolean hasStop=false; 
Method[] method = cl.getMethods(); 
for(Method md: method){ 
if(md.isAnnotationPresent(StartObject.class)) {
hasStart=true;
if(md.isAnnotationPresent(StopObject.class)) {
hasStop=true;
} if(hasStart && hasStop){
break;
System.out.println("Start annotaton - " + hasStart + "; Stop annotation - " + hasStop );

Отримаємо:
Start annotaton - true; 
Stop annotation - true.


Якщо спробувати прибрати одну з анотацій, то висновок повідомить про невідповідність вимогам.

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

.