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, iż 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ę, iż 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, iż 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, o ile 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 : SantaJak 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