Dependency Injection
BeginnerSpring's DI container wires your application together. Understanding @Component stereotypes, @Bean factory methods, @Qualifier disambiguation, and scope is essential for every Spring developer.
Overview
Spring's component model uses stereotype annotations to classify beans: @Component (generic), @Service (business logic), @Repository (data access — also adds persistence exception translation), @Controller/@RestController (web layer). @Configuration + @Bean defines beans explicitly when you need constructor arguments or third-party classes. When multiple beans satisfy an injection point, @Qualifier or @Primary resolves the ambiguity. Bean scope (singleton, prototype, request, session) controls how many instances are created.
@Component Stereotypes
The four stereotypes are semantically equivalent to @Component but communicate intent and enable framework features. @Repository adds PersistenceExceptionTranslationPostProcessor support, translating database-specific exceptions into Spring's DataAccessException hierarchy.
// @Service — business logic, no extra framework magic
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
public User register(RegisterRequest req) {
if (userRepository.existsByEmail(req.email())) {
throw new EmailAlreadyExistsException(req.email());
}
User user = new User(req.email(), passwordEncoder.encode(req.password()));
User saved = userRepository.save(user);
emailService.sendWelcome(saved); // fire and forget
return saved;
}
}
// @Repository — data access layer
// Also translates SQLExceptions to DataAccessException
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
boolean existsByEmail(String email);
}
// Plain @Component — doesn't fit other stereotypes
@Component
public class SlugGenerator {
public String generate(String title) {
return title.toLowerCase().replaceAll("\s+", "-");
}
}@Configuration and @Bean
Use @Bean methods inside @Configuration classes to register third-party classes or beans that need complex initialization. @Configuration classes are proxied by CGLIB so @Bean method calls between them return the same singleton instance.
@Configuration
public class AppConfig {
// Register third-party class as a Spring bean
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12); // cost factor 12
}
// Bean with dependencies (injected as method parameters)
@Bean
public ObjectMapper objectMapper() {
return Jackson2ObjectMapperBuilder.json()
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.modules(new JavaTimeModule())
.build();
}
// Conditional bean — only if property is set
@Bean
@ConditionalOnProperty(name = "feature.email.enabled", havingValue = "true")
public EmailClient emailClient(
@Value("${email.api.key}") String apiKey) {
return new ResendEmailClient(apiKey);
}
}@Qualifier, @Primary and Bean Scopes
When two beans implement the same interface, @Primary marks the default and @Qualifier picks a specific one by name. Bean scope controls how many instances exist: singleton (default, one per context), prototype (new instance per injection), request (one per HTTP request — needs web context).
// Two implementations of the same interface
@Service("razorpayPayment")
public class RazorpayPaymentService implements PaymentService { ... }
@Service("paypalPayment")
@Primary // used when no @Qualifier specified
public class PaypalPaymentService implements PaymentService { ... }
// Injecting a specific one
@Service
public class CheckoutService {
private final PaymentService razorpay;
private final PaymentService paypal;
public CheckoutService(
@Qualifier("razorpayPayment") PaymentService razorpay,
@Qualifier("paypalPayment") PaymentService paypal) {
this.razorpay = razorpay;
this.paypal = paypal;
}
}
// Prototype scope — new instance every time
@Component
@Scope("prototype")
public class ReportGenerator {
// Stateful — each caller gets its own instance
}
// Request scope — one per HTTP request
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
private String correlationId;
}Key Points to Remember
- 1@Service, @Repository, @Controller are specializations of @Component — use them to communicate layer intent.
- 2@Repository adds persistence exception translation: vendor SQLExceptions → Spring DataAccessException.
- 3Use @Bean methods in @Configuration classes for third-party objects or beans needing complex setup.
- 4@Primary sets a default when multiple beans satisfy a type; @Qualifier picks a specific named bean.
- 5Singleton scope (default) means one shared instance — never store mutable request state in a singleton.
- 6Prototype scope creates a new instance per injection; request scope creates one per HTTP request.
Interview Questions
Sign in to ask AriaWhat is the difference between @Component, @Service, and @Repository?
When would you use @Bean instead of @Component?
What happens when two beans of the same type exist and you use @Autowired without @Qualifier?
How do you inject a prototype-scoped bean into a singleton?
What is @Primary and how does it differ from @Qualifier?
Ask Aria about Dependency Injection
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.