Modyfikacja artefaktów Java

Autor
Damian
Terlecki
5 minut

Zdarza się, że podczas szukania źródła błędu w systemie znajdujesz je w zewnętrznej bibliotece i po wielu godzinach analizy uświadamiasz sobie, że praktycznie niemożliwe będzie naprawienie nieszczęsnego błędu. Częstym tego powodem jest to, że komponenty są ze sobą ściśle powiązane i nie ma możliwości na ich podmianę, bądź (co gorsze) mamy do czynienia z metodami statycznymi. Java jest jednak dosyć dojrzałym językiem i również w tym zakresie znajdziemy różnego rodzaju rozwiązania pozwalające na obejście tego typu problemów.

Proces kompilacji i uruchamiania

Sytuacja w uproszczeniu wygląda następująco: kod Java (pliki .java) jest kompilowany (javac) do kodu bajtowego (pliki .class), a następnie, wraz z pewnymi meta-danymi, pakowany do archiwum ([J|W|E]AR). Klasy umieszczone w takim artefakcie są później ładowane za pomocą ClassLoadera (do pamięci) w środowisku wykonawczym JVM. Ostatecznie bajtkod tych klas jest weryfikowany i kompilowany (przez kompilator JIT) do kody natywnego. W ramach tego procesu można znaleźć wiele sposobów rozwiązania problemu z problematycznymi klasami. Mamy tu szerokie pole do popisu, przede wszystkim możemy:

  • zmienić kolejność klas w ścieżce classpath, aby w pierwszej kolejności załadować poprawioną klasę (o tej samej nazwie i pakiecie);
  • zaimplementować własnego ClassLoadera, który w miejsce błędnej klasy załaduje poprawioną jej wersję (uwaga na załadowanie przez więcej niż jeden ClassLoader);
  • użyć instrumantacji i zaimplementować własnego agenta Javy, który zajmie się redefinicją / retransformacją klas (z pewnymi ograniczeniami);
  • usunąć klasę z archiwum artefaktu i zaimplementować własną.
Bajtkod

Każdy, kto kiedykolwiek podejrzał, jak wygląda kod bajtowy, prawdopodobnie zgodzi się, że manipulowanie nim może nie należeć do najprostszych zadań w życiu programisty. Na szczęście istnieje wiele bibliotek, które mogą w tym pomóc, takich jak ByteBuddy, Javassist lub ASM. Sam miałem okazję ich użyć do naprawy błędów ukrytych w metodach statycznych bibliotek zewnętrznych. Często jednak manipulacja kodem bajtowym lub dodawanie własnych ClassLoaderów może być niemile widziana, szczególnie gdy możliwość modyfikowania skryptów uruchomieniowych/kolejności ładowania klas jest ograniczona.

Modyfikacja artefaktów Java

W szczególnych przypadkach jedną z możliwych opcji uporania się ze zbugowaną klasą jest jej usunięcie z archiwum i zaimplementowanie własnej. Doskonałym przykładem może być komponent MDB (Message Driven Bean) w Javie biznesowej. Zazwyczaj takie elementy są luźno powiązane, a ich usunięcie, poza zaprzestaniem działania danej funkcjonalności, nie powinno wiązać się z większymi nieprzyjemnościami. Przed wykonaniem tej czynności polecam jednak sprawdzenie wszelkich deskryptorów wdrożeniowych oraz konfiguracji serwerów aplikacyjnych, w celu zachowania ustalonej konfiguracji. To rozwiązanie nie jest zbyt eleganckie, ale pozwala podmienić zepsuty komponent, dopóki biblioteka, z której korzystamy, nie zostanie naprawiona.

Usunięcie klasy jest proste i sprowadza się do rozpakowania archiwum, usunięcia jej i spakowania ponownie. Dzięki Mavenowi możliwe jest zautomatyzowanie tego procesu na etapie przygotowania paczki za pomocą wtyczki, takiej jak np. truezip-maven-plugin:

<plugins>
  <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>truezip-maven-plugin</artifactId>
    <version>1.2</version>
    <executions>
      <execution>
        <id>remove-a-file</id>
        <goals>
          <goal>remove</goal>
        </goals>
        <phase>package</phase>
        <configuration>
          <fileset>
            <directory>${project.build.directory}/com.example.project.ear/lib/com.example.library.jar/</directory>
            <includes>
              <include>com/example/library/Broken.class</include>
            </includes>
          </fileset>
        </configuration>
      </execution>
    </executions>
  </plugin>
</plugins>

W zależności od tego, czy ładować będziesz (serwer) spakowaną paczkę czy też rozpakowane (exploded) archiwum, może zajść potrzeba dodatkowej konfiguracji wtyczki. Ten sposób nie jest zbyt elegancki i nie zawsze można go wykorzystać, warto jednak o nim pamiętać, gdyż w ostateczności może się przydać. Szczególnie gdy hotfix jest potrzebny na wczoraj, a klient z każdą sekundą traci pieniądze.