🛠️ Testing, Tools & Ecosystem · Advanced

Spring Boot concepts in Java

IoC container, beans, auto-configuration, REST controllers.

🧩 The mysteryYou add one dependency and one line of config, and a fully configured database connection pool appears in your app. You never wrote new for it. Who did?

Inversion of control

Normally your classes create their dependencies with new. With IoC, it's the opposite: Spring's container creates your objects (beans) and injects their dependencies. Classes stay loosely coupled and easy to test.

How beans are found

Classes marked **@Component, @Service or @RestController are found by component scanning. @Bean** methods inside a @Configuration class register objects you build yourself. **@SpringBootApplication** = configuration + auto-configuration + component scan.

Giving Checkout its client

✗ Field injection
@Service
class Checkout {
    @Autowired
    private PaymentClient client;
}

Hidden, mutable dependency; hard to build in a unit test.

✓ Constructor injection
@Service
class Checkout {
    private final PaymentClient client;
    Checkout(PaymentClient c) {
        client = c;
    }
}

Explicit and immutable; a test can call new Checkout(fake).

Auto-configuration

Spring Boot's auto-configuration creates sensible beans conditionally, based on the classpath and properties: add the JPA starter, a PostgreSQL driver and a URL, and a DataSource appears. Each piece is guarded by conditions like *class present* and *no bean of this type yet*.

🤔 Think first

Your own DataSource

You define your own DataSource bean. What happens to the one Boot would have created?

Think about it, then reveal the answer

Boot's auto-configured DataSource backs off, and yours is used. Auto-configuration only fills the gaps you leave.

⚠️ The trap

State in a singleton

Beans are singletons by default: one shared instance per application context. All concurrent requests share its fields, so per-request mutable state like this gets mixed up between users.

@Service
class Cart {
    private Order current; // shared by
                           // every request!
}
💼 In the real world

REST in a few lines

A **@RestController** method's return value becomes the response body, typically JSON. Thanks to constructor injection, the same controller is easy to unit test: build it with a fake dependency, no Spring needed.

Key takeaways

  1. Prefer constructor injection with final fields
  2. Beans are singletons by default
  3. Auto-configuration is conditional and backs off
  4. @RestController return values become the response body (e.g. JSON)
🤯 Did you know?

Spring Boot's auto-configuration is ordinary configuration code guarded by conditions such as @ConditionalOnClass and @ConditionalOnMissingBean.

Practice questions

You add the JPA starter and a PostgreSQL driver and set the database URL. A DataSource bean appears though you never wrote one. Why?

  1. Every Spring app ships an in-memory DataSource
  2. Spring generates it from your SQL files
  3. Auto-configuration creates beans conditionally, based on the classpath and properties
  4. The JDBC driver registers itself as a Spring bean
Check your answer

Auto-configuration creates beans conditionally, based on the classpath and properties. Auto-configuration classes are guarded by conditions like 'class present on the classpath' and 'no bean of this type defined yet'.

What's the recommended way to give Checkout its PaymentClient?

@Service
class Checkout {
    @Autowired
    private PaymentClient client;
}
  1. Make the field static so it's shared
  2. Call new PaymentClient() inside each method
  3. Look it up with context.getBean() on every call
  4. Constructor injection into a final field
Check your answer

Constructor injection into a final field. Constructor injection makes the dependency explicit and immutable, and lets you build the class in a unit test without Spring.

Your API is live on the internet. Next: what attackers will throw at it, and how to write Java that survives.