W dzisiejszym wpisie wykorzystamy funkcjonalność, która pojawiła się wraz z wydaniem Javy 9. Jest to funkcjonalność o nazwie Multi-Release Jar. Pozwala ona na dostarczenie jednego artefaktu z plikami w różnych wersjach. Zapraszam do wpisu po więcej szczegółów.
Problem
Bardzo często migracja do nowej wersji Javy wiąże się z utrzymywaniem starej wersji dla klientów, którzy się jeszcze nie zmigrowali. Jest to związane także z wydawaniem różnych wersji co komplikuje proces CI i CD. Java w teorii zapewnia kompatybilność wsteczną, ale nie zawsze działa to tak jakbyśmy zakładali. Wydawanie aplikacji w kilku wersjach prowadzi również do problemów na środowiskach developerski oraz spowalnia proces wytwarzania oprogramowania. Przykładowo mamy dwójkę klientów, jeden z nich korzysta z Javy 8, a drugi z Javy 9. W Javie 9 pojawił się feature X, który może bardzo przyśpieszyć działanie kodu. Niestety poprzez jedną bazę kodu, nie dostosowujemy aplikacji, aby korzystała z nowego feature, ponieważ prowadziłoby to do wydania ekstra wersji dla klienta z Java 8. Nasz development jest ograniczony.
Rozwiązanie – Multi-Release Jar
Rozwiązaniem powyższego problemu jest wykorzystanie funkcjonalności Multi-Release Jar. Pozwala ona dostarczyć jeden artefakt, który posiada przygotowane pliki dla różnych wersji Javy.
Dwa pliki
W naszym przykładzie przygotujemy dwa pliki. Każdy z nich będzie wypisywał tekst z przekazanej listy jednoelementowej. Listy te będą tworzone na dwa sposoby:
- z wykorzystaniem “starego API” czyli
Arrays.asList
- z wykorzystaniem API dostępnego od Javy 9 czyli
List.of
public class Runner { public static void main(String[] args) { System.out.println(Arrays.asList("Java 8")); } } public class Runner { public static void main(String[] args) { System.out.println(List.of("Java 9")); } }
Tutaj należy zwrócić uwagę jakich zmian dokonaliśmy. Zgodnie z dokumentacją możemy zmieniać tylko logikę wewnątrz metody. Wszystkie nagłówki i wygląd metod musi być taki sam. Nie możemy dokładać kolejnych publicznych metod.
Struktura folderów
Następnie musimy przechowywać te pliki w uporządkowanej strukturze. Najczęściej przygotowuje się nowy folder z numerem wersji w nazwie:
src/ main/ java/ pl/ codecouple/ Runner.java java9/ pl/ codecouple/ Runner.java
Kompilacja
Tak jak pisałem we wstępie funkcjonalność ta pojawiła się wraz z Javą 9. Po instalacji Javy i ustawieniu folderu /bin
w zmiennych środowiskowych powinniśmy mieć dostęp do kompilatora Javy. Dostępny jest on pod poleceniem javac
. Od wersji Javy 9 pojawiło się kilka nowych przełączników w tym narzędziu. Dla nas najistotniejszy jest przełącznik --release
:
javac --release <release> Compile for a specific VM version. Supported targets: 6, 7, 8, 9
Skompilujmy więc nasze źródła dla dwóch wersji korzystając z tego przełącznika (przełącznik -d
służy do wskazania folderu, gdzie mają znaleźć się skompilowane źródła. Można do tego wykorzystać też Maven’a):
javac --release 8 -d classes src\main\java\pl\codecouple\Runner.java
Oraz źródła dla Javy 9 z użyciem List.of
:
javac --release 9 -d classes-9 src\main\java9\pl\codecouple\Runner.java
Budujemy multi-release jar
Udało nam się skompilować kod źródłowy w dwóch wersjach, teraz pora na przygotowanie paczki zawierającej obie wersje. Zrobimy to wykorzystując polecenie jar
, które podobnie jak javac
pochodzi z folderu /bin
. Użyjemy także dodatkowego przełącznika --release
, w którym wskazujemy folder ze skompilowanymi plikami w odpowiedniej wersji:
jar --release VERSION Places all following files in a versioned directory of the jar (i.e. META-INF/versions/VERSION/)
Wszystkie te pliki zostaną umieszczone w folderze META-INF/version/WERSJA/
:
jar --create --file multi-release.jar --main-class pl.codecouple.Runner -C classes . --release 9 -C classes-9 .
Zawartość
Jak pisałem powyżej po wydaniu polecenia powinien pojawić się nowy plik multi-release.jar
. Zawartość tego pliku prezentuje się następująco:
pl/ codecouple/ Runner.class META-INF/ versions/ 9/ pl/ codecouple/ Runner.class MANIFEST.MF
Wewnątrz folderu versions/9
znalazł się skompilowany przez nas plik w wersji Javy 9. Ponadto w pliku MANIFEST.MF
, który jest plikiem zawierającym dodatkowe informacje o archiwum pojawił się wpis:
Multi-Release: true
Jest to wpis, który świadczy o tym, iż to archiwum występuje w wersji Multi-Release. Jeśli chcielibyśmy przetestować nasz kod w działaniu wystarczy przygotować środowisko z dwiema wersjami Javy (polecam SDKMAN):
java -version java version "9" Java(TM) SE Runtime Environment (build 9+178) Java HotSpot(TM) 64-Bit Server VM (build 9+178, mixed mode) java -jar multi-release.jar [Java 9] java -version java version "1.8.0_144" Java(TM) SE Runtime Environment (build 1.8.0_144-b01) Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode) java -jar multi-release.jar [Java 8]
Jak widzicie powyżej, funkcjonalność Multi-Release Jar pozwoliła nam stworzyć jeden artefakt działający na obu wersjach Javy.