Rozumiem różnicę między środowiskiem uruchomieniowym i czasem kompilacji oraz jak je rozróżnić, ale po prostu nie widzę potrzeby rozróżniania między zależnościami czasu kompilacji i czasu wykonania .
Dławię się tym: w jaki sposób program może nie zależeć w czasie wykonywania od czegoś, od czego zależał podczas kompilacji? Jeśli moja aplikacja Java używa log4j, to potrzebuje pliku log4j.jar w celu kompilacji (mój kod integruje się z metodami składowymi i wywołuje je z wnętrza log4j), a także środowiska uruchomieniowego (mój kod nie ma absolutnie żadnej kontroli nad tym, co się stanie, gdy kod wewnątrz log4j .jar jest uruchomiony).
Czytam o narzędziach do rozwiązywania zależności, takich jak Ivy i Maven, i te narzędzia wyraźnie rozróżniają te dwa typy zależności. Po prostu nie rozumiem takiej potrzeby.
Czy ktokolwiek może podać proste wyjaśnienie typu „King's English”, najlepiej z rzeczywistym przykładem, który nawet taki biedny głupek jak ja mógłby zrozumieć?
źródło
Odpowiedzi:
Zależność od czasu kompilacji jest zwykle wymagana w czasie wykonywania. W maven
compile
zależność o określonym zakresie zostanie dodana do ścieżki klasy w czasie wykonywania (np. W wojnach zostaną skopiowane do WEB-INF / lib).Nie jest to jednak ściśle wymagane; na przykład możemy kompilować w oparciu o określony interfejs API, czyniąc z niego zależność w czasie kompilacji, ale w czasie wykonywania możemy dołączyć implementację, która również zawiera API.
Mogą wystąpić skrajne przypadki, w których projekt wymaga pewnej zależności do skompilowania, ale wtedy odpowiedni kod nie jest w rzeczywistości potrzebny, ale będą one rzadkie.
Z drugiej strony, dołączanie zależności środowiska uruchomieniowego, które nie są potrzebne w czasie kompilacji, jest bardzo powszechne. Na przykład, pisząc aplikację Java EE 6, kompilujesz w oparciu o API Java EE 6, ale w czasie wykonywania można użyć dowolnego kontenera Java EE; to ten kontener zapewnia implementację.
Zależności w czasie kompilacji można uniknąć, używając odbicia. Na przykład sterownik JDBC można załadować za pomocą,
Class.forName
a rzeczywistą ładowaną klasę można konfigurować za pomocą pliku konfiguracyjnego.źródło
provided
zakres dodaje zależność od czasu kompilacji bez dodawania zależności od środowiska wykonawczego, przy założeniu, że zależność zostanie dostarczona w czasie wykonywania innymi środkami (np. Udostępniona biblioteka w kontenerze).runtime
z drugiej strony dodaje zależność od czasu wykonania, nie czyniąc jej zależnością od czasu kompilacji.Każda zależność Maven ma zakres, który definiuje, w której ścieżce klas jest dostępna ta zależność.
Podczas tworzenia pliku JAR dla projektu zależności nie są pakowane w generowany artefakt; są używane tylko do kompilacji. (Jednak nadal możesz zmusić mavena do uwzględnienia zależności w zbudowanym pliku jar , zobacz: Dołączanie zależności w pliku jar za pomocą Mavena )
W przypadku używania Mavena do tworzenia pliku WAR lub EAR można skonfigurować Maven do łączenia zależności z wygenerowanym artefaktem, a także można go skonfigurować tak, aby wykluczył pewne zależności z pliku WAR przy użyciu podanego zakresu.
Najczęstszy zakres - Zakres kompilacji - wskazuje, że zależność jest dostępna dla projektu w ścieżce klas kompilacji, ścieżkach klas kompilacji i wykonywania testów jednostkowych oraz ostatecznej ścieżce klas środowiska uruchomieniowego podczas wykonywania aplikacji. W aplikacji internetowej Java EE oznacza to, że zależność jest kopiowana do wdrożonej aplikacji. Jednak w pliku .jar zależności nie zostaną uwzględnione w zakresie kompilacji.
Zakres czasu wykonywania wskazuje, że zależność jest dostępna dla projektu od wykonywania testów jednostkowych i ścieżek klas wykonywania w czasie wykonywania, ale w przeciwieństwie do zakresu kompilacji nie jest dostępna podczas kompilowania aplikacji lub jej testów jednostkowych. Zależność wykonawcza jest kopiowana do wdrożonej aplikacji, ale nie jest dostępna podczas kompilacji! Jest to dobre, aby upewnić się, że nie polegasz omyłkowo na konkretnej bibliotece.
Na koniec podany zakres wskazuje, że kontener, w którym jest wykonywana aplikacja, zapewnia zależność w Twoim imieniu. W przypadku aplikacji Java EE oznacza to, że zależność jest już zależna od ścieżki klas kontenera serwletu lub serwera aplikacji i nie jest kopiowana do wdrożonej aplikacji. Oznacza to również, że potrzebujesz tej zależności do kompilowania projektu.
źródło
Potrzebujesz zależności w czasie kompilacji, które mogą być potrzebne w czasie wykonywania. Jednak wiele bibliotek działa bez wszystkich możliwych zależności. tj. biblioteki, które mogą korzystać z czterech różnych bibliotek XML, ale do działania potrzebuje tylko jednej.
Wiele bibliotek potrzebuje po kolei innych bibliotek. Te biblioteki nie są potrzebne w czasie kompilacji, ale są potrzebne w czasie wykonywania. tj. kiedy kod jest faktycznie wykonywany.
źródło
Ogólnie masz rację i prawdopodobnie jest to idealna sytuacja, jeśli zależności czasu wykonywania i czasu kompilacji są identyczne.
Podam 2 przykłady, kiedy ta zasada jest niepoprawna.
Jeśli klasa A zależy od klasy B, która zależy od klasy C, która zależy od klasy D, gdzie A jest twoją klasą, a B, C i D są klasami z różnych bibliotek zewnętrznych, potrzebujesz tylko B i C w czasie kompilacji i potrzebujesz również D w runtime. Często programy używają dynamicznego ładowania klas. W tym przypadku nie potrzebujesz klas dynamicznie ładowanych przez bibliotekę, której używasz w czasie kompilacji. Ponadto często biblioteka wybiera, której implementacji użyć w czasie wykonywania. Na przykład SLF4J lub Commons Logging może zmienić implementację dziennika docelowego w czasie wykonywania. W czasie kompilacji potrzebujesz tylko samego SSL4J.
Przeciwny przykład, gdy potrzebujesz więcej zależności w czasie kompilacji niż w czasie wykonywania. Pomyśl, że tworzysz aplikację, która ma działać w różnych środowiskach lub systemach operacyjnych. Potrzebujesz wszystkich bibliotek specyficznych dla platformy w czasie kompilacji i tylko bibliotek potrzebnych do bieżącego środowiska w czasie wykonywania.
Mam nadzieję, że moje wyjaśnienia pomogą.
źródło
Zwykle wykres zależności statycznych jest pod-grafem wykresu dynamicznego, patrz np. Ten wpis na blogu autora NDepend .
To powiedziawszy, jest kilka wyjątków, głównie zależności, które dodają obsługę kompilatora, która staje się niewidoczna w czasie wykonywania. Na przykład do generowania kodu za pośrednictwem Lombok lub dodatkowych sprawdzeń, jak za pośrednictwem (podłączanego typu) Checker Framework .
źródło
Właśnie napotkałem problem, który odpowiada na Twoje pytanie.
servlet-api.jar
jest przejściową zależnością w moim projekcie sieci Web i jest potrzebny zarówno w czasie kompilacji, jak i w czasie wykonywania. Aleservlet-api.jar
jest również zawarte w mojej bibliotece Tomcat.Rozwiązaniem jest tutaj udostępnienie
servlet-api.jar
w mavenie tylko w czasie kompilacji i nie umieszczanie go w moim pliku wojennym, aby nie kolidował zservlet-api.jar
zawartym w mojej bibliotece Tomcat.Mam nadzieję, że to wyjaśnia zależność czasu kompilacji i czasu wykonywania.
źródło
compile
iprovided
zakresami, a nie międzycompile
iruntime
.Compile scope
jest potrzebny w czasie kompilacji i jest spakowany w Twojej aplikacji.Provided scope
jest potrzebny tylko w czasie kompilacji, ale nie jest spakowany w aplikacji, ponieważ jest dostarczany przez inne sposoby, na przykład jest już na serwerze Tomcat.compile
iruntime
maven .provided
Zakres jest droga Maven uchwyty przypadku gdy zależność czasu kompilacji nie powinny być włączone do pakietu uruchomieniowego.Ogólne koncepcje czasu kompilacji i środowiska uruchomieniowego
compile
orazruntime
zależności specyficzne i zakresowe Mavena to dwie bardzo różne rzeczy. Nie można ich bezpośrednio porównywać, ponieważ nie mają one tej samej ramki: ogólne koncepcje kompilacji i środowiska uruchomieniowego są szerokie, podczas gdy koncepcje mavencompile
iruntime
zakresu dotyczą konkretnie dostępności / widoczności zależności w zależności od czasu: kompilacja lub wykonanie.Nie zapominaj, że Maven to przede wszystkim
javac
/java
wrapper, a w Javie masz ścieżkę klasy czasu kompilacji, którą określasz,javac -cp ...
i ścieżkę klas środowiska wykonawczego, którą określaszjava -cp ...
.Nie byłoby błędem traktowanie
compile
zakresu Maven jako sposobu na dodanie zależności zarówno w kompilacji Java, jak iw ścieżce klasy środowiska wykonawczego (javac
andjava
), podczas gdyruntime
zakres Maven może być postrzegany jako sposób na dodanie zależności tylko w środowisku wykonawczym Java classppath (javac
).To, co opisujesz, nie ma żadnego związku
runtime
anicompile
zakresu.Wygląda na to, że bardziej odpowiada
provided
zakresowi, który określisz, aby zależność zależała od tego w czasie kompilacji, ale nie w czasie wykonywania.Używasz go, ponieważ potrzebujesz zależności do kompilacji, ale nie chcesz włączać go do pakietu (JAR, WAR lub innych), ponieważ zależność jest już dostarczona przez środowisko: może być zawarta na serwerze lub w dowolnym ścieżka do ścieżki klasy określonej podczas uruchamiania aplikacji Java.
W tym przypadku tak. Ale przypuśćmy, że musisz napisać przenośny kod, który opiera się na slf4j jako fasadzie przed log4j, aby móc później przełączyć się na inną implementację rejestrowania (log4J 2, logback lub dowolną inną).
W tym przypadku w twoim pomie musisz określić slf4j jako
compile
zależność (jest to ustawienie domyślne), ale określisz zależność log4j jakoruntime
zależność:W ten sposób nie można było odwoływać się do klas log4j w skompilowanym kodzie, ale nadal będzie można odwoływać się do klas slf4j.
Jeśli określisz te dwie zależności z
compile
czasem, nic nie powstrzyma cię przed odwoływaniem się do klas log4j w skompilowanym kodzie i możesz stworzyć niepożądane sprzężenie z implementacją logowania:Typowym zastosowaniem
runtime
zakresu jest deklaracja zależności JDBC. Aby napisać kod przenośny, nie chcesz, aby kod klienta mógł odwoływać się do klas określonej zależności DBMS (na przykład: zależność PostgreSQL JDBC), ale chcesz, aby to samo zawierało go w aplikacji, ponieważ w czasie wykonywania klasy są potrzebne do API JDBC współpracuje z tym systemem DBMS.źródło
W czasie kompilacji włączasz kontrakty / API, których oczekujesz od swoich zależności. (np .: tutaj po prostu podpisujesz umowę z dostawcą szerokopasmowego Internetu) W rzeczywistości używasz zależności. (np .: faktycznie korzystasz z szerokopasmowego internetu)
źródło
Aby odpowiedzieć na pytanie „w jaki sposób program może nie zależeć w czasie wykonywania od czegoś, od czego zależał podczas kompilacji?”, Spójrzmy na przykład procesora adnotacji.
Załóżmy, że napisałeś własny procesor adnotacji i przypuśćmy, że ma on zależność od czasu kompilacji,
com.google.auto.service:auto-service
dzięki czemu może używać@AutoService
. Ta zależność jest wymagana tylko do kompilowania procesora adnotacji, ale nie jest wymagana w czasie wykonywania: wszystkie inne projekty zależne od procesora adnotacji do przetwarzania adnotacji nie wymagają zależności odcom.google.auto.service:auto-service
w czasie wykonywania (ani w czasie kompilacji ani w żadnym innym czasie) .Nie jest to zbyt częste, ale zdarza się.
źródło
Plik
runtime
Zakres jest tam, aby zapobiec programistów dodawanie bezpośrednie zależności do biblioteki implementacji w kodzie zamiast korzystania abstrakcje lub elewacjach.Innymi słowy, wymusza korzystanie z interfejsów.
Konkretne przykłady:
1) Twój zespół używa SLF4J zamiast Log4j. Chcesz, aby twoi programiści używali SLF4J API, a nie Log4j. Log4j może być używany tylko przez SLF4J wewnętrznie. Rozwiązanie:
2) Twoja aplikacja uzyskuje dostęp do MySQL za pomocą JDBC. Chcesz, aby programiści tworzyli kod w oparciu o standardową abstrakcję JDBC, a nie bezpośrednio przeciwko implementacji sterownika MySQL.
mysql-connector-java
(sterownik MySQL JDBC) jako zależność środowiska wykonawczego.Zależności środowiska wykonawczego są ukryte podczas kompilacji (generują błędy w czasie kompilacji, jeśli kod ma od nich „bezpośrednią” zależność), ale są uwzględniane w czasie wykonywania i podczas tworzenia artefaktów możliwych do wdrożenia (pliki WAR, pliki JAR SHADED itp.).
źródło