19 Übung: Component Scan sichtbar machen

Der Component Scan arbeitet im Verborgenen. In dieser Übung machst du ihn sichtbar – und testest, was passiert, wenn Komponenten nicht gefunden werden.

19.1 Teil 1: Beans auflisten

Spring Boot Actuator zeigt alle registrierten Beans. Aktiviere ihn.

Schritt 1: Dependency hinzufügen

In build.gradle.kts, im dependencies-Block:

implementation("org.springframework.boot:spring-boot-starter-actuator")

Schritt 2: Endpoint freischalten

In application.yaml:

management:
  endpoints:
    web:
      exposure:
        include: beans, health, info

Schritt 3: Anwendung starten und Endpoint aufrufen

./gradlew bootRun

Öffne im Browser oder mit curl:

curl http://localhost:8080/actuator/beans | jq '.contexts.application.beans | keys[]' | grep -i arcade

Du siehst alle Beans aus dem Arcade-Package:

"arcadeHighscoreApplication"
"gameService"
"homeController"
"inMemoryGameRepository"

Spring hat sie alle gefunden und registriert.

19.2 Teil 2: Eine Bean verstecken

Was passiert, wenn eine Komponente nicht gefunden wird?

Schritt 1: Annotation entfernen

Öffne InMemoryGameRepository.java und kommentiere die Annotation aus:

// @Repository  <- auskommentiert
public class InMemoryGameRepository implements GameRepository {
    // ...
}

Schritt 2: Neu starten

./gradlew bootRun

Die Anwendung startet nicht:

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in de.digitalfrontiers.arcade.service.GameService 
required a bean of type 'de.digitalfrontiers.arcade.repository.GameRepository' 
that could not be found.

Action:

Consider defining a bean of type 
'de.digitalfrontiers.arcade.repository.GameRepository' in your configuration.

Spring findet keine Implementierung von GameRepository. Der GameService kann nicht erzeugt werden.

Schritt 3: Annotation wiederherstellen

@Repository  // wieder aktiv
public class InMemoryGameRepository implements GameRepository {

Die Anwendung startet wieder.

19.3 Teil 3: Package-Grenzen testen

Jetzt testen wir, was außerhalb des Scan-Bereichs passiert.

Schritt 1: Neues Package außerhalb erstellen

Erstelle ein Package de.digitalfrontiers.common (Achtung: nicht unter arcade):

src/main/java/
├── de/digitalfrontiers/
│   ├── arcade/        <- wird gescannt
│   │   └── ...
│   └── common/        <- wird NICHT gescannt
│       └── LoggingHelper.java

Schritt 2: Komponente erstellen

src/main/java/de/digitalfrontiers/common/LoggingHelper.java:

package de.digitalfrontiers.common;

import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;

@Component
public class LoggingHelper {
    
    @PostConstruct
    public void init() {
        System.out.println("=== LoggingHelper wurde initialisiert ===");
    }
    
    public void log(String message) {
        System.out.println("[LOG] " + message);
    }
}

Schritt 3: Starten und beobachten

./gradlew bootRun

Such in der Konsolenausgabe nach LoggingHelper wurde initialisiert. Du wirst es nicht finden. Die Klasse liegt außerhalb des Scan-Bereichs.

Schritt 4: Scan erweitern

Ändere die Hauptklasse:

@SpringBootApplication
@ComponentScan(basePackages = {
    "de.digitalfrontiers.arcade",
    "de.digitalfrontiers.common"
})
public class ArcadeHighscoreApplication {
    // ...
}

Starte neu. Jetzt erscheint die Meldung:

=== LoggingHelper wurde initialisiert ===

Schritt 5: Aufräumen

Entferne @ComponentScan wieder und lösche das common-Package. Wir brauchen es nicht weiter.

19.4 Teil 4: Debug-Logging aktivieren

Spring kann detailliert protokollieren, was es beim Scan findet.

In application.yaml:

logging:
  level:
    org.springframework.context.annotation: DEBUG

Beim Start siehst du jetzt:

DEBUG ClassPathBeanDefinitionScanner - Identified candidate component class: 
      file [.../arcade/service/GameService.class]
DEBUG ClassPathBeanDefinitionScanner - Identified candidate component class: 
      file [.../arcade/repository/InMemoryGameRepository.class]
DEBUG ClassPathBeanDefinitionScanner - Identified candidate component class: 
      file [.../arcade/web/HomeController.class]

Spring listet jede gefundene Komponente auf.

Entferne das Debug-Logging nach dem Experiment – es produziert viel Output.