Skip to main content

Chapter 3: Beans and Spring Context

Introduction​

In the previous chapters, you used @Service, @Repository, @Controller without really understanding what they were. This chapter reveals what a Bean is and how Spring manages them.

What You Will Learn​

  1. What is a Spring Bean?
  2. The different stereotype annotations
  3. How to create Beans with @Bean and @Configuration
  4. How to name Beans
  5. How to choose between multiple Beans (@Qualifier, @Primary)

Duration: 45 minutes


What is a Bean?​

Simple Definition​

A Bean = An object managed by Spring

That's it! Nothing magical.

Analogy​

Imagine Spring as a hotel:

Spring Hotel
├── Room 101: Bean "userService"
├── Room 102: Bean "productRepository"
├── Room 103: Bean "orderController"
└── Room 104: Bean "emailService"

Spring (the hotel):

  • Creates the objects (rooms)
  • Names them (room numbers)
  • Keeps them in memory
  • Provides them when needed

You (the client):

  • Request a Bean: "I want userService"
  • Spring gives it to you: "Here, room 101!"

Bean vs Normal Object​

Normal Object (Without Spring)​

// You create
EmailService emailService = new EmailService();

// You manage the object's lifecycle
emailService.configure();

// You have to pass it everywhere
OrderService orderService = new OrderService(emailService);

Problems:

  • ❌ You have to create manually
  • ❌ You have to manage dependencies
  • ❌ You have to pass objects everywhere

Spring Bean​

@Service  // ← Spring manages this object
public class EmailService {
// Automatic configuration
}

@Service
public class OrderService {
// Spring automatically injects EmailService
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}

Advantages:

  • ✅ Spring creates automatically
  • ✅ Spring manages dependencies
  • ✅ Spring injects where necessary

Creating Beans: Method 1 - Stereotype Annotations​

The 4 Main Annotations​

Spring offers 4 annotations to create Beans:

@Component  // Generic component
@Service // Business layer
@Repository // Data layer
@Controller // Presentation layer (web)

Hierarchy​

All inherit from @Component!


1. @Component (Generic)​

Usage: For any component that doesn't fit into other categories.

@Component
public class PriceCalculator {

public double calculateDiscount(double price) {
return price * 0.9;
}
}

When to use it?

  • Utility classes
  • Components that are neither Service, Repository, nor Controller

2. @Service (Business Logic)​

Usage: For the business layer (Service Layer).

@Service
public class OrderService {

private final OrderRepository orderRepository;
private final EmailService emailService;

public OrderService(OrderRepository orderRepository,
EmailService emailService) {
this.orderRepository = orderRepository;
this.emailService = emailService;
}

public void processOrder(Order order) {
// Business logic
orderRepository.save(order);
emailService.sendConfirmation(order);
}
}

When to use it?

  • Classes containing business logic
  • Orchestration of multiple repositories
  • Transactions

3. @Repository (Data Access)​

Usage: For the data access layer.

@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByCustomerId(Long customerId);
}

Specific advantages:

  • ✅ Automatic translation of SQL exceptions into Spring exceptions
  • ✅ Clear indication of role (data access)

When to use it?

  • JPA Repository interfaces
  • DAO (Data Access Object) classes

4. @Controller and @RestController (Web)​

Usage: For the web layer.

@RestController
@RequestMapping("/api/orders")
public class OrderController {

private final OrderService orderService;

public OrderController(OrderService orderService) {
this.orderService = orderService;
}

@GetMapping
public List<Order> getAllOrders() {
return orderService.getAllOrders();
}
}

Difference:

  • @Controller → Returns views (HTML)
  • @RestController → Returns JSON (REST API)

Summary: When to Use Which Annotation?​

AnnotationLayerUsage
@ComponentGenericUtility classes, other components
@ServiceBusinessBusiness logic, orchestration
@RepositoryDataDatabase access
@ControllerWebWeb pages (HTML)
@RestControllerWebREST API (JSON)

Golden Rule: Use the most specific annotation!


Creating Beans: Method 2 - @Bean and @Configuration​

Why Another Method?​

Problem: You can't add @Service to classes you don't control.

Examples:

  • External library classes
  • Standard Java classes (ObjectMapper, RestTemplate, etc.)

Solution: @Bean and @Configuration


@Configuration​

Spring configuration class:

@Configuration
public class AppConfig {

// @Bean methods here
}

@Configuration tells Spring: "This class contains Bean definitions"


@Bean​

Create a Bean manually:

@Configuration
public class AppConfig {

@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
return mapper;
}
}

What happens:

1. Spring scans AppConfig
↓
2. Spring finds the method objectMapper() annotated with @Bean
↓
3. Spring calls objectMapper()
↓
4. Spring keeps the result as a Bean named "objectMapper"
↓
5. You can inject ObjectMapper everywhere!

Complete Example​

@Configuration
public class EmailConfig {

// Bean 1: Email configuration
@Bean
public EmailSettings emailSettings() {
EmailSettings settings = new EmailSettings();
settings.setHost("smtp.gmail.com");
settings.setPort(587);
return settings;
}

// Bean 2: Sending service (depends on Bean 1)
@Bean
public EmailSender emailSender(EmailSettings settings) {
// ^^^^^^^^^^^^^^^^^^^^^^
// Spring automatically injects!
return new EmailSender(settings);
}
}

Usage:

@Service
public class OrderService {

private final EmailSender emailSender;

// Spring injects the Bean created in EmailConfig
public OrderService(EmailSender emailSender) {
this.emailSender = emailSender;
}
}

@Bean vs @Service​

Aspect@Service@Bean
On what?ClassMethod
Where?AnywhereIn @Configuration
ControlYour classAny class
UsageBusiness classesConfiguration, external classes

Example:

// ✅ Your class
@Service
public class MyService { }

// ✅ External class
@Configuration
public class Config {
@Bean
public ExternalLibraryClass externalClass() {
return new ExternalLibraryClass();
}
}

Naming Beans​

Default Name​

With @Service, @Component, etc.:

@Service
public class UserService { }
// ^^^^^^^^^^^
// Bean name: "userService" (camelCase)

With @Bean:

@Bean
public EmailSender emailSender() {
// ^^^^^^^^^^^^
// Bean name: "emailSender"
return new EmailSender();
}

Rule: First letter lowercase (camelCase)


Custom Name​

With @Service:

@Service("myCustomName")
public class UserService { }
// Bean named: "myCustomName"

With @Bean:

@Bean(name = "gmailSender")
public EmailSender emailSender() {
return new GmailEmailSender();
}
// Bean named: "gmailSender"

Handling Multiple Implementations​

Problem​

public interface PaymentProcessor {
void process(Payment payment);
}

@Service
public class StripePayment implements PaymentProcessor { }

@Service
public class PayPalPayment implements PaymentProcessor { }

// ❌ Spring doesn't know which one to inject!
@Service
public class OrderService {
private final PaymentProcessor processor;

public OrderService(PaymentProcessor processor) {
// Startup error!
}
}

Solution 1: @Primary​

Mark one implementation as the default:

@Service
@Primary // ← This implementation is default
public class StripePayment implements PaymentProcessor {
@Override
public void process(Payment payment) {
System.out.println("Payment via Stripe");
}
}

@Service
public class PayPalPayment implements PaymentProcessor {
@Override
public void process(Payment payment) {
System.out.println("Payment via PayPal");
}
}

Result:

@Service
public class OrderService {
private final PaymentProcessor processor;

// Spring automatically injects StripePayment (@Primary)
public OrderService(PaymentProcessor processor) {
this.processor = processor;
}
}

Solution 2: @Qualifier​

Choose a specific implementation:

@Service("stripePayment")
public class StripePayment implements PaymentProcessor { }

@Service("paypalPayment")
public class PayPalPayment implements PaymentProcessor { }

Injection with choice:

@Service
public class OrderService {
private final PaymentProcessor processor;

public OrderService(@Qualifier("paypalPayment") PaymentProcessor processor) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// Explicitly chooses PayPal
this.processor = processor;
}
}

Solution 3: Inject All Beans​

If you need all implementations:

@Service
public class PaymentOrchestrator {

private final List<PaymentProcessor> allProcessors;

// Spring injects a list with ALL PaymentProcessors
public PaymentOrchestrator(List<PaymentProcessor> allProcessors) {
this.allProcessors = allProcessors;
}

public void processWithAll(Payment payment) {
for (PaymentProcessor processor : allProcessors) {
processor.process(payment);
}
}
}

The Spring Context (ApplicationContext)​

What is the Context?​

The Spring Context = The container that holds all the Beans


Accessing the Context (Rarely Necessary)​

Normally, Spring injects automatically. But if needed:

@Service
public class MyService {

private final ApplicationContext context;

// Inject the context itself
public MyService(ApplicationContext context) {
this.context = context;
}

public void doSomething() {
// Manually retrieve a Bean
UserService userService = context.getBean(UserService.class);

// Retrieve a Bean by name
EmailSender sender = context.getBean("emailSender", EmailSender.class);

// List all Beans of a type
Map<String, PaymentProcessor> processors =
context.getBeansOfType(PaymentProcessor.class);
}
}

⚠️ Avoid doing this! It's an anti-pattern. Use normal injection.


Bean Lifecycle​

Steps​

1. Instantiation
↓
2. Dependency injection
↓
3. Initialization (@PostConstruct)
↓
4. Bean ready (usable)
↓
5. Destruction (@PreDestroy)

Lifecycle Hooks​

Execute code after injection:

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;

@Service
public class DatabaseService {

private final DataSource dataSource;

public DatabaseService(DataSource dataSource) {
this.dataSource = dataSource;
// ❌ dataSource not yet fully initialized
}

@PostConstruct // ← Called AFTER injection
public void init() {
System.out.println("DatabaseService initialized");
// ✅ Everything is ready, you can do initializations
connectToDatabase();
}

@PreDestroy // ← Called BEFORE destruction
public void cleanup() {
System.out.println("DatabaseService destroyed");
// Close connections, release resources
disconnectFromDatabase();
}
}

Execution order:

1. new DatabaseService(dataSource)  ← Constructor
2. init() ← @PostConstruct
3. [Bean used during application]
4. cleanup() ← @PreDestroy (at shutdown)

Complete Example​

// Interface
public interface NotificationService {
void send(String message);
}

// Implementation 1
@Service("emailNotification")
public class EmailNotification implements NotificationService {

@PostConstruct
public void init() {
System.out.println("EmailNotification initialized");
}

@Override
public void send(String message) {
System.out.println("Email: " + message);
}

@PreDestroy
public void cleanup() {
System.out.println("EmailNotification destroyed");
}
}

// Implementation 2
@Service("smsNotification")
@Primary // ← Default
public class SmsNotification implements NotificationService {

@Override
public void send(String message) {
System.out.println("SMS: " + message);
}
}

// Configuration
@Configuration
public class AppConfig {

@Bean
public NotificationLogger logger() {
return new NotificationLogger();
}
}

// Usage
@Service
public class AlertService {

private final NotificationService notification;
private final List<NotificationService> allNotifications;

public AlertService(
NotificationService notification, // SmsNotification (@Primary)
List<NotificationService> allNotifications // Both
) {
this.notification = notification;
this.allNotifications = allNotifications;
}

public void alert(String message) {
// Uses SMS (Primary)
notification.send(message);
}

public void alertAll(String message) {
// Uses all
allNotifications.forEach(n -> n.send(message));
}
}

// Usage with choice
@Service
public class SpecialAlertService {

private final NotificationService notification;

public SpecialAlertService(
@Qualifier("emailNotification") NotificationService notification
) {
this.notification = notification;
}
}

Chapter Summary​

What is a Bean?​

Bean = Object managed by Spring

Creating Beans​

Method 1: Stereotype Annotations

@Component  // Generic
@Service // Business
@Repository // Data
@Controller // Web

Method 2: @Bean in @Configuration

@Configuration
public class Config {
@Bean
public MyClass myBean() {
return new MyClass();
}
}

Handling Multiple Implementations​

@Primary              // Default implementation
@Qualifier("name") // Choose specifically
List<Interface> // Inject all

Lifecycle​

@PostConstruct   // After injection
@PreDestroy // Before destruction

Key Points​

✅ Bean = Object managed by Spring
✅ @Service, @Repository, @Controller inherit from @Component
✅ @Bean for external classes or complex configuration
✅ @Primary for default implementation
✅ @Qualifier for specific choice
✅ @PostConstruct for initialization
✅ @PreDestroy for cleanup


Self-Assessment​

  1. What is the difference between a Bean and a normal object?
  2. When to use @Service vs @Component?
  3. What is the difference between @Service and @Bean?
  4. How to handle multiple implementations of an interface?
  5. What is @PostConstruct used for?

Next Step​

You now understand what Beans are. Let's move on to Scopes to control their lifecycle!

➡️ Chapter 4: Scopes and Configuration

⬅️ Previous Chapter: Dependency Injection

⬅️ Back to Module 5