İçeriğe geç

Auto-configuration — Bu Bean Nereden Geldi?

Orta 8 dk Çok sık karşılaşılır

Önce şunu oku: IoC ve Dependency Injection

30 saniyede özet

Spring Boot sihir yapmaz, bir kontrol listesine bakar: şu kütüphane var mı, şu ayar yazılmış mı, sen zaten kendi nesneni tanımladın mı? Eksik olanı kurar, seninkini gördüğünde geri çekilir.

İyi bir otelde odana girdiğinde ihtiyacın olan her şey hazırdır. Ama kendi ütünü getirdiysen, otel bir ütü daha koymaz.

  1. Bayt: Projeye tek bir bağımlılık ekledim, bir satır ayar bile yazmadım.

  2. Sen: Ve?

  3. Bayt: Bir DataSource, bir EntityManagerFactory ve bir JdbcTemplate ortaya çıktı! Kim yazdı bunları?

  4. Bayt: Sihir yok, bir kontrol listesi var. Resepsiyona bir bakalım.

Projeye tek bir bağımlılık ekledin ve uygulama artık bir veritabanı istiyor. Tek satır ayar yazmadın, ama bir DataSource, bir EntityManagerFactory ve bir JdbcTemplate ortaya çıktı. Nereden?

Starter getirir, auto-configuration kurar

Bir starterBir özellik için gereken bağımlılıkları tek paket hâlinde getiren boş bir modül. Kod içermez; asıl işi classpath'e düşen sınıflara bakan auto-configuration yapar.Sözlükte gör → bir alışveriş listesi gibidir: içinde neredeyse hiç kod yoktur, yalnızca gereken kütüphaneleri projeye getirir.

Asıl işi, @SpringBootApplication’ın içinde saklı @EnableAutoConfiguration yapar. Her kütüphane “ben şunları kurabilirim” diye bir aday listesi getirir; Boot her adayın koşuluna bakıp ya kurar ya geçer.

Bu adaylara auto-configurationSpring Boot'un classpath'e, ayarlara ve mevcut bean'lere bakarak senin yerine bean tanımlaması. Her tanım bir @Conditional koşuluna bağlıdır.Adaylar META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports dosyasında listelenir. Boot 3 ile spring.factories bu iş için artık okunmuyor.Sözlükte gör → denir. Her biri “şu varsa ve şu yoksa, şunu tanımla” diyen sıradan bir @Configuration sınıfıdır.

Kafam karıştı, daha basit anlat

Starter bir alışveriş listesidir, malzemeleri getirir. Auto-configuration ise aşçıdır: mutfakta ne olduğuna bakar ve ona göre yemeği hazırlar.

Hızlı kontrolBaşlangıç

`spring-boot-starter-data-jpa` gibi bir starter'ın içinde ne vardır?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Spring Boot 3 hangi auto-configuration sınıflarının aday olduğunu nereden öğrenir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Her bean bir koşula bağlı

KoşulNeye bakarÖrnek
@ConditionalOnClassClasspathJDBC sınıfları varsa DataSource kur
@ConditionalOnMissingBeanKayıtlı bean’lerKullanıcı DataSource tanımlamadıysa
@ConditionalOnPropertyAyarlarfeature.x.enabled=true ise
@ConditionalOnSingleCandidateKayıtlı bean’lerTek bir DataSource varsa JPA’yı kur
Kendi @Bean DataSource metodunu yazdın. Boot da classpath'te JDBC görüyor. Kaç DataSource oluşur? Cevabı göster

Bir tane — seninki. Boot’un tanımı @ConditionalOnMissingBean(DataSource.class) ile işaretli ve senin bean’ini görünce vazgeçiyor.

Resepsiyon yalnızca eksik olanı tamamlar.
Adım adım oku
  1. Boot her auto-configuration için bir kontrol listesine bakar.
  2. JDBC kütüphanesi classpath'te mi? Bağlantı adresi yazılmış mı? İkisi de evet.
  3. Senin tanımladığın bir DataSource yoksa Boot kendi DataSource'unu kurar.
  4. Sen @Bean ile yazdıysan Boot geri çekilir. İki durumda da uygulamada tek bir DataSource vardır.

Buna back offBir auto-configuration'ın, kullanıcı aynı işi gören bir bean tanımladığında kendi bean'ini oluşturmaktan vazgeçmesi. @ConditionalOnMissingBean ile sağlanır.Sözlükte gör → denir. Varsayılanı değiştirmek için exclude gerekmez; aynı tipte bir bean tanımlaman yeter.

Kafam karıştı, daha basit anlat

Kendi yemeğini getirdiysen aşçı aynısını pişirmez, kenara çekilir. Varsayılanı değiştirmek için kendi bean’ini tanımlaman yeter.

Hızlı kontrolOrta

Uygulamada kendi `@Bean DataSource` metodunu tanımladın. Spring Boot'un DataSourceAutoConfiguration'ı ne yapar?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Her koşul anotasyonu neye bakar?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

Classpath

Bir sınıf yüklenebiliyor mu

    Bean konteyneri

    Hangi bean'ler zaten kayıtlı

      Ayarlar

      application.properties, ortam değişkenleri

        Uygulama türü

        Web mi, hangi tür web

          Kendin gör

          Auto-configuration — bu bean nereden geldi?

          Tohum 152291
          build + properties + Application
          1// build.gradle
          2implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
          3runtimeOnly 'org.postgresql:postgresql'
          4
          5# application.properties
          6spring.datasource.url=jdbc:postgresql://db:5432/shop
          7
          8// Application.java
          9@SpringBootApplication
          10// kendi DataSource bean'i yok
          Java 21UTF-8LF
          1. DataSourceAutoConfiguration
          2. HibernateJpaAutoConfiguration
          3. JdbcTemplateAutoConfiguration
          Hız
          Adım 0

          Şu an ne oldu?

          SpringApplication.run() başladı

          Önce senin @Configuration sınıfların işlenir, sonra auto-configuration adayları sırayla koşullarına göre değerlendirilir.

          Görevler0/3

          • "'url' attribute is not specified" hatasını alaçık

            İpucu

            Boot bir DataSource kurmaya çalışsın ama ne bir adres ne de gömülü bir veritabanı bulsun.

          • Tek satır ayar yazmadan veritabanıyla ayağa kalkaçık

            İpucu

            url verme. Classpath'te gömülü bir veritabanı olursa Boot onu kendisi açar.

          • Kendi DataSource'unla Boot'unkini geri çektiraçık

            İpucu

            Aynı tipte bir bean tanımla; exclude kullanma.

          Olay günlüğü (0)

          Henüz olay yok. Oynat veya adımla.

          1. Varsayılanla oynat. Starter, sürücü ve url var: Hikari havuzu, JPA ve JdbcTemplate kuruldu.
          2. url’i kapat. Meşhur hata: “‘url’ attribute is not specified”.
          3. Sürücüyü H2 yap. Aynı durumda Boot bellek içi bir veritabanı açıyor.
          4. Kendi DataSource’unu aç. Boot’unki geri çekildi.
          5. Onu kapat ve exclude’u aç. DataSource yok, JPA kurulamıyor.
          Hızlı kontrolOrta

          Projeye spring-boot-starter-data-jpa ve PostgreSQL sürücüsünü ekledin ama hiçbir ayar yazmadın. Uygulama başlarken ne olur?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          Satır satır: bir auto-configuration okumak

          Koşullar sırayla değerlendiriliyor

          GreetingAutoConfiguration.java
          şu an çalışan satır@AutoConfiguration
          2@ConditionalOnClass(GreetingService.class)
          3@EnableConfigurationProperties(GreetingProperties.class)
          4public class GreetingAutoConfiguration {
          5
          6 @Bean
          7 @ConditionalOnMissingBean
          8 @ConditionalOnProperty(name = "greeting.enabled", matchIfMissing = true)
          9 GreetingService greetingService(GreetingProperties props) {
          10 return new GreetingService(props.prefix());
          11 }
          12}

          Debug

          Adım 1/5

          Boot Sınıf AutoConfiguration.imports dosyasında listelendiği için aday. Kullanıcının kendi yapılandırması çoktan işlendi.

          Java 21UTF-8LF1:1

          Sol/sağ ok tuşlarıyla da gezebilirsin.

          Bir bean’in nereden geldiğini tahmin etme, sor. Uygulamayı --debug ile başlat: CONDITIONS EVALUATION REPORT her adayın hangi koşulda eşleşip eşleşmediğini yazar.

          Kafam karıştı, daha basit anlat

          Her @Conditional... satırı bir “eğer”dir: bu sınıf varsa, bu bean yoksa, bu ayar açıksa. Hepsi evet derse bean kurulur.

          Hızlı kontrolOrta

          Bir bean'in neden oluşturulmadığını ya da nereden geldiğini görmenin en doğrudan yolu hangisi?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          Tuzaklar

          Kendi starter'ını yazarken· istersen atla

          Auto-configuration sınıfını uygulamanın taradığı pakete koyma: sıra garantisi kaybolur ve @ConditionalOnMissingBean yanlış karar verir. Boot 3’ten beri kayıt yalnızca AutoConfiguration.imports dosyasından okunur, spring.factories artık yetmez.

          Düşünmeden exclude. Tek bir bean’i değiştirmek istiyorsan kendininkini tanımlaman yeter. exclude, o auto-configuration’ın getirdiği her şeyi birden kapatır.

          Gereksiz starter. Classpath’e düşen her kütüphane yeni auto-configuration’ları tetikleyebilir. Kullanmadığın starter’ı kaldır.

          Hızlı kontrolİleri

          Şirket içi bir kütüphanede varsayılan bir CacheManager sunmak istiyorsun, kullanıcı kendi CacheManager'ını tanımlarsa geri çekilmeli. Bazı servislerde iki CacheManager oluşuyor. Hangi satırlar sorunlu?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          Hatalı satıra dokun, sonra kontrol et.

          CacheDefaults.java
          Java 21UTF-8LF

          Bankalarda her serviste denetim kaydı zorunludur; platform ekibi bunu tek bağımlılıkla kuran bir starter yazar. Aşağıdaki örnek böyle bir starter: koşullar, ödeme servisinin kendi sink’iyle geri çekilme ve testleri.

          Derinleş · Bankanın platform ekibinden: audit-spring-boot-starter 6 dosya · ~87 satır · ilk okumada atlayabilirsin
          Proje dosyaları

          audit-starter/src/main/java/com/bank/audit/autoconfigure/ AuditAutoConfiguration.java Auto-configuration: koşullar sağlanırsa bean'leri kurar, uygulama kendi bean'ini tanımlarsa geri çekilir.

          audit-starter/src/main/java/com/bank/audit/autoconfigure/AuditAutoConfiguration.java
          @AutoConfiguration(after = JacksonAutoConfiguration.class)
          @ConditionalOnClass(AuditClient.class) // the library is on the classpath
          @ConditionalOnProperty(prefix = "audit", name = "enabled", havingValue = "true", matchIfMissing = true)
          @EnableConfigurationProperties(AuditProperties.class)
          public class AuditAutoConfiguration {
          // Back off if the application defines its own sink.
          @Bean
          @ConditionalOnMissingBean
          AuditSink auditSink(AuditProperties props, ObjectMapper json) {
          return new HttpAuditSink(props.endpoint(), props.timeout(), json);
          }
          @Bean
          @ConditionalOnMissingBean
          AuditClient auditClient(AuditSink sink, AuditProperties props) {
          return new AuditClient(sink, props.serviceName());
          }
          // Only in servlet web apps: record every request automatically.
          @Bean
          @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
          @ConditionalOnMissingBean
          AuditRequestFilter auditRequestFilter(AuditClient client) {
          return new AuditRequestFilter(client);
          }
          }

          audit-starter/src/main/java/com/bank/audit/autoconfigure/ AuditProperties.java Starter'ın ayarları: audit.* altında, IDE tamamlamasıyla.

          audit-starter/src/main/java/com/bank/audit/autoconfigure/AuditProperties.java
          @ConfigurationProperties("audit")
          public record AuditProperties(
          @DefaultValue("true") boolean enabled,
          @DefaultValue("http://audit-service/events") URI endpoint,
          @DefaultValue("500ms") Duration timeout,
          @DefaultValue("unknown-service") String serviceName) {
          }

          audit-starter/src/main/resources/META-INF/spring/ org.springframework.boot.autoconfigure.AutoConfiguration.imports Boot'un bu sınıfı bulmasını sağlayan dosya. Bileşen taraması yok; açık liste.

          audit-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
          com.bank.audit.autoconfigure.AuditAutoConfiguration

          payments-service/src/main/java/com/bank/payments/config/ AuditOverride.java Ödeme servisi: denetim kaydı kaybolmasın diye kendi Kafka AuditSink'ini tanımlar, starter'ınki kurulmaz.

          payments-service/src/main/java/com/bank/payments/config/AuditOverride.java
          // The payments service must not lose an audit event when the audit service is down:
          // it writes them to Kafka, which keeps them until they are consumed.
          // Because the starter's sink is @ConditionalOnMissingBean, this one wins
          // and the starter quietly keeps its AuditClient, wired to this sink.
          @Configuration
          class AuditOverride {
          @Bean
          AuditSink kafkaAuditSink(KafkaTemplate<String, AuditEvent> kafka) {
          // Keyed by subject (the customer), so one customer's events stay in order.
          // join(): the payment waits for the broker's ack instead of fire-and-forget.
          return event -> kafka.send("audit-events", event.subject(), event).join();
          }
          }

          audit-starter/src/test/java/com/bank/audit/autoconfigure/ AuditAutoConfigurationTest.java ApplicationContextRunner: koşulları uygulama başlatmadan, milisaniyelerde test eder.

          audit-starter/src/test/java/com/bank/audit/autoconfigure/AuditAutoConfigurationTest.java
          class AuditAutoConfigurationTest {
          private final ApplicationContextRunner runner = new ApplicationContextRunner()
          .withConfiguration(AutoConfigurations.of(AuditAutoConfiguration.class, JacksonAutoConfiguration.class));
          @Test
          void createsDefaultBeans() {
          runner.run(context -> {
          assertThat(context).hasSingleBean(AuditClient.class);
          assertThat(context).getBean(AuditSink.class).isInstanceOf(HttpAuditSink.class);
          });
          }
          @Test
          void backsOffWhenTheApplicationDefinesItsOwnSink() {
          runner.withBean(AuditSink.class, () -> event -> {})
          .run(context -> assertThat(context).getBean(AuditSink.class).isNotInstanceOf(HttpAuditSink.class));
          }
          @Test
          void canBeSwitchedOff() {
          runner.withPropertyValues("audit.enabled=false")
          .run(context -> assertThat(context).doesNotHaveBean(AuditClient.class));
          }
          @Test
          void staysAwayWhenTheLibraryIsMissing() {
          runner.withClassLoader(new FilteredClassLoader(AuditClient.class))
          .run(context -> assertThat(context).doesNotHaveBean(AuditClient.class));
          }
          }

          payments-service/src/main/resources/ application.yml Hangi koşulun neden tuttuğunu görmek için rapor.

          payments-service/src/main/resources/application.yml
          audit:
          service-name: payments-service
          timeout: 300ms
          # Prints the conditions evaluation report at startup: every auto-configuration,
          # whether it matched, and which condition decided it. Development only.
          debug: true

          Kendini sına

          Şimşek turu1/5

          Bir starter içinde çoğunlukla kod değil, bağımlılık listesi vardır.

          Soru 1/2İleri

          Spring dokümantasyonu @ConditionalOnMissingBean'i yalnızca auto-configuration sınıflarında kullanmayı önerir. Neden?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          Aklında kalacak üç şey

          1. 1 Starter kod getirmez, bağımlılık getirir. Nesneleri kuran şey, ortama bakıp karar veren auto-configuration sınıflarıdır.
          2. 2 Aynı tipte kendi bean'ini tanımladığında Boot'unki geri çekilir. exclude ise o auto-configuration'ı tamamen kapatır.
          3. 3 Bir bean'in neden kurulduğunu ya da kurulmadığını --debug ile açılan koşul raporu söyler.
          Sonraki kapı Alanın üstüne @Positive yazdın ve eksi bir sayı yine de içeri girdi. Kim kontrol etmedi? Validation ve Hata Yönetimi — İstemci Ne Görüyor? · 8 dk

          5 kart sonraki derste seni bekliyor

          0/5 kart bu dersten toplandı

          Bu dersin üstüne kurulanlar

          Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.