Dependency Injection & Beans
@Component, @Service and @Repository stereotypes, constructor vs field injection, the ApplicationContext, and bean scopes.
Stereotype annotations
Spring needs to know which of your classes it should manage as beans — objects it constructs, wires, and owns the lifecycle of. The most common way to mark a class is with a stereotype annotation, all of which are specializations of the base @Component:
| Annotation | Use for |
|---|---|
@Component |
Any generic Spring-managed bean that doesn't fit a more specific category |
@Service |
Business logic / service layer classes |
@Repository |
Data access classes — also translates persistence-specific exceptions into Spring's unified DataAccessException hierarchy |
@Controller / @RestController |
Web layer classes (covered in the Spring MVC and Spring Boot tracks) |
@Repository
public class JpaUserRepository implements UserRepository {
// data access code
}
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User register(String email) {
return userRepository.save(new User(email));
}
}
Functionally, @Service and @Repository behave like @Component for wiring purposes — the distinct names exist for readability and, in @Repository's case, for the extra exception-translation behavior. Using the semantically correct one makes the codebase's layering obvious at a glance.
Spring finds these classes via component scanning — by default, everything under the package of your main configuration/application class and its sub-packages is scanned for stereotype annotations.
Constructor injection (the preferred style)
There are three ways to get a dependency into a bean. Only one of them is recommended for anything beyond a quick example.
// Constructor injection — recommended
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
Since Spring 4.3, if a class has exactly one constructor, @Autowired on it is optional — Spring uses that constructor automatically. It's still fine to write it explicitly for clarity.
// Field injection — works, but avoid it
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
}
// Setter injection — rarely needed
@Service
public class UserService {
private UserRepository userRepository;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
Why constructor injection wins:
- Immutability — the field can be
final, so it's guaranteed to be set exactly once and never reassigned. - No partially-constructed objects — a
UserServicesimply cannot exist without aUserRepository; with field injection, you can construct an object first and have Spring populate the field afterwards, leaving a window where it's in an invalid state. - Testability without a container —
new UserService(mockRepository)works in a plain unit test with no Spring, no reflection tricks, no@InjectMocks. Field injection requires reflection (or a testing framework's help) to set a private field from outside the class. - Circular dependencies fail fast — if
AneedsBandBneedsA, constructor injection fails at startup with a clear error. Field/setter injection can mask the same design problem by resolving it lazily, letting a genuine design smell go unnoticed.
The ApplicationContext
The ApplicationContext is Spring's IoC container implementation — it's responsible for:
- Reading configuration (annotations, Java config classes, or historically XML) to discover bean definitions.
- Instantiating beans in dependency order.
- Injecting each bean's dependencies.
- Managing each bean's lifecycle, including calling
@PostConstruct/@PreDestroycallbacks and closing resources on shutdown.
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
In a Spring Boot application you almost never call getBean directly — the container wires everything automatically at startup, and you simply declare what each bean needs via its constructor.
Bean scopes
By default, every Spring bean is a singleton — the container creates exactly one instance and returns that same shared instance for every injection point and every getBean call.
@Service
public class UserService { }
UserService a = context.getBean(UserService.class);
UserService b = context.getBean(UserService.class);
// a == b -- same instance
| Scope | Instances created | Typical use |
|---|---|---|
singleton (default) |
One per container | Stateless services, repositories — the overwhelming majority of beans |
prototype |
A new instance every time the bean is requested | Stateful, non-thread-safe objects that shouldn't be shared |
request (web apps) |
One per HTTP request | Request-scoped data in a web application |
session (web apps) |
One per HTTP session | User-session-scoped data |
@Component
@Scope("prototype")
public class ReportGenerator {
// holds mutable state per report — must not be shared as a singleton
}
Singleton is right for the vast majority of beans because services and repositories are typically stateless — all the "state" they operate on (a user ID, an order) is passed in as a method parameter, not stored as a field. Reach for prototype only when a bean genuinely accumulates per-use mutable state.
Common mistakes
- Using field injection out of habit — it compiles fine and looks shorter, but gives up immutability, testability, and fail-fast circular dependency detection for no real benefit.
- Injecting a
prototype-scoped bean into asingletonbean via the constructor and expecting a fresh instance each time — the singleton only gets the prototype injected once, at its own construction time, unless you use a provider (ObjectProvider<T>) to fetch a new one on demand. - Forgetting that stereotype annotations only work if the class lives somewhere covered by component scanning.