Spring Boot and the IoC Container
newWhat You Will Learn
- What "Inversion of Control" and "Dependency Injection" mean.
- What
@SpringBootApplicationactually switches on. - How auto-configuration and starter dependencies work together.
- The stereotype annotations:
@Component,@Service,@Repository,@RestController,@Configuration. - Constructor injection, and why the project uses Lombok's
@RequiredArgsConstructor. - Defining beans explicitly with
@Beanmethods. - Reading configuration with
@Valueandapplication.yaml.
Chapter Structure
1.1 Inversion of Control (IoC)
Normally your code decides when to create objects:
// Without a container — you build the whole graph by hand
var userRepository = new UserRepository(dataSource);
var passwordEncoder = new BCryptPasswordEncoder();
var jwtUtil = new JwtUtil(secret, expiration);
var authService = new AuthService(userRepository, passwordEncoder, jwtUtil, revokedTokenRepository);Every class needs to know how to construct everything it depends on. With Inversion of Control that responsibility is flipped: a container creates the objects and supplies each one with what it needs. Your class just says "I need an AuthService" and receives one.
Dependency Injection (DI) is the specific technique Spring uses to do this — it "injects" collaborators through the constructor.
ApplicationContext.1.2 The Application Entry Point
The hospital-erp backend starts from a single tiny class:
package com.example.hospital_erp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class HospitalErpApplication {
public static void main(String[] args) {
SpringApplication.run(HospitalErpApplication.class, args);
}
}SpringApplication.run(...) does a lot:
- Creates the
ApplicationContext. - Component scanning — looks through the package of this class and all sub-packages for annotated classes (controllers, services, repositories, configs).
- Runs auto-configuration.
- Instantiates every bean and injects dependencies in the right order.
- Starts the embedded web server (Tomcat) on the configured port.
com.example.hospital_erp because that is where HospitalErpApplication lives. Every feature package — auth, patient, appointment, reports — sits under it, so all their beans are found automatically. A class placed in a sibling package outside that root would be silently ignored.1.2.1 What @SpringBootApplication expands to
It is a convenience annotation that combines three:
| Annotation | Effect |
|---|---|
@SpringBootConfiguration | Marks this class as a source of bean definitions (a specialised @Configuration). |
@EnableAutoConfiguration | Turns on Spring Boot's "configure things based on what is on the classpath" behaviour. |
@ComponentScan | Scans this package and below for @Component and friends. |
1.3 Auto-Configuration and Starters
A starter is a curated bundle of dependencies. The project's pom.xml pulls in:
spring-boot-starter-webmvc → REST controllers + embedded Tomcat + Jackson JSON
spring-boot-starter-data-jpa → Hibernate + Spring Data + connection pool
spring-boot-starter-security → Spring Security filter chain
spring-boot-starter-validation → Bean Validation (Jakarta)
mysql-connector-j → the MySQL JDBC driver
lombok → boilerplate reduction (compile-time)Auto-configuration reacts to those jars. Because spring-boot-starter-data-jpa and a MySQL driver are on the classpath and a spring.datasource.url is set, Spring Boot automatically builds a DataSource, an EntityManagerFactory, and a JpaTransactionManager — none of which appear anywhere in the project's code.
The project overrides exactly one piece of security auto-configuration — the SecurityFilterChain — in config/SecurityConfig.java. Everything else is left on its defaults. That is the intended style: lean on the defaults, replace only the specific bean you care about.
1.4 Beans and Stereotype Annotations
A bean is simply an object the container manages. The most common way to declare one is a stereotype annotation on a class. They are all @Component underneath; the different names document intent and sometimes add behaviour.
| Annotation | Layer | Example in hospital-erp |
|---|---|---|
@RestController | Web / API | PatientController, AuthController |
@Service | Business logic | PatientService, VisitService, AuthService |
@Repository | Persistence | PatientRepository (actually an interface — see Chapter 3) |
@Component | Anything else | JwtUtil, JwtAuthFilter |
@Configuration | Bean definitions | SecurityConfig |
@Repository talks to the database, @Service holds the rules, @RestController talks to the outside world, @Component is the generic fallback.1.4.1 Singleton scope
By default every bean is a singleton: the container makes one instance and shares it everywhere. There is one PatientService object for the whole application, used by every HTTP request at the same time.
1.5 Dependency Injection in Practice
Spring supports field, setter and constructor injection. Constructor injection is the recommended form and the only one used in this codebase. Here is AuthService:
@Service
@RequiredArgsConstructor // Lombok generates the constructor
public class AuthService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final JwtUtil jwtUtil;
private final RevokedTokenRepository revokedTokenRepository;
// ... methods use those four fields ...
}There is no @Autowired and no hand-written constructor. Two things combine:
- Lombok's
@RequiredArgsConstructorgenerates a constructor taking everyfinalfield. - Spring sees a single constructor and injects a matching bean for each parameter automatically (no annotation needed since Spring 4.3).
- Dependencies can be
final→ the object is fully built and immutable once constructed. - Impossible to create the object in an invalid, half-injected state.
- Dependencies are visible in one place; a constructor with 10 parameters is a smell telling you the class does too much.
- Trivial to unit test — just call
newwith mocks.
1.5.1 How Spring resolves a parameter
If no bean matches, startup fails with NoSuchBeanDefinitionException. If more than one matches, startup fails with NoUniqueBeanDefinitionException unless you disambiguate with @Qualifier or @Primary. Failing at startup rather than at request time is deliberate.
1.6 Defining Beans with @Bean Methods
Stereotypes work when you own the class. When the object comes from a library you cannot annotate, declare it inside a @Configuration class. From SecurityConfig:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthFilter jwtAuthFilter;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
// ...
.build();
}
}Key points:
- The method return value becomes a bean; its type is what gets injected elsewhere. That is why
AuthServicecan depend on the interfacePasswordEncodereven though the concrete class isBCryptPasswordEncoder. - A
@Beanmethod's parameters are themselves injected —HttpSecurityandAuthenticationConfigurationare beans Spring Boot auto-configured. - The bean name defaults to the method name (
passwordEncoder).
1.7 External Configuration
Values that change between environments do not belong in code. The project keeps them in src/main/resources/application.yaml:
spring:
datasource:
url: jdbc:mysql://localhost:3306/aarogya_hms?useSSL=false&serverTimezone=UTC
username: root
password: qwerty@12345
jpa:
hibernate:
ddl-auto: validate
show-sql: true
server:
port: 8080
jwt:
secret: aG9zcGl0YWwtZXJwLXNlY3JldC1rZXktbXVzdC1iZS...
expiration: 86400000spring.* keys are consumed by auto-configuration. Custom keys like jwt.* are read wherever you want with @Value. From JwtUtil:
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
// ...
}application.yaml, an environment-specific file such as application-prod.yaml (activated with SPRING_PROFILES_ACTIVE=prod), OS environment variables (JWT_SECRET), and JVM system properties. This is how the hard-coded secret and DB password in the file should be overridden in a real deployment — see Chapter 7.1.7.1 @Value vs @ConfigurationProperties
@Value("${jwt.secret}") | @ConfigurationProperties("jwt") |
|---|---|
| One key at a time, injected into a field. | Binds a whole group of keys onto a typed object. |
| Fine for one or two values (what this project does). | Better once a prefix has many keys; supports validation and IDE metadata. |
Quick Concept Map
IoC → the container owns object creation
DI → dependencies arrive via the constructor
@SpringBootApplication → config + auto-config + component scan
Starter → a bundle of dependencies → auto-configuration reacts to it
Bean → an object Spring manages (singleton by default)
@Service / @RestController / @Repository / @Component → class-level bean declarations
@Bean method → bean declaration for types you do not own
@Value + application.yaml → configuration outside code
Common Mistakes
- Putting a component in a package that is not under the main application class — it never gets scanned.
- Calling
new PatientService(...)yourself instead of injecting it, so its own dependencies arenull. - Using field injection (
@Autowiredon a field) — the field cannot befinaland the class is hard to test. - Storing request-specific state in a singleton bean's fields.
- Two beans of the same type with no
@Primary/@Qualifier, causing a startup failure. - Forgetting that
@Valueneeds the property to exist — a missingjwt.secretfails startup unless a default is given:@Value("${jwt.secret:changeme}"). - Committing real secrets to
application.yamlinstead of supplying them per-environment.
Revision Questions
- In one sentence each, define IoC and DI.
- Name the three annotations
@SpringBootApplicationcombines. - What determines which packages are component-scanned?
- What is a "starter" and how does auto-configuration use it?
- Why is constructor injection preferred over field injection?
- How does
@RequiredArgsConstructorcooperate with Spring's injection? - When do you need a
@Beanmethod instead of@Service? - What scope are beans by default, and what does that imply about state?
- Where would you put the production database password instead of
application.yaml?
Practice
AuthService and list every bean Spring must create before it can construct AuthService.@Component class UhidGenerator and inject it into PatientService in place of the private helper methods."AAR-%06d" patient-code prefix from PatientService into application.yaml and read it with @Value.Chapter Wrap-Up
A Spring Boot application is a graph of beans that the ApplicationContext builds at startup. @SpringBootApplication switches on component scanning and auto-configuration; starters put libraries on the classpath and auto-configuration turns them into working infrastructure. You declare your own classes with stereotype annotations, receive their collaborators through the constructor, and reach for @Bean methods only for types you do not own. Configuration that varies by environment lives in application.yaml and is read with @Value.
@RestController and see how Spring MVC turns a URL and a JSON body into a Java method call.Teaching note: examples are adapted from the hospital-erp backend (Spring Boot 4, Java 25) for learning clarity. Package names and class names match that project so you can open each file as you read.