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
- What is a Spring Bean?
- The different stereotype annotations
- How to create Beans with @Bean and @Configuration
- How to name Beans
- 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?
| Annotation | Layer | Usage |
|---|---|---|
@Component | Generic | Utility classes, other components |
@Service | Business | Business logic, orchestration |
@Repository | Data | Database access |
@Controller | Web | Web pages (HTML) |
@RestController | Web | REST 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? | Class | Method |
| Where? | Anywhere | In @Configuration |
| Control | Your class | Any class |
| Usage | Business classes | Configuration, 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
- What is the difference between a Bean and a normal object?
- When to use @Service vs @Component?
- What is the difference between @Service and @Bean?
- How to handle multiple implementations of an interface?
- 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