Форум администраторов игровых серверов

Форум администраторов игровых серверов (https://forum.zone-game.info/TT.php)
-   Java (https://forum.zone-game.info/forumdisplay.php?f=126)
-   -   Java annotation (https://forum.zone-game.info/showthread.php?t=15712)

Azagthtot 28.08.2011 18:51

Java annotation
 
Вложений: 1
Java предлагает разработчику очень интересный механизм аннотаций.
Что такое аннотация? А это все, что начинается с @ например,
@Override :)
Однако не секрет, что многие неплохие l2 разработчики не пользуются ничем кроме вышеупомянутой @Override, да и вообще плохо представляют преимущества этого механизма. То что описано ниже, предназначено "пролить луч света в темном царстве".
И так.
Что такое аннотация, и как ее можно использовать в мирных целях.
Начнем с того, какие этапы бывают в жизни программы.
Этап первый - написание (зачатие) он нам совершенно не интересен :)
Этап второй - компиляция. Здесь уже возможно использование аннотаций. Например та же @Override говорит компилятору "пройдись по суперкласам, поищи метод с тем же именем и теми же параметрами. Не найдешь - скажи программисту, что он идиот". Этот этап интересен, но не очень.
Этап третий - выполнение. Вот тут мы и будем творить.
На этом этапе аннотация становится частью RTTI. Что такое RTTI?
Это Run-Time Type Information.Что, не знаете что такое? Однако пользуетесь :) Пример?
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") - не верно

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

Код:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface XMLField {
 String node() default "";
 String property();
}

Во-первых, мы рассказали компилятору, что аннотация применима для поля (@Target), во вторых, сказали, что бы включил в RTTI (@Retention)
Ну вот, аннотацию написали. Теперь будем учится ползать по классу, и делать много нехороших вещей (например, плевать на модификаторы private и protected :) )
Собственно "скелет" нашего 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

Опять работает, странно, да? :)

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

Aquanox 02.09.2011 23:47

Re: Java annotation
 
Аннотации это хорошо, досихпор работаю с собственным фреймворком конфигурации на базе метаданных (с валидацией данных и кучей удобных плюшек) - доволен я, довольны знакомые.

Зачем изобретать 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. Слишком комплексные методы - лучше бы их по сервисным классам распихать, либо на более мелкие поделить.

Azagthtot 03.09.2011 06:10

Re: Java annotation
 
Спасибо за замечания. Однако, целью этого "урока" было показать как работают аннотации, а не использование готовых. Я сознательно "изобретал велосипед" в примерах, что бы было понятно как весь этот механизм работает.
То же самое я могу и сказать про setFieldValue. Я не ставил себе задачу сделать рабочий фреймворк.

Extez1 03.09.2011 12:26

Re: Java annotation
 
Спасибо хорошая тема.


Текущее время: 18:04. Часовой пояс GMT +3.

Powered by vBulletin® Version 3.8.6
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd. Перевод: zCarot