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)
Java
@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.

Java
// 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.

Java
// Field injection — works, but avoid it
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
}
Java
// 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 UserService simply cannot exist without a UserRepository; 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 containernew 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 A needs B and B needs A, 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/@PreDestroy callbacks and closing resources on shutdown.
Java
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.

Java
@Service
public class UserService { }
Java
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
Java
@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 a singleton bean 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.