Java предлагает разработчику очень интересный механизм аннотаций.
Что такое аннотация? А это все, что начинается с @ например,
@Override
Однако не секрет, что многие неплохие l2 разработчики не пользуются ничем кроме вышеупомянутой @Override, да и вообще плохо представляют преимущества этого механизма. То что описано ниже, предназначено "пролить луч света в темном царстве".
И так.
Что такое аннотация, и как ее можно использовать в мирных целях.
Начнем с того, какие этапы бывают в жизни программы.
Этап первый - написание (зачатие) он нам совершенно не интересен
Этап второй - компиляция. Здесь уже возможно использование аннотаций. Например та же @Override говорит компилятору "пройдись по суперкласам, поищи метод с тем же именем и теми же параметрами. Не найдешь - скажи программисту, что он идиот". Этот этап интересен, но не очень.
Этап третий - выполнение. Вот тут мы и будем творить.
На этом этапе аннотация становится частью RTTI. Что такое RTTI?
Это Run-Time Type Information.Что, не знаете что такое? Однако пользуетесь Пример?
a instanceof Object
это типичное использование RTTI, ибо класс объекта есть его элемент.
Вообще, для работы с RTTI есть куча методов класса Class (собственно класс Class это и есть RTTI).
И так, зачем мы можем использовать аннотации?
Вообще-то, использование аннотаций основано на принципе "лучше полдня потерять, потом за 5 мин долететь". Вот простой, и жизненный пример.
У нас есть xml файл вида
И мы хотим загружать из него следующие объекты
Нет ничего проще!
А теперь, представьте, что ваша мысль несется в даль, и у нашего NameObject появилось еще поле title?
- Да не вопрос, строка кода!
А 100500 новых полей?
"у, мля" - сказали программисты и ушли писать код....
А теперь изображу то же самое с использование аннотаций
Добавить поле?
Добавить 100500 полей? Ну вы поняли....
Все дело в "волшебном классе" AnnotatedXMLNode. Вот его, да и сами аннотации мы будем разбирать ниже.
NB! Буду писать несколькими постами, с интервалом в 10-15 мин. С примерами. Просьба писать ваши вопросы после поста "на сегодня все".
Добавлено через 30 минут
И так, для начала напишем сами используемые аннотации.
Описание аннотации выглядит как описание интерфейса, только перед словом interface добавляется символ @
Вы наверное обратили на отсутствие модификаторов видимости, и на слово default.
модификаторы не нужны, а default означает что данное поле можно не указывать.
т.е.
@XMLField(property="zzz",node="mmm") - верно
@XMLField(property="zzz") - верно
@XMLField(node="mmm") - не верно
И так, аннотацию мы сделали. Теперь неплохо бы сказать, когда она используется (компиляция, исполнение) и к какому типу применима (класс, поле). Делается это посредством... вы таки не поверите, но аннотаций
Во-первых, мы рассказали компилятору, что аннотация применима для поля (@Target), во вторых, сказали, что бы включил в RTTI (@Retention)
Ну вот, аннотацию написали. Теперь будем учится ползать по классу, и делать много нехороших вещей (например, плевать на модификаторы private и protected )
Собственно "скелет" нашего AnnotatedXMLNode
Соответственно, для загрузки мы будем использовать метод load().
А вот теперь начнется самое интересное. Для начала нам надо найти все поля, во всех суперклассах имеющих аннотацию Node.
Как ищется аннотация? Да очень просто!
У класса Field есть метод isAnnotationPresent (впрочем, он есть у любого класса RTTI, как Class, Field, Method и т.д.)
Вот с помощью него мы и будем искать аннотации.
Кто-то спросит, а как полчить все поля класса?. Есть метод getDeclaredField у Class. Но тут есть грабли. Вот пример граблей.
Кто то ожидал увидеть вывод
_a
_b
?
А вот фиг вам! getDeclaredFields() работает ТОЛЬКО для текущего класса. Так что будет
_b
Как нам получить все поля?
А очень просто
Мы идем по суперклассам до Object. И вытягиваем все поля.
}
Добавлено через 3 часа 53 минуты
Теперь начнем писать наш класс. Для начала соберем все поля, которые требуют чтения.
Таким образом, мы получим map содержащий в качестве ключа имя xml-узлов.
P.S. Уже поздно, дочь не дала дописать все за сегодня. Продолжу с утра
Добавлено через 16 часов 39 минут
Продолжаем изучение аннотаций. И так, мап с сответствием имя-поле мы получили, теперь вроде бы можем писать метод. Однако есть еще один "грабельки". Все поля у нас разного типа, а при работе с xml возвращается только тип String. Тут есть два способа. Первый - написать простейший сеттер. Второй - "подсмотреть" в Aion как сделаны трансфоромации STring --> type. Я приведу первый способ, работающий с двумя типами String и int
Вот такой простенький метод. Ну а теперь поехали делать собственно загрузку
Теперь приступим к использованию, нашего чудо-класса
Собственно сам наш загружемай класс
А теперь - загрузчик
Обратите внимание, для поиска в XML я использую не поиск по нодам вручную,а Xpath. Это проще и удобнее.
Ну и сам XML-файл
Запускаем...
Работает!!!! Добавляем поле value
Запускаем
Опять работает, странно, да?
Ну вот, на сегодня все, задавайте вопросы.
Ах, да. Все примеры в виде Eclipse-проекта - внизу
Что такое аннотация? А это все, что начинается с @ например,
@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();
}
}
- Да не вопрос, строка кода!
А 100500 новых полей?
"у, мля" - сказали программисты и ушли писать код....
А теперь изображу то же самое с использование аннотаций
Код:
public class NamedObject extends AnnotatedXMLNode {
@XMLField(property="name")
public String name;
}
Код:
@XMLField(nodename="weight",property="set")
public int weight;
Все дело в "волшебном классе" AnnotatedXMLNode. Вот его, да и сами аннотации мы будем разбирать ниже.
NB! Буду писать несколькими постами, с интервалом в 10-15 мин. С примерами. Просьба писать ваши вопросы после поста "на сегодня все".
Добавлено через 30 минут
И так, для начала напишем сами используемые аннотации.
Описание аннотации выглядит как описание интерфейса, только перед словом interface добавляется символ @
Код:
public @interface XMLField {
String node() default "";
String property();
}
модификаторы не нужны, а 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();
}
Ну вот, аннотацию написали. Теперь будем учится ползать по классу, и делать много нехороших вещей (например, плевать на модификаторы private и protected )
Собственно "скелет" нашего AnnotatedXMLNode
Код:
public class AnnotatedXMLNode {
public void load(Node node) {
}
}
А вот теперь начнется самое интересное. Для начала нам надо найти все поля, во всех суперклассах имеющих аннотацию 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();
}
}
Добавлено через 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();
}
}
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
Код:
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-проекта - внизу