DEV Community

Hunor Vadasz-Perhat
Hunor Vadasz-Perhat

Posted on

πŸ“Œ spring-note-005: Customizing the Nature of a Bean

(Reference: Spring Docs - Customizing Beans)


πŸ”Ή Why Customize Beans?

πŸ’‘ Spring allows us to modify how beans behave using annotations and configurations.

πŸ’‘ Customization gives us control over bean instantiation, dependency resolution, and initialization.


πŸ“Œ Key Customization Features

πŸ”₯ Spring provides several ways to customize beans:

1️⃣ Lazy vs. Eager Initialization (@Lazy)

2️⃣ Defining Primary Beans (@Primary)

3️⃣ Using Qualifiers for Specific Bean Selection (@Qualifier)

4️⃣ Conditional Beans (@Conditional)


1️⃣ Lazy vs. Eager Initialization (@Lazy)

πŸ”₯ By default, Singleton Beans are created at startup.

πŸ”₯ With @Lazy, Spring delays bean creation until it is actually needed.

πŸ“Œ Example:

import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Component
@Lazy
public class Kraken {
    public Kraken() {
        System.out.println("πŸ¦‘ Kraken is awakening lazily...");
    }
}
Enter fullscreen mode Exit fullscreen mode

Test with a REST Controller:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/lazy")
public class LazyController {
    private final Kraken kraken;

    public LazyController(Kraken kraken) {
        this.kraken = kraken;
    }

    @GetMapping
    public String awakenKraken() {
        return "βš“ Kraken is now awake!";
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ Bean is NOT created at startup! It is only created when /lazy endpoint is called.


2️⃣ Defining Primary Beans (@Primary)

πŸ”₯ If multiple beans of the same type exist, Spring needs to know which one to use by default.

πŸ”₯ @Primary marks a bean as the default choice unless explicitly overridden.

πŸ“Œ Example:

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Component
@Primary
public class MainCaptain implements Captain {
    @Override
    public String command() {
        return "πŸ΄β€β˜ οΈ Main Captain: Full speed ahead!";
    }
}

@Component
public class BackupCaptain implements Captain {
    @Override
    public String command() {
        return "βš“ Backup Captain: Ready if needed!";
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ When injecting Captain, MainCaptain is used by default.


3️⃣ Using Qualifiers for Specific Bean Selection (@Qualifier)

πŸ”₯ When multiple beans exist, we can use @Qualifier to specify which one to inject.

πŸ“Œ Example:

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class Ship {
    private final Captain captain;

    public Ship(@Qualifier("backupCaptain") Captain captain) {
        this.captain = captain;
    }

    public String sail() {
        return captain.command();
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ Now BackupCaptain is explicitly used instead of MainCaptain.


4️⃣ Conditional Beans (@Conditional)

πŸ”₯ @Conditional allows us to register beans based on runtime conditions.

πŸ”₯ Useful for feature toggles, environment-based configurations, or platform-specific beans.

πŸ“Œ Example: Load a bean only if a certain property is set.

import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Component;

@Component
@Conditional(StormyWeatherCondition.class)
public class StormySail {
    public StormySail() {
        System.out.println("β›ˆοΈ Storm detected! Adjusting sails...");
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ“Œ Custom Condition Class:

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class StormyWeatherCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return "stormy".equals(context.getEnvironment().getProperty("weather.mode"));
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ If weather.mode=stormy is set in application.properties, StormySail bean will be loaded.


πŸ“Œ Hands-On Project: Testing Bean Customization

πŸ’‘ Let’s modify our Spring Boot app to test these customizations!


Step 1: Create a Spring Boot Project

1️⃣ Go to Spring Initializr

2️⃣ Select:

  • Spring Boot Version: Latest stable
  • Dependencies: Spring Web
  • Packaging: Jar 3️⃣ Click Generate and extract the zip file.

Step 2: Define Beans with Custom Behavior

πŸ“Œ Lazy Bean:

@Component
@Lazy
public class Kraken {
    public Kraken() {
        System.out.println("πŸ¦‘ Kraken is awakening lazily...");
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ“Œ Primary & Alternative Beans:

@Component
@Primary
public class MainCaptain implements Captain {
    @Override
    public String command() {
        return "πŸ΄β€β˜ οΈ Main Captain: Full speed ahead!";
    }
}

@Component
public class BackupCaptain implements Captain {
    @Override
    public String command() {
        return "βš“ Backup Captain: Ready if needed!";
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ“Œ Conditional Bean:

@Component
@Conditional(StormyWeatherCondition.class)
public class StormySail {
    public StormySail() {
        System.out.println("β›ˆοΈ Storm detected! Adjusting sails...");
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Create a REST Controller to Test Beans

πŸ“Œ Lazy Bean Activation:

@RestController
@RequestMapping("/lazy")
public class LazyController {
    private final Kraken kraken;

    public LazyController(Kraken kraken) {
        this.kraken = kraken;
    }

    @GetMapping
    public String awakenKraken() {
        return "βš“ Kraken is now awake!";
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ“Œ Check which Captain is Injected:

@RestController
@RequestMapping("/captain")
public class CaptainController {
    private final Captain captain;

    public CaptainController(Captain captain) {
        this.captain = captain;
    }

    @GetMapping
    public String getCaptain() {
        return captain.command();
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Run the Application & Test

πŸ’‘ Run the app using:

mvn spring-boot:run
Enter fullscreen mode Exit fullscreen mode

or

./mvnw spring-boot:run
Enter fullscreen mode Exit fullscreen mode

πŸ“Œ Test Lazy Initialization:

  • Open browser and visit: http://localhost:8080/lazy
  • You should see: "βš“ Kraken is now awake!"
  • And in logs: "πŸ¦‘ Kraken is awakening lazily..."

πŸ“Œ Test Default vs. Qualified Bean Injection:

  • Open browser and visit: http://localhost:8080/captain
  • If @Primary is used β†’ "πŸ΄β€β˜ οΈ Main Captain: Full speed ahead!"
  • If @Qualifier("backupCaptain") is used β†’ "βš“ Backup Captain: Ready if needed!"

πŸ“Œ Summary

βœ… @Lazy delays bean creation until needed.

βœ… @Primary sets the default bean when multiple exist.

βœ… @Qualifier allows precise bean selection.

βœ… @Conditional loads beans based on runtime conditions.


πŸ“Œ Topics Covered in This Section
πŸ“œ Customizing Beans in Spring (βœ”οΈ Covered)

  • Lazy vs. Eager Initialization (@lazy).
  • Setting Default Beans with @primary.
  • Overriding with @Qualifier when multiple beans exist.
  • Conditional Beans (@Conditional) for dynamic configurations.

πŸ› οΈ Spring Boot & Customization (βœ”οΈ Covered)

  • When to use lazy initialization for performance optimization.
  • How @primary works in multi-bean scenarios.
  • Using @Conditional to load beans based on environment properties.

πŸ”₯ Key Takeaways You Need to Remember:
βœ… @lazy prevents beans from being created at startup.
βœ… @primary sets the default bean when multiple exist.
βœ… @Qualifier ensures the correct bean is injected.
βœ… @Conditional allows runtime-based bean loading.

Top comments (0)