CodeGym /Kursy /Spring Boot /Starter jako zestaw zależności

Starter jako zestaw zależności

Spring Boot
Poziom 4, Lekcja 1
Dostępny

1. Ręczne składanie stosu – zły start

Jeśli wcześniej pisałeś głównie aplikacje konsolowe Java, zależność wyglądała prosto: „potrzebuję biblioteki – podłączyłem". Z aplikacjami webowymi, nawet małymi, to szybko przestaje działać: dla jednej „ficzery" potrzebujesz dziesiątek jar-ów, przy czym część z nich potrzebna jest nie tobie bezpośrednio, a infrastrukturze. I oto już nie piszesz kodu, a składasz LEGO bez obrazka na pudełku.

Wyobraź sobie, że potrzebujesz „po prostu postawić serwis HTTP i zwracać JSON". Ręcznie oznacza to wybór frameworka webowego, serwera (Tomcat/Jetty/Undertow), biblioteki JSON, logowania, zestawu podstawowych modułów Spring i jeszcze starannego uzgodnienia wersji tak, żeby to wszystko w ogóle ze sobą działało. W tym momencie wielu początkujących zaczyna myśleć, że „Spring jest trudny". Choć komplikuje się tu nie programowanie, a ręczne składanie stosu.

Spring Boot rozwiązuje ten ból nie tym, że „chowa zależności", a tym, że daje wygodne i sensowne punkty wejścia. Pierwszy taki punkt wejścia to starter.

2. Czym jest starter w Spring Boot

Słowo starter w nazwie zależności brzmi tak, jakby dostawano „gotową aplikację w pudełku". W rzeczywistości wszystko jest prostsze i niezawodniejsze: starter to zależność, która ciągnie za sobą zestaw innych zależności, dobranych pod konkretny scenariusz. Zazwyczaj sam starter prawie nie zawiera kodu aplikacyjnego. Jego zadaniem jest powiedzieć budowaniu: „potrzebuję takiego stosu".

W Gradle wygląda to maksymalnie prozaicznie – jak zwykła linia w dependencies:

dependencies {
    // Podłączamy nie "jedną bibliotekę", a cały scenariusz (web MVC na stosie servlet)
    implementation("org.springframework.boot:spring-boot-starter-webmvc")
}

I tu następuje ważny psychologiczny zwrot. Nie „podłączyłeś jedną bibliotekę webmvc". Zadeklarowałeś intencję: „moja aplikacja będzie serwisem webowym MVC opartym na servletach i Spring". W odpowiedzi Gradle pobierze nie jeden artefakt, a cały graf zależności tranzytywnych. Na razie wystarczy po prostu mieć w głowie: za jedną linią kryje się cała gałąź bibliotek.

Łatwo tu pomylić starter i BOM z poprzedniego wykładu. BOM odpowiada na pytanie „jakie wersje są kompatybilne", a starter – na pytanie „jakie biblioteki zazwyczaj są potrzebne dla tego scenariusza". To różne role, i razem dają bardzo silny efekt: jedną linią wybierasz scenariusz i dostajesz uzgodniony zestaw bibliotek bez ręcznego dobierania wersji.

3. Curated dependency set i „komplet elementów"

Słowo curated w IT zazwyczaj oznacza „dobrane i sprawdzone". W naszym kontekście curated dependency set to zestaw zależności, który już ktoś zebrał za ciebie tak, żeby był logiczny i w większości projektów działał „domyślnie". To przypomina komplet do składania stołu: nie musisz osobno kupować 32 śrub, 4 nóg i klucza sześciokątnego – bierzesz pudełko, gdzie to wszystko już jest i pasuje do siebie.

Jeśli przełożyć to na język projektów Spring Boot, starter to „pudełko pod scenariusz". Kilka przykładów scenariuszy, które napotkamy w catalog-service w miarę wzrostu projektu, wygląda tak:

Scenariusz (po ludzku) Jak wyrażane w Gradle O co tak naprawdę prosisz platformę
„Potrzebuję serwisu web na MVC" spring-boot-starter-webmvc MVC, konwersja JSON, wbudowany serwer i podstawowa infrastruktura web
„Potrzebuję diagnostyki w runtime" spring-boot-starter-actuator Endpointy Actuator, integracje dla health/info i diagnostyczny szkielet
„Potrzebuję normalnego stosu testowego" spring-boot-starter-test JUnit 6 + Spring Test + wygodne narzędzia testowe w uzgodnionych wersjach

Zauważ: w tych liniach nie ma wymieniania niskopoziomowych modułów. To normalne. Dobry build.gradle.kts to nie lista wszystkich śrub, a lista tego, jakie możliwości są potrzebne projektowi.

I jeszcze jedno: starter to nie „magia". To po prostu zależność, która rozkłada się w inne zależności. Magia zaczyna się dopiero wtedy, gdy przestajesz patrzeć, co dokładnie podłączyłeś, i czekasz, że „samo wszystko wymyśli". Tak nie robimy: rozumiemy, co się dzieje, tylko na zrozumiałym poziomie.

4. Starter i zachowanie aplikacji

Gdy początkujący słyszy „podłączyłem starter – i wszystko zadziałało", wydaje się, że tu jest jakaś mistyka. W rzeczywistości logika jest prosta: starter przynosi na classpath potrzebny zestaw bibliotek, a Spring Boot już potrafi podnieść sensowną infrastrukturę ponad takim classpath. To znaczy starter nie „włącza ficzery" magicznym przyciskiem, a zmienia dostępny stos aplikacji.

Na przykład spring-boot-starter-webmvc to nie „jeden jar z kodem web", a wybrany scenariusz web na stosie servlet. Dlatego razem z nim do projektu przyjeżdżają moduły Spring MVC, obsługa JSON, wbudowany serwer i inne części, bez których taki serwis zazwyczaj nie działa.

Na razie wystarczy tu zatrzymać jedną myśl: starter wpływa na zachowanie przez classpath. A jak ten graf naprawdę czytać, skąd dokładnie biorą się Tomcat, Jackson i inne biblioteki, już wyjaśnia dependency tree.

5. Bezpośrednie zależności w catalog-service

catalog-service w tym kursie to projekt edukacyjny, ale powinien przypominać normalny, żywy serwis. I jeden z dobrych nawyków, które warto wyrobić wcześnie: nie zamieniać build.gradle.kts w składowisko przypadkowych jar-ów. Lista bezpośrednich zależności powinna być krótka, bo każda linia to decyzja, która zmienia classpath i zachowanie aplikacji.

Jeśli na chwilę oderwać się od bieżącego catalog-service i wziąć abstrakcyjny minimalny przykład Boot, zestaw bezpośrednich zależności może wyglądać tak. To właśnie minimalny przykład, a nie bieżący snapshot dnia:

dependencies {
    // Podstawowy szkielet Spring Boot (bez przywiązania do web/DB itd.)
    implementation("org.springframework.boot:spring-boot-starter")

    // Testowy „komplet": JUnit + Spring Test + narzędzia
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

Taki przykład jest użyteczny właśnie jako abstrakcja: widać, że bezpośrednich zależności jest mało, i każda linia wyraża rolę, a nie wnętrze classpath.

Roboczy snapshot catalog-service, od którego teraz wychodzimy, jest konkretniejszy: serwis złożony jest przez scenariusz web, a nie przez bazowy starter. Jego jądro to spring-boot-starter-webmvc plus spring-boot-starter-test. Jeśli potem ponad tym snapshotem będzie potrzebna jeszcze warstwa diagnostyczna, build dorośnie dosłownie o jedną zrozumiałą linię:

dependencies {
    // Web MVC na stosie servlet: kontrolery, JSON, embedded server i podstawowa infrastruktura web
    implementation("org.springframework.boot:spring-boot-starter-webmvc")

    // Diagnostyka: health/info/metrics i inne możliwości actuator
    implementation("org.springframework.boot:spring-boot-starter-actuator")

    // Testowy starter – zazwyczaj zostaje jeden, bez ręcznego składania stosu testowego
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

To rozszerzenie bieżącego baseline'u, a nie jego ukryta zamiana. Nawet z Actuator build nadal czyta się jako lista możliwości: web, diagnostyka, testy. Nie wymieniamy ręcznie Tomcat, Jackson i logback – robi to wybrany zestaw starter'ów.

Porównaj to ze „ręcznym" stylem, który często spotykamy u początkujących po kilku wyszukiwaniach w Google. Wygląda „poważnie", ale w praktyce prawie zawsze jest gorszy:

dependencies {
    // „Ręczny" zestaw modułów pod warstwę web: wygląda kontrolowanie, ale łatwo pomylić się i dostać konflikt wersji
    implementation("org.springframework:spring-webmvc")
    implementation("org.apache.tomcat.embed:tomcat-embed-core")
    implementation("com.fasterxml.jackson.core:jackson-databind")
    implementation("ch.qos.logback:logback-classic")
    implementation("org.slf4j:slf4j-api")
}

Problem tu nie w tym, że te biblioteki są „złe". Problem jest inny: wziąłeś na siebie rolę zespołu Spring Boot. Teraz ty odpowiadasz za kompatybilność, za właściwy zestaw modułów, za wyrównanie wersji i za to, żeby niczego nie zapomnieć. Dla projektu edukacyjnego to gwarantowane przeciążenie poznawcze. Dla produkcji taka ścieżka bywa uzasadniona, ale tylko jeśli już dobrze rozumiesz, co dokładnie robisz i po co.

W ramach kursu chcemy, żeby build.gradle.kts przypominał listę tego, czego projekt potrzebuje – web, actuator, testy – a nie wyliczenie wszystkich wewnętrznych elementów.

6. Starter'y i zwykłe zależności

Łatwo wpaść w skrajność: „skoro w Boot są starter'y, to wszystko podłącza się przez starter'y". Tak nie jest. Starter to dobry format dla scenariuszy. Ale czasem potrzebujesz nie scenariusza, a małego narzędzia. Na przykład procesor adnotacji do generowania metadanych konfiguracji – to nie ficzera runtime, a wsparcie build-time. I tu starter nie jest potrzebny.

W catalog-service będziemy mieć zależność, którą często mylą ze starter'em tylko dlatego, że też jest z Boot. Ale jej rola jest inna: spring-boot-configuration-processor. Pomaga IDE i budowaniu rozumieć twoje własne @ConfigurationProperties i podłącza się jako annotation processor.

W Gradle wygląda to zazwyczaj tak:

dependencies {
    // Potrzebny tylko na etapie kompilacji: generuje metadane dla IDE/podpowiedzi dla @ConfigurationProperties
    annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")

    // Stos testowy – osobno, żeby nie obciążać runtime classpath
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

I tu pojawia się jeszcze jedna ważna myśl dla początkującego: nie każda zależność powinna leżeć w implementation. Czasem jest to testImplementation – potrzebne tylko do testów, czasem annotationProcessor – potrzebne na etapie kompilacji, czasem developmentOnly – potrzebne do lokalnego developmentu. Jeśli wszystko bez rozróżnienia kłaść do implementation, classpath staje się cięższy, głośniejszy i mniej przewidywalny.

Starter'y pomagają utrzymywać główny stos w porządku, ale nie eliminują zdrowego rozsądku. Po prostu teraz bazowe pytanie brzmi inaczej: „potrzebuję scenariusza – starter, czy narzędzia – zwykła biblioteka/procesor?"

7. Typowe błędy przy pracy ze starter'ami

Ze starter'ami najczęściej się myli nie dlatego, że są trudne, a dlatego, że łamią przyzwyczajony model „jedna linia = jedna biblioteka". Na początku jest to jak przesiadka z roweru na elektryczną hulajnogę: niby to samo, ale nagle szybciej, i w ścianę można wjechać prościej. Poniżej kilka błędów, które warto zawczasu rozpoznawać, żeby nie zamieniać dependency management w loterię.

Błąd nr 1: myślenie, że starter to „jeden gruby jar z wszystkim w środku".
Starter to nie „fat jar" i nie jakaś monolityczna biblioteka. Rozkłada się w zestaw zależności, i właśnie ten zestaw formuje classpath. Jeśli trzymać w głowie model „tam w środku magia", nieuchronnie przestaniesz rozumieć, co naprawdę jest podłączone i dlaczego aplikacja zachowuje się właśnie tak.

Błąd nr 2: podłączyć starter i obok ręcznie „dosypywać" niskopoziomowe moduły tego samego scenariusza.
Na przykład dodać spring-boot-starter-webmvc, a obok jeszcze spring-webmvc, tomcat-embed-core i kilka modułów Jackson „dla pewności". Wygląda jak troska, ale w praktyce to duplikacja i potencjalne konflikty. Jeśli masz już starter, najpierw przyjmij, że przywiózł bazowy zestaw, a dopiero potem dodawaj to, czego naprawdę brakuje – i co potrafisz uzasadnić.

Błąd nr 3: wybrać starter po „podobnej nazwie", a nie po zadaniu.
W ekosystemie Boot są zależności, które brzmią niemal tak samo. Początkujący często wybiera według zasady „w nazwie jest web – to to". Prawidłowo jest najpierw sformułować scenariusz słowami: „chcę servlet MVC" lub „chcę diagnostykę" – i dopiero pod to wybierać starter. Nazwa to wskazówka, ale nie gwarancja trafienia w cel.

Błąd nr 4: oczekiwanie, że starter rozwiąże architekturę aplikacji.
Starter rozwiązuje problem warstwy zależności: przynosi uzgodniony zestaw bibliotek. Nie decyduje, jak organizować pakiety, gdzie trzymać logikę biznesową, jak nazywać serwisy i jakie endpointy robić. Jeśli czekasz, że „podłączyłem starter – i wszystko stało się prawidłowe", bardzo szybko zaczniesz budować aplikację według zasady „no Boot jest mądry".

Błąd nr 5: próbować „zoptymalizować" build i usunąć „zbędne" tranzytywne zależności, nie rozumiejąc ich roli.
Gdy po raz pierwszy zobaczysz drzewo zależności, wyda się ogromne. I to normalna reakcja. Ale chęć „posprzątania" grafu bez rozumienia szybko prowadzi do sytuacji, gdy aplikacja przestaje startować, a przyczyną jest jedna przypadkowo wykluczona biblioteka. Starter'y istnieją właśnie dlatego, że w złożonym stosie „po prostu usunąć zbędne" zazwyczaj nie można bez konsekwencji.

1
Zadanie
Spring Boot, poziom 4, lekcja 1
Niedostępne
Jeden starter zamiast ręcznego zestawu webowego
Jeden starter zamiast ręcznego zestawu webowego
1
Zadanie
Spring Boot, poziom 4, lekcja 1
Niedostępne
Krótki build z dwoma scenariuszami wykonawczymi
Krótki build z dwoma scenariuszami wykonawczymi
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION