W poprzednim wpisie o adnotacjach opisałem jak tworzyć adnotacje w Javie. I co dalej? Mamy już naszą adnotacje, więc wypadałoby ją jakoś użyć. W tym wpisie pokaże w jaki sposób możemy użyć naszych adnotacji.

Małe uzupełnienie poprzedniego wpisu

W poprzednim wpisie pominąłem jedną (przynajmniej :P) istotną sprawę przy tworzeniu adnotacji.

Dziedziczenie adnotacji

Przy tworzeniu adnotacji możemy użyć również @Inherited, który powoduje, że adnotacja będzie widoczną również jako obecna na klasach, które dziedziczą po klasie z tą adnotacją.

Zdefiniujmy naszą adnotacje następująco:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}

Klasę, która będzie oznaczona naszą adnotacją:

@MyAnnotation
public class MySuperClass {
}

I naszą klasę dziedziczącą po MySuperClass:

public class MyClass extends MySuperClass {
}

W ten sposób MyAnnotation będzię widoczne również na MyClass.

Wracając do tematu…

Wydaję mi się, że najpopularniejszym przypadkiem użycia adnotacji są różnego rodzaju frameworki jak np. Hibernate czy Spring.

Jeżeli mamy naszą klasę oznaczoną np. adnotacją @Entity, to jak framework 'znajduje’ informacje, że dana klasa ma właśnie tą adnotacje?

Jako przykład weźmy prostą adnotacje:

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
	String name();
}

oraz klasę:

@MyAnnotation(name = "Santa")
public class MyClass {
	private int a;
}

I teraz spróbujemy dobrać się do naszej adnotacji 🙂

Zacznijmy może od początku – czyli warto byłoby najpierw znaleźć klasy z adnotacjami, żebyśmy wiedzieli z czym będziemy pracować 😉

W Springu definiujemy pakiety, które mają być skanowane pod kątem adnotacji. Wyjdziemy z tego samego założenia – nie ma sensu skanować wszystkich klas, jeżeli nie jest to konieczne 🙂

Stwórzmy zatem dwie metody, dzięki którym będziemy mogli przejść rekurencyjnie po wszystkich folderach z danego pakietu o zwrócić listę wszystkich klas, adnotacji i interfejsów.

private static List<Class<?>> getClasses(final String packageName) throws Exception {
	// pobieramy Class Loader
	ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	// tworzymy nasza 'wyjsciowa' sciezke do rozpoczecia poszukiwan innych klas
	String path = packageName.replace('.', '/');
	// pobieramy liste wszystkich plikow z naszej wyjsciowej sciezki
	Enumeration<URL> resources = classLoader.getResources(path);

	List<Class<?>> classes = new ArrayList<Class<?>>();
	// iteratujemy po wszystkich plikach, znajdujemy wszystkie klasy z danego folderu i dodajemy do listy
	while (resources.hasMoreElements()) {
		final File dir = new File(resources.nextElement().getFile());

		classes.addAll(findClasses(dir, packageName));
	}
	
	// zwracamy liste znalezionych klas
	return classes;
}
private static List<Class<?>> findClasses(File directory, String packageName) throws Exception {
	final String _class = ".class";
	List<Class<?>> classes = new ArrayList<Class<?>>();
	// iterujemy po wszystkich plikach z danej lokalizacji
	for (File file : directory.listFiles()) {
		// jezeli obiekt jest folderem
		if (file.isDirectory()) {
			// to dodajemy jego nazwe do nazwy pakietu i iteracyjnie bedziemy dalej szukac klas
			classes.addAll(findClasses(file, packageName + "." + file.getName()));
		// jezeli plik jest klasa
		} else if (file.getName().endsWith(_class)) {
			// to za pomoca Class.forName ladujemy nasza klase, a nastepnie dodajemy do zwracanej listy
			classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - _class.length())));
		}
	}
	return classes;
}

Metody są okraszone komentarzami, więc nie powinno być problemu ze zrozumieniem co tam się dzieje.

Metody nie robią co prawda nic skomplikowanego, ale po co pisać coś, co zostało już raz dobrze napisane 😉 Metody te pokazałem, żeby przedstawić jak to wygląda od podstaw, w najprostszej formie. W życiu codziennym polecam używanie odpowiednich metod z Spring, Guavy czy np. Reflections Library.

I teraz samo znajdowanie klas z naszą adnotacją:

public static void main(String[] args) throws Exception {
	// zmajdujemy wszystkie klasy z pakietu pl.lantkowiak
	List<Class<?>> classes = getClasses("pl.lantkowiak");
	
	for (Class<?> clazz : classes) {
		// pobieramy nasza adnotacje
		final MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
		
		// jezeli adnotacja jest nullem to oznacza, ze dana klasa nie posiada tej adnotacji
		if (annotation != null) {
			String name = annotation.name();
			
			System.out.println(clazz + " : " + name);
		}
	}
}

Metoda wypisze na ekranie następującą linie:

class pl.lantkowiak.MyClass : Santa

Jak widać udało nam się znaleźć wszystkie (całą jedną w tym konkretnym przypadku ;)) klasy z adnotacją @MyAnnotation i odczytać wartości atrybutów adnotacji.

Podsumowanie

We wpisie został pokazany sposób w jaki można znaleźć klasy z konkretnego pakietu z konkretną adnotacją. Dzięki temu możemy zdefiniować własne zachowania/akcje dla klas z danymi adnotacjami.

Oczywiście wpis ten nie wyczerpuje tematu. Mechanizm refleksji dostarcza wielu innych metod związanych ze znajdywaniem klas czy adnotacji. Zachęcam do zagłębienia się w ten temat 🙂