Рейтинг темы:
  • 0 Голос(ов) - 0 в среднем
  • 1
  • 2
  • 3
  • 4
  • 5
Java annotation
#1
Java предлагает разработчику очень интересный механизм аннотаций.
Что такое аннотация? А это все, что начинается с @ например,
@Override Smile
Однако не секрет, что многие неплохие l2 разработчики не пользуются ничем кроме вышеупомянутой @Override, да и вообще плохо представляют преимущества этого механизма. То что описано ниже, предназначено "пролить луч света в темном царстве".
И так.
Что такое аннотация, и как ее можно использовать в мирных целях.
Начнем с того, какие этапы бывают в жизни программы.
Этап первый - написание (зачатие) он нам совершенно не интересен Smile
Этап второй - компиляция. Здесь уже возможно использование аннотаций. Например та же @Override говорит компилятору "пройдись по суперкласам, поищи метод с тем же именем и теми же параметрами. Не найдешь - скажи программисту, что он идиот". Этот этап интересен, но не очень.
Этап третий - выполнение. Вот тут мы и будем творить.
На этом этапе аннотация становится частью RTTI. Что такое RTTI?
Это Run-Time Type Information.Что, не знаете что такое? Однако пользуетесь Smile Пример?
a instanceof Object
это типичное использование RTTI, ибо класс объекта есть его элемент.
Вообще, для работы с RTTI есть куча методов класса Class (собственно класс Class это и есть RTTI).
И так, зачем мы можем использовать аннотации?
Вообще-то, использование аннотаций основано на принципе "лучше полдня потерять, потом за 5 мин долететь". Вот простой, и жизненный пример.
У нас есть xml файл вида
Код:
<?xml version="1.0" ?>
<objects>
<object name="zzzz"/>
<object name="mmm"/>
</objects>

И мы хотим загружать из него следующие объекты
Код:
public class NamedObject {
  public String name;
}
Нет ничего проще!
Код:
public class NamedObject {
  public String name;
  public void load(Node node) {
     name = node.getAttributes().getNamedItem("name").getNodeValue();
}
}
А теперь, представьте, что ваша мысль несется в даль, и у нашего NameObject появилось еще поле title?
- Да не вопрос, строка кода!
А 100500 новых полей?
"у, мля" - сказали программисты и ушли писать код....
А теперь изображу то же самое с использование аннотаций
Код:
public class NamedObject extends AnnotatedXMLNode {
@XMLField(property="name")
public String name;
}
Добавить поле?
Код:
@XMLField(nodename="weight",property="set")
public int weight;
Добавить 100500 полей? Ну вы поняли....
Все дело в "волшебном классе" AnnotatedXMLNode. Вот его, да и сами аннотации мы будем разбирать ниже.

NB! Буду писать несколькими постами, с интервалом в 10-15 мин. С примерами. Просьба писать ваши вопросы после поста "на сегодня все".

Добавлено через 30 минут
И так, для начала напишем сами используемые аннотации.
Описание аннотации выглядит как описание интерфейса, только перед словом interface добавляется символ @
Код:
public @interface XMLField {
String node() default "";
String property();
}
Вы наверное обратили на отсутствие модификаторов видимости, и на слово default.
модификаторы не нужны, а default означает что данное поле можно не указывать.
т.е.
@XMLField(property="zzz",node="mmm") - верно
@XMLField(property="zzz") - верно
@XMLField(node="mmm") - не верно

И так, аннотацию мы сделали. Теперь неплохо бы сказать, когда она используется (компиляция, исполнение) и к какому типу применима (класс, поле). Делается это посредством... вы таки не поверите, но аннотаций Smile

Код:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface XMLField {
String node() default "";
String property();
}
Во-первых, мы рассказали компилятору, что аннотация применима для поля (@Target), во вторых, сказали, что бы включил в RTTI (@Retention)
Ну вот, аннотацию написали. Теперь будем учится ползать по классу, и делать много нехороших вещей (например, плевать на модификаторы private и protected Smile )
Собственно "скелет" нашего AnnotatedXMLNode
Код:
public class AnnotatedXMLNode {
public void load(Node node) {
}
}
Соответственно, для загрузки мы будем использовать метод load().
А вот теперь начнется самое интересное. Для начала нам надо найти все поля, во всех суперклассах имеющих аннотацию Node.
Как ищется аннотация? Да очень просто!
У класса Field есть метод isAnnotationPresent (впрочем, он есть у любого класса RTTI, как Class, Field, Method и т.д.)
Вот с помощью него мы и будем искать аннотации.
Кто-то спросит, а как полчить все поля класса?. Есть метод getDeclaredField у Class. Но тут есть грабли. Вот пример граблей.
Код:
public  class A {
   private int _a;
   public void check() {
      for(Field f : this.getClass().getDeclaredFields() ) {
           System.out.println(f.getName());
       }
    }
}
public class B extends A {
public String _b;
}
...
...
B b = new B();
b.check();
Кто то ожидал увидеть вывод
_a
_b
?
А вот фиг вам! getDeclaredFields() работает ТОЛЬКО для текущего класса. Так что будет
_b
Как нам получить все поля?
А очень просто
Код:
public void check() {
      Class<?> clazz = getClass();
      while(clazz!=Object.class) { // Спасибо, поля Object нас не инетерсуют
         for(Field f : clazz.getDeclaredFields() ) {
             System.out.println(f.getName());
         }
         clazz = clazz.getSuperClass();
}
Мы идем по суперклассам до Object. И вытягиваем все поля.
}

Добавлено через 3 часа 53 минуты
Теперь начнем писать наш класс. Для начала соберем все поля, которые требуют чтения.
Код:
private void Map<String,Map<String,Field>> _fields = new HashMap<String,Map<String,Field>>();
private void collectFields() {
  Class<?> clazz = getClass();
  while(clazz!=AnnotatedXMLNode.class) {
       for(java.lang.reflect.Field f : clazz.getDeclaredFields() ) {
                if(f.isAnnotationPresent(XMLField.class)) {
                         XMLField annotation = f.getAnnotation(XMLField.class);
                         Map<String, java.lang.reflect.Field> m = _fields.get(annotation.node());
                         if(m==null) {
                            m = new HashMap<String, java.lang.reflect.Field>();
                            _fields.put(annotation.node(),m);
                          }
                          m.put(annotation.property(),f);
                  }
               }
            }
             clazz = clazz.getSuperClass();
       }
}
Таким образом, мы получим map содержащий в качестве ключа имя xml-узлов.
P.S. Уже поздно, дочь не дала дописать все за сегодня. Продолжу с утра

Добавлено через 16 часов 39 минут
Продолжаем изучение аннотаций. И так, мап с сответствием имя-поле мы получили, теперь вроде бы можем писать метод. Однако есть еще один "грабельки". Все поля у нас разного типа, а при работе с xml возвращается только тип String. Тут есть два способа. Первый - написать простейший сеттер. Второй - "подсмотреть" в Aion как сделаны трансфоромации STring --> type. Я приведу первый способ, работающий с двумя типами String и int
Код:
private void setFieldVlue(Field f, String value) throws Exception {
   f.setAccessible(true); // Это магический вызов :) Все protected и private поля становятся доступными.
// А вы что хотели, область видимости - это не защита данных а инструмент для проектирования
   if(f.getType()==String.class)
       f.set(this,value);
   else if(f.getType() == int.class)
        f.set(this,Integer.valueOf(value));
   else
      throw new Exception("Invalid field type");
}
Вот такой простенький метод. Ну а теперь поехали делать собственно загрузку
Код:
    public void load(Node node) {

           collectFields(); // Собираем поля
           Map<String,java.lang.reflect.Field> fields = _fields.get(""); // Берем поля, для которых надо использовать атрибуты из корневой ноды
           if(fields!=null) {
               for(Entry<String, java.lang.reflect.Field> e : fields.entrySet()) {
                  Node n = node.getAttributes().getNamedItem(e.getKey());
                  if(n!=null) try {
                       setFieldValue(e.getValue(),n.getNodeValue());
                  } catch(Exception err) {
                      System.out.println("Error setting field value");
                      err.printStackTrace();
                  }
               }
           }
           for(Node n = node.getFirstChild();n!=null;n=n.getNextSibling()) {
               fields = _fields.get(n.getNodeName());
               if(fields!=null) {
                 for(Entry<String, java.lang.reflect.Field> e : fields.entrySet()) {
                    Node n1 = n.getAttributes().getNamedItem(e.getKey());
                      if(n1!=null) try {
                           setFieldValue(e.getValue(),n1.getNodeValue());
                      } catch(Exception err) {
                          System.out.println("Error setting field value");
                          err.printStackTrace();
                      }
                 }
               }
             }
    }
Теперь приступим к использованию, нашего чудо-класса
Собственно сам наш загружемай класс
Код:
public class MyXMLObject extends AnnotatedXMLNode {
    @XMLField(property="name")
    public String name;

    @Override
    public String toString() {
        return "Name: "+name;
    }

}
А теперь - загрузчик
Код:
public class Main {
    public static void main(String [] args) {
        DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance();
        builder.setValidating(false);
        builder.setIgnoringComments(true);
        File file = new File("./sample.xml");
        try {
            Document doc = builder.newDocumentBuilder().parse(file);
            XPath xpath = XPathFactory.newInstance().newXPath(); // Итерировать? Нафиг! Есть Xpath!!!!
            XPathExpression expr = xpath.compile("//object");
            NodeList objects = (NodeList)expr.evaluate(doc, XPathConstants.NODESET);
            for(int i=0;i<objects.getLength();i++) {
                Node n = objects.item(i);
                MyXMLObject obj = new MyXMLObject();
                obj.load(n);
                System.out.println(obj);
            }
            
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

Обратите внимание, для поиска в XML я использую не поиск по нодам вручную,а Xpath. Это проще и удобнее.
Ну и сам XML-файл
Код:
<?xml version="1.0" ?>
<objects>
  <object name="First">
    <set value="1"/>
  </object>
  <object name="Second">
    <set value="2"/>
  </object>

</objects>
Запускаем...
Код:
G:\Samples\Annotations\build>java -cp ../bin Main
Name: First
Name: Second
Работает!!!! Добавляем поле value


Код:
public class MyXMLObject extends AnnotatedXMLNode {
    @XMLField(property="name")
    public String name;
    @XMLField(property="value",node="set")
    private int val;
    
    @Override
    public String toString() {
        return "Name: "+name+", val :"+val;
    }
}
Запускаем
Код:
G:\Samples\Annotations\build>java -cp ../bin Main
Name: First, val :1
Name: Second, val :2
Опять работает, странно, да? Smile

Ну вот, на сегодня все, задавайте вопросы.
Ах, да. Все примеры в виде Eclipse-проекта - внизу


Файлы вложений
.zip   Annotations.zip (Размер: 11.01 KB / Загрузок: 8)
Ответ
#2
Аннотации это хорошо, досихпор работаю с собственным фреймворком конфигурации на базе метаданных (с валидацией данных и кучей удобных плюшек) - доволен я, довольны знакомые.

Зачем изобретать JAXB и XMLBeans чтобы мапить поля объектов на xml? Если надо конфигурировать объекты через XML то целесообразнее использовать аналогичную систему с ANT'овыми файлом сборки.

Код:
private void setFieldVlue(Field f, String value)
прмитивы пойдут в разнос. посмотрите как это сделано в Spring Framework класс ReflectionUtils. Да и setAccessible() может швырнуть в лицо SecurityException при включенном SecurityManager (вечная головная боль). Плюс рекомендуется восстанавливать состояние доступа

Код:
boolean oldAccess = field.isAccessible();
field.setAccessible(true); // вне блока try/finally, можно внутри при случае если там не будет ожидаться другой SecurityException
try {
... код
} finally {
field.setAccessible(oldAccess);
}

P.S. Мало документации в коде, на аннотациях нет @Documented. Слишком комплексные методы - лучше бы их по сервисным классам распихать, либо на более мелкие поделить.
for(;Forum.getPostCount() < Integer.MAX_VALUE; Forum.writeNewPost()); | TERA Video | GamezTERA Emu
Ответ
#3
Спасибо за замечания. Однако, целью этого "урока" было показать как работают аннотации, а не использование готовых. Я сознательно "изобретал велосипед" в примерах, что бы было понятно как весь этот механизм работает.
То же самое я могу и сказать про setFieldValue. Я не ставил себе задачу сделать рабочий фреймворк.
Ответ
#4
Спасибо хорошая тема.
Ответ


Возможно похожие темы ...
Тема Автор Ответы Просмотры Последний пост
  Сервер майнкрафт 1.17 не видит Java 16 Erikvd2244 0 612 05-18-2023, 05:59 PM
Последний пост: Erikvd2244
  Требуется Java разработчик в RVR мод л2 slimak 0 2,426 03-19-2018, 11:08 PM
Последний пост: slimak
  Java Unicode Boris2105 1 2,819 08-10-2017, 02:09 AM
Последний пост: klubheads
  Java junior Tails 18 7,240 07-17-2017, 03:52 PM
Последний пост: n3k0nation
  Установить старую java ZIP 2 3,428 09-27-2016, 10:39 AM
Последний пост: Aquanox
  Учебники и книги по Java 2 CHIPSET 47 55,517 09-06-2016, 10:14 PM
Последний пост: sawayik
  Java == API == Libraries Kenrix 1 2,234 05-11-2014, 05:28 PM
Последний пост: n3k0nation
  Java 8 JavaMan 33 9,566 04-08-2014, 01:06 PM
Последний пост: SouthBridge
  Опрос от Оракла: Особенности Java EE 8 JavaMan 0 1,534 01-11-2014, 04:13 AM
Последний пост: JavaMan
  Java NB Задачи на последовательность NDWT 0 2,211 12-26-2013, 04:47 PM
Последний пост: NDWT

Перейти к форуму:


Пользователи, просматривающие эту тему: 1 Гость(ей)