CodeGym /Kursy /Spring Boot /Czytamy auto-config i pliki imports

Czytamy auto-config i pliki imports

Spring Boot
Poziom 9, Lekcja 3
Dostępny

1. Sens czytania auto-config

Gdy Spring Boot działa „jak trzeba", wygląda magicznie. Gdy zachowanie odbiega od oczekiwań, chce się szybko zrozumieć trzy rzeczy: kto stworzył ten bean, po jakim warunku włączył się ten blok i jaka dźwignia jest tu w ogóle umiarkowana – property, własny bean czy exclusion. Dobra wiadomość: auto-configuration – to nie szamanizm, lecz zwykłe klasy Java z warunkami.

Do źródeł auto-config warto zaglądać nie dla heroicznego source-divingu, lecz dla bardzo praktycznego zadania: wybrać minimalną interwencję. Czasem wystarczy zmienić property. Czasem potrzebny jest własny bean, żeby Boot ustąpił. Czasem okazuje się, że problem naprawdę leży w całym bloku auto-config.

Potrzebny jest spokojny i powtarzalny algorytm: od „dziwnego zachowania" dojść do konkretnej klasy auto-config, zrozumieć, jakie warunki ją włączyły i wybrać najmniejszą interwencję: property, własny bean lub (w rzadkich przypadkach) exclusion. I można czytać źródła bez bólu, jeśli wiedzieć, gdzie patrzeć i gdzie się zatrzymać, żeby nie wpaść w niekończący się source-diving.

Component scanning i auto-configuration imports

Bardzo częsty błąd nowicjusza – myślenie, że Spring „wszystko znajdzie przez skanowanie pakietów". Tak, Twoje @Component, @Service, @Controller (gdy się pojawią) naprawdę żyją w świecie component scanning. Ale auto-configuration żyje w innym świecie: Spring Boot importuje specjalne klasy konfiguracyjne z zależności na podstawie wcześniej przygotowanej listy. Dlatego szukanie auto-config przez „a w jakim pakiecie to zostało zeskanowane?" – to jak szukanie kota w lodówce: możliwe, że kiedyś go tam zamknąłeś, ale koty zazwyczaj nie mieszkają w tym miejscu.

Porównajmy to w jednej tabeli – naprawdę pomaga utrzymać obraz w głowie, gdy tydzień później otwierasz projekt i próbujesz sobie przypomnieć, „kto tu jest główny".

Mechanizm Co rejestruje Gdzie leży Jak trafia do kontekstu Typowe pytanie
Component scanning Twoje komponenty aplikacji Twój kod (src/main/java) Spring skanuje pakiety od @SpringBootApplication „Dlaczego mój serwis nie został stworzony?"
Auto-configuration Domyślne infrastrukturalne beany Zależności (.jar) Spring Boot czyta plik imports i importuje klasy auto-config „Kto stworzył ten bean i dlaczego?"
@Configuration + @Bean Jawne beany Twój kod (pakiet config) Sam rejestrujesz „Jak aknuratnie zastąpić default?"

Jeśli to utrzymać w głowie, przestajesz „zgadywać" i zaczynasz diagnozować. A zgadywanie w Spring Boot – to zajęcie wdzięczne, bo Boot ma lepszą pamięć niż my: pamięta wszystkie zależności na classpath.

2. Plik imports AutoConfiguration.imports

Gdy mówi się „Boot podłącza auto-config", przydatny jest konkretny obraz. To nie jest mistyczna teleportacja, lecz czytanie zwykłego pliku tekstowego wewnątrz jara. W Spring Boot 4 (jak i w nowoczesnych wersjach wcześniej) główna lista kandydatów do auto-configuration przechowywana jest w zasobie: META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.

Format prosty: po jednej linii – pełna nazwa klasy. Boot czyta te linie, dostaje listę kandydatów i dalej stosuje warunki (@Conditional...), żeby zrozumieć, co naprawdę włączyć. To znaczy, że plik imports odpowiada na pytanie: „jakie klasy auto-config w ogóle są brane pod uwagę?" – zanim zacznie się magia warunków.

Weźmy ten sam kawałek aplikacji startup-support: StartupMessageFormatter, StartupSummaryRunner i auto-config wokół nich. Mały przykład pliku imports (mocno skrócony, żeby mózg nie uciekł):

com.example.catalogservice.support.startup.StartupSupportAutoConfiguration
com.example.catalogservice.support.startup.StartupReportAutoConfiguration

Ważne, że to nie jest component scanning. Klasy te nie muszą leżeć w Twoich pakietach. Mogą przyjść z dowolnej zależności – czy z spring-boot-autoconfigure, czy z zewnętrznego startera.

Szukanie pliku imports w IDE i terminalu

Gdy słyszysz „otwórz jar i znajdź wewnątrz META-INF/...", chce się udać, że nagle odcięło Ci internet i idziesz do lasu. W praktyce wszystko jest prostsze: IDE potrafi otwierać zawartość zależności, a terminal potrafi pokazywać listę plików wewnątrz jara.

Jeśli jesteś w IntelliJ IDEA, zazwyczaj wystarczy otworzyć External Libraries, znaleźć spring-boot-autoconfigure-..., rozwinąć go jak folder i przejść po ścieżce META-INF/spring/.... Wygląda prawie jak zwykłe drzewo plików. Nic strasznego – jar to w zasadzie zip z klasami i zasobami.

Jeśli bardziej lubisz terminal, minimalne polecenie wygląda tak (przykład dla Linux/macOS, koncepcyjnie):

# Pokazujemy listę plików wewnątrz jara (bez rozpakowywania)
jar tf ~/.gradle/caches/.../spring-boot-autoconfigure-4.0.3.jar   | grep "AutoConfiguration.imports"

Żeby obejrzeć zawartość konkretnego pliku, zazwyczaj robi się jar xf (wyodrębnij) lub używa się unzip -p. Tu nie będę zasypywać Cię „magią poleceń" – myśl jest ważniejsza niż komenda: plik imports to zwykły zasób wewnątrz jara. Można go otworzyć i przeczytać jako tekst.

3. Algorytm czytania auto-config

Podchodząc do auto-config jak do powieści na 900 stron – przegrasz. Podchodząc jak inżynier z pytaniem i krótką listą kontrolną – wygrasz. Poniżej – roboczy algorytm, który dobrze pasuje do rzeczywistości Junior-developera i nie wymaga „rozumienia całego Spring".

Krok Co robisz Co chcesz wiedzieć
1 Formułujesz zachowanie słowami „Co dokładnie mnie dziwi?"
2 Określasz, który bean/class za to odpowiada „To jaki typ/interfejs?"
3 Znajdujesz klasę auto-config, która tworzy ten bean „Kto w ogóle tworzy default?"
4 Sprawdzasz, dlaczego auto-config się włączyła „Jakie @Conditional... zadziałały?"
5 Czytasz metodę @Bean i jej warunki „Kiedy tworzony jest konkretny bean?"
6 Wybierasz minimalną interwencję „Property? Własny bean? Exclude?"
7 Po zmianie potrafisz wyjaśnić wynik „Co jest teraz default, a co – moje?"

Jeśli chcesz zapamiętać bardzo krótko, to tak: kto → dlaczego → gdzie ustępuje → czym jest zarządzany.

4. Czytanie auto-config od góry do dołu

Główny lifehack czytania auto-config: nie zacznij od środka. Zacznij od adnotacji klasy i pierwszych linii – tam zazwyczaj leży „reguła włączenia". Dopiero potem schodź do metod @Bean. To jak czytanie umowy: najpierw dowiadujemy się, co w ogóle podpisałeś, a potem studiujemy punkt 7.4.12 o karze za spóźnioną dostawę.

Oto minimalny dydaktyczny auto-config, który wygodnie czytać (podobny do prawdziwych, ale nieprzeciążony detalami):

package com.example.catalogservice.support.startup;

import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;

// Ta klasa uczestniczy w mechanizmie Spring Boot auto-configuration
@AutoConfiguration
// Włączamy konfigurację tylko jeśli potrzebna klasa naprawdę jest na classpath
@ConditionalOnClass(StartupMessageFormatter.class)
public class StartupSupportAutoConfiguration {

    @Bean
    // Boot "ustępuje", jeśli zadeklarowałeś własny bean tego typu
    @ConditionalOnMissingBean
    StartupMessageFormatter startupMessageFormatter() {
        // Domyślna implementacja: prosty format komunikatu
        return appName -> "Started: " + appName;
    }
}

Tu „od góry do dołu" czyta się dosłownie jak ludzki tekst. Najpierw widzimy, że to @AutoConfiguration, czyli klasa uczestniczy w mechanizmie auto-configuration. Potem widzimy @ConditionalOnClass(StartupMessageFormatter.class) – czyli „ten blok ma sens tylko jeśli taka klasa w ogóle jest na classpath". I dopiero potem dochodzimy do @Bean, który tworzony jest tylko jeśli w aplikacji nie ma innego StartupMessageFormatter (@ConditionalOnMissingBean).

Najbardziej przydatna rzecz, którą możesz wyciągnąć z czytania takich klas: Boot prawie zawsze wprost mówi, gdzie ustąpi. Jeśli widzisz @ConditionalOnMissingBean, to często oznacza: „chcesz po swojemu – zadeklaruj własny bean".

5. Mini-case: formatowanie komunikatu startowego

Weźmy ten sam kawałek aplikacji startup-support. Kontrakt jest ten sam: StartupMessageFormatter – interfejs z metodą format(String appName). A kod aplikacyjny zależy od niego przez StartupSummaryRunner.

package com.example.catalogservice.catalog.bootstrap;

import com.example.catalogservice.support.startup.StartupMessageFormatter;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class StartupSummaryRunner implements ApplicationRunner {

    // Zależność od abstrakcji: implementacja może przyjść z auto-config
    private final StartupMessageFormatter formatter;

    public StartupSummaryRunner(StartupMessageFormatter formatter) {
        // Wstrzykujemy bean przez konstruktor (łatwiej testować i czytać)
        this.formatter = formatter;
    }

    @Override
    public void run(ApplicationArguments args) {
        // W prawdziwym projekcie tu częściej będzie logger, ale dla przykładu wystarczy println
        System.out.println(formatter.format("catalog-service"));
        // Oczekiwane wyjście (jeśli włączyła się domyślna implementacja):
        // Started: catalog-service
    }
}

Jeśli nie tworzyłeś StartupMessageFormatter jawnie, logiczne pytanie: „kto go stworzył?". I tu przestajemy zgadywać i przechodzimy do algorytmu.

Patrzymy na typ: StartupMessageFormatter. Dalej mamy dwa realistyczne scenariusze. Albo to nasz kod (i wtedy łatwo znajdziemy, gdzie jest tworzony), albo przyszedł z auto-config (np. podłączyłeś bibliotekę/starter albo masz wewnętrzny moduł). Wtedy szukamy klasy auto-config tworzącej bean tego typu. W przykładzie dydaktycznym to StartupSupportAutoConfiguration.

Kolejne pytanie: „jak ten auto-config w ogóle się podłączył?". Odpowiedź: przez plik imports wewnątrz jar, gdzie wymieniony jest StartupSupportAutoConfiguration. To znaczy, że nie czytamy „magicznej rzeczywistości", lecz konkretny łańcuch: imports → klasa auto-config → @Conditional... → @Bean.

6. Czytanie auto-config jako droga do rozwiązania

Bardzo typowa pułapka nowicjusza – otworzyć klasę auto-config, zobaczyć wewnątrz dwadzieścia linii i zacząć czytać każdą jak święte pismo. W efekcie mózg się przegrzewa, a rozwiązania nadal nie ma. Żeby tego uniknąć, przydatne jest trzymanie w głowie „pytań-filtrów", które zadajesz kodowi.

Wygodnie trzymać je wprost w formie krótkiego komentarza (to nie lista w sensie „wypiszmy 40 punktów", lecz raczej ściągawka do powtarzania jak mantrę):

// Szybkie czytanie auto-config:
// 1) Co to za blok i jak się nazywa?
// 2) Po jakich warunkach w ogóle się włącza?
// 3) Jaki bean tworzy (typ ważniejszy niż nazwa)?
// 4) Gdzie ustępuje (OnMissingBean / OnProperty)?
// 5) Co jest mniejsze skalą: property, własny bean czy exclude?

Jeśli odpowiadasz na te 5 pytań, prawie zawsze znajdujesz normalną drogę kastomizacji bez „framework takeover". I właśnie to jest dorosły cel: nie stać się osobą, która w każdej niejasnej sytuacji pisze exclude = Everything.class.

7. @ConditionalOnProperty jako przełącznik

Druga złota adnotacja do czytania – @ConditionalOnProperty. Zazwyczaj oznacza: „to zachowanie można włączać/wyłączać ustawieniem". To znaczy, że zamiast pisać własny bean lub wykluczać auto-config, możesz po prostu ustawić property. To prawie zawsze najtańszy sposób.

W tym samym startup-support layer można spotkać inną dźwignię: bean nie jest zastępowany własnym, lecz włączany i wyłączany przez property. Przykład dydaktyczny:

package com.example.catalogservice.support.startup;

import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;

@AutoConfiguration
public class StartupReportAutoConfiguration {

    @Bean
    @ConditionalOnProperty(
            // Nazwa właściwości zarządzającej włączeniem beana
            name = "app.catalog.startup-report-enabled",
            // Włączamy bean tylko przy jawnej wartości "true"
            havingValue = "true",
            // Jeśli właściwości nie ma, uważamy że jest true (zachowanie "włączone domyślnie")
            matchIfMissing = true
    )
    StartupReportSink startupReportSink() {
        // Domyślny sink: po prostu drukujemy komunikat (w prawdziwym świecie będzie logger/metryki)
        return message -> System.out.println(message); // audit
    }
}

Czytamy to jako zdanie: „stwórz StartupReportSink, jeśli właściwość app.catalog.startup-report-enabled jest równa true; jeśli właściwości nie ma – uważaj, że jest true". Ważne, że tu już widać rozwiązanie: jeśli chcesz wyłączyć raport – nie musisz wykluczać auto-config. Wystarczy ustawić app.catalog.startup-report-enabled=false w application.yaml.

Tak, nie jesteśmy jeszcze w module konfiguracji zewnętrznej i nie będziemy wchodzić w pierwszeństwo i profile. Ale przynajmniej już widzisz: źródła auto-config wprost pokazują Ci „gałkę sterowania". I to jest bardzo Boot-centryczny sposób myślenia: najpierw szukamy gotowego przełącznika, a potem dopiero wchodzimy w wiring.

8. Auto-config z obcych zależności

Czasem otwierasz klasę w IDE, a tam „decompiled code" i wszystko wygląda jakby pisał to robot nienawidzący ludzi. Normalna sytuacja: źródła zależności mogą nie pobrać się automatycznie. W większości IDE jest akcja w stylu „Download Sources" dla zależności Gradle. Po niej klasy auto-config stają się czytelne i życie się poprawia.

Jeśli źródeł nadal nie ma, masz dwie bezpieczne drogi. Pierwsza – patrzeć na adnotacje i sygnatury metod nawet w zdekompilowanej postaci: @ConditionalOnMissingBean i typ wartości zwracanej są widoczne prawie zawsze. Druga – w ostateczności, nie czytać całej klasy, lecz znaleźć obok klasę *Properties (lub przynajmniej zrozumieć nazwę property z @ConditionalOnProperty) i użyć tego jako „gałki" zachowania.

Sens w tym, że nawet bez idealnych źródeł wystarczy Ci zrozumieć dwie rzeczy: jaki typ beana jest tworzony i przy jakich warunkach. To wystarczy, żeby podjąć decyzję „własny bean vs property vs exclude". A wewnętrzna konfiguracja konkretnej biblioteki (z jej 300 opcjami) prawie zawsze wykracza poza ramy zadania dnia.

9. Typowe błędy przy czytaniu auto-config

Błąd nr 1: mylenie pliku imports z component scanning.
Nowicjusz widzi, że klasa auto-config leży w jakimś pakiecie i zaczyna szukać, dlaczego ten pakiet „jest skanowany". W efekcie zmienia @ComponentScan, przesuwa klasę main i projekt staje się jeszcze dziwniejszy. Prawidłowy model jest inny: auto-config brana jest z pliku imports, a nie ze skanowania.

Błąd nr 2: czytanie auto-config „od środka", pomijając warunki klasy.
Bardzo łatwo otworzyć klasę, przewinąć do @Bean i zacząć rozeznawać, co tam jest tworzone. Ale warunki na poziomie klasy mogą w pełni wyjaśnić, dlaczego ten blok w ogóle się włączył (np. z powodu obecności określonej klasy na classpath). Jeśli pominiesz górę, ryzykujesz rozwiązywanie nie tego problemu.

Błąd nr 3: „tonięcie w szczegółach" wewnątrz metody @Bean.
W prawdziwym kodzie Boot metody @Bean bywają rozbudowane: tam tworzą się buildery, wmieszają się properties, są optional-gałęzie. Nowicjusz próbuje zrozumieć wszystko, włącznie z każdym if. Na poziomie kursu zazwyczaj wystarczy zrozumieć typ tworzonego beana, kluczowe warunki (OnMissingBean, OnProperty) i miejsce, gdzie Boot daje punkt podmiany.

Błąd nr 4: zobaczenie @ConditionalOnProperty i mimo to zrobienie exclusion.
To klasyka: masz gotowy wyłącznik zachowania, ale wyłączasz cały blok przez exclude, a potem dziwisz się, że razem z nim zniknęło coś przydatnego. Jeśli jest property – prawie zawsze zacznij od niego, bo to minimalna interwencja.

Błąd nr 5: wyciągnięcie wniosku „Boot to magia", zamiast „Boot ma reguły".
Najbardziej szkodliwa myśl po czytaniu źródeł – „tam wszystko zbyt skomplikowane, niczego nie rozumiem". Bardziej przydatna myśl – „widzę reguły włączenia i widzę punkty ustąpienia". Nie musisz rozumieć całego frameworka, żeby zrozumieć, dlaczego pojawił się konkretny bean i jak go zastąpić. To jak z samochodem: żeby zmienić koło, nie trzeba znać termodynamiki silnika.

1
Zadanie
Spring Boot, poziom 9, lekcja 3
Niedostępne
Eksport `AutoConfiguration.imports` z cache Gradle
Eksport `AutoConfiguration.imports` z cache Gradle
1
Zadanie
Spring Boot, poziom 9, lekcja 3
Niedostępne
Skrypt odczytu lokalnej klasy auto-config
Skrypt odczytu lokalnej klasy auto-config
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION