Brak klas java wyszukiwanych za pomocą refleksji

Dzisiaj chciałbym podzielić się ciekawym przypadkiem, który zabrał mi trochę czasu zanim doszedłem do rozwiązania.
Przypadek dotyczył użycia refleksji w testach jednostkowych, aby sprawdzić czy ustalone konwencje nazewnicze są przestrzegane.

Problem - refleksja nie znajduje wszystkich klas

Załóżmy projekt, który ma 4 klasy o nazwach:

  • UsecaseA
  • UsecaseB
  • UsecaseC
  • UsecaseD

Wszystkie te klasy rozszerzają klasę AbstractUsecase.
Kod można podejrzeć tutaj: github.com/speedlog/reflection-cant-find-class/tree/main/src/main/java/pl/speedlog/example

Napisałem test, który miał sprawdzać istnienie klas, które rozszerzają AbstractUsecase.

 1@Test 
 2void searchUsecaseClasses() {
 3    Reflections reflections = new Reflections("pl.speedlog.example.reflection");
 4    Set < Class << ? extends AbstractUsecase >> classes = reflections.getSubTypesOf(AbstractUsecase.class);
 5    Set < Class << ? extends AbstractUsecase >> expectedList = new HashSet < > ();
 6    expectedList.add(UsecaseA.class);
 7    expectedList.add(UsecaseB.class);
 8    expectedList.add(UsecaseC.class);
 9    expectedList.add(UsecaseD.class);
10    Assertions.assertEquals(expectedList, classes);
11}

Okazuje się, że ten prosty test, który miał sprawdzić czy 4 klasy UsecaseA-D rozszerzają AbstractUsecase niestety kończył się błędem:

1Expected :[class pl.speedlog.example.reflection.domain2.UsecaseC, class pl.speedlog.example.reflection.domain1.UsecaseB, class pl.speedlog.example.reflection.domain2.UsecaseD, class pl.speedlog.example.reflection.domain1.UsecaseA]
2Actual   :[class pl.speedlog.example.reflection.domain2.UsecaseC, class pl.speedlog.example.reflection.domain1.UsecaseA]

Znajdywał tylko 2 klasy zamiast 4.

Rozwiązanie

Okazało się, że projekt odziedziczył zależność do biblioteki refleksji w sposób transparentny (poprzez inną bibliotekę). Na dodatek była to stara wersja 0.9.9-RC1.

Klasy, które nie były wykrywane korzystały z funkcjonalności wprowadzonych w java 8 (lambda). Biblioteka refleksji potrafiła odczytać dane tylko z klas zawierających bytecode java 7.

Do tego rozwiązania doszedłem dopiero, gdy zmieniłem poziom logowania na DEBUG. Komunikat jasno wskazywał, że nie potrafi odczytać wspomnianych wyżej klas:

123:18:48.305 [main] DEBUG org.reflections.Reflections - could not scan file pl/speedlog/example/reflection/domain1/UsecaseB.class in url file:/home/speedlog/repos/blog/reflection-cant-find-class/target/classes/ with scanner TypeAnnotationsScanner
223:18:48.306 [main] DEBUG org.reflections.Reflections - could not scan file pl/speedlog/example/reflection/domain1/UsecaseB.class in url file:/home/speedlog/repos/blog/reflection-cant-find-class/target/classes/ with scanner SubTypesScanner
323:18:48.307 [main] DEBUG org.reflections.Reflections - could not scan file pl/speedlog/example/reflection/domain2/UsecaseD.class in url file:/home/speedlog/repos/blog/reflection-cant-find-class/target/classes/ with scanner TypeAnnotationsScanner
423:18:48.308 [main] DEBUG org.reflections.Reflections - could not scan file pl/speedlog/example/reflection/domain2/UsecaseD.class in url file:/home/speedlog/repos/blog/reflection-cant-find-class/target/classes/ with scanner SubTypesScanner

Podsumowanie

  1. Zawsze gdy masz problem, którego rozwiązanie nie jest widoczne od razu zmieniaj poziom logowania na DEBUG lub TRACE.
  2. Sprawdzaj wersje bibliotek i staraj się używać aktualnych wersji.

Tłumaczenia: