РЕГИСТРАЦИЯ |
EMAIL
ПАРОЛЬ

Michael Milonov
© Fusionsoft

Несмотря на то, что полноценный механизм аннотаций в языке Java появился относительно недавно, многие разработчики успели оценить его удобство и успешно применяют данную возможность Java API при написании программ. Аннотации, имея много очевидных преимуществ, как всякое новое средство не лишены недостатков. Одним из них является то, что аннотации не наследуются в производных классах. Данная особенность вносит некоторое неудобство в использование аннотаций и может оказаться критичной, если над пользовательскими Java классами используются различные обертки, создаваемые через механизм reflection. В этом случае нет возможности добавить аннотации в генерируемый класс, и приходится отказываться от использования этого удобного инструмента декларативного описания поведения программы. Для устранения недостатка компанией Fusionsoft разработана библиотека наследуемых аннотаций, речь о которой пойдет ниже. Библиотека доступна в исходных кодах, абсолютно бесплатна и не имеет ограничений на коммерческое использование.

 

Требования

Java SDK 1.5 или выше;
junit-4.2 для выполнения тестового примера.

 

Описание проблемы

Рассмотрим следующий пример:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface A {}
	
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface B {}
	
@A
public interface BaseInterface {
	@B
	public void method1();
}

public class BaseClass {
	@B
	public void method2(){}
}

public class Derived extends BaseClass implements BaseInterface{
	public void method1(){}
	public void method2(){}
}

В этом примере мы объявили два типа аннотации: аннотацию @A для типов, и аннотацию @B для методов. Использовали эти аннотации для описания некоторого базового класса и интерфейса. Создали производный класс - наследник базового класса и интерфейса.

В соответствии с правилами аннотирования Java, ни производный класс, ни его методы не наследуют тех аннотаций, которые были объявлены в базовых классе и интерфейсе. Поэтому следующий код вернет пустоту в обоих случаях:

Derived.class.getMethod(
		"method1", new Class[0]).getAnnotation(B.class)

Derived.class.getMethod(
		"method2", new Class[0]).getAnnotation(B.class)

 

Доступ к аннотациям базовых классов

Основная идея получения аннотаций в классах-наследниках состоит в том, что их можно извлечь через механизм reflection у базовых классов, которые являются аннотированными. Для удобства пользования наследуемыми аннотациями их можно централизованно хранить и извлекать, используя имя классов и сигнатуры методов в качестве ключей для получения аннотаций.

Продукт предоставляет централизованное хранилище для получения аннотированных классов:

public class AnnotationManager {
	private static Map<Class <?>, AnnotatedClass> classToAnnotatedMap =
		new HashMap<Class<?>, AnnotatedClass>();
	
	/**
	 * @param theClass to wrap.
	 * @return the annotated class wrapping the specified one.
	 */
	public static AnnotatedClass getAnnotatedClass(Class<?> theClass){
		AnnotatedClass annotatedClass = classToAnnotatedMap.get(theClass);
		if (annotatedClass == null){
			annotatedClass = new AnnotatedClassImpl(theClass);
			classToAnnotatedMap.put(theClass, annotatedClass);
		}
		return annotatedClass;
	}
}

Обращаясь к статическому методу getAnnotatedClass можно получить объект AnnotatedClass, содержащий аннотации всех предков запрашиваемого класса. AnnotatedClass используя методы reflection: Class.getInterfaces, Class.getDeclaredAnnotations, рекурсивно проходит по всем предками запрашиваемого класса и кеширует аннотации, ассоциируя их с аннотированными интерфейсами и классами. При этом процедура заполнения HashMap с аннотациями для класса будет выглядеть следующим образом:

private Map<Class<?>, Annotation> getAllAnnotationMapCalculated(){
		HashMap result = new 
                    HashMap<Class<?>, Annotation>();
		
		final Class<?> superClass = getTheClass().getSuperclass();
		// Get the superclass's annotations
		if (superClass != null)
			fillAnnotationsForOneClass(result, superClass);

		// Get the superinterfaces' annotations
		for (Class<?> c : getTheClass().getInterfaces())
			fillAnnotationsForOneClass(result, c);
		
		// Get its own annotations. They have preference to inherited
            //annotations.
		for (Annotation annotation : getTheClass().
                                       getDeclaredAnnotations())
			result.put(annotation.getClass().
                      getInterfaces()[0], annotation);
		return result;
}

Здесь используется lazy-initialization pattern для доступа к аннотациям: они кэшируются только во время первого обращения к ним.

 

Использование библиотеки наследуемых аннотаций

Итак, как же будет выглядеть доступ к JAVA аннотациям с использованием библиотеки наследуемых аннотаций? Доступ к наследуемым аннотациям аналогичен стандартным аннотациям с тем отличием, что используются специальные классы библиотеки взамен методов Java API.

В случае нашего примера доступ к аннотациям будет выглядеть следующим образом:

AnnotatedClass annotatedClass = 
			AnnotationManager.getAnnotatedClass(Derived.class);
annotatedClass.getAnnotation(A.class);

AnnotatedMethod annotatedMethod = annotatedClass
		.getAnnotatedMethod("method1", new Class[0]);
annotatedMethod.getAnnotation(B.class);

annotatedMethod = annotatedClass
		.getAnnotatedMethod("method2", new Class[0]);
annotatedMethod.getAnnotation(B.class);

Поскольку method1 и method2 не имеют параметров, то в качестве параметра функции getAnnotatedMethod используется пустой массив new Class[0].

 

Заключение

Использование метаинформации в коде Java программы и декларативное описание поведения тех или иных функций зачастую очень удобно и лаконично. И возможность наследования может оказаться весьма кстати. Библиотека наследуемых JAVA аннотаций предоставляет такую возможность. Самую свежую версию библиотеки можно скачать здесь http://fusionsoft-online.com/download/static/products/annotation.zip. Мы будем рады услышать Ваши отзывы и мнения, отправляйте их по адресу info@fusionsoft-online.com.