# Spring Framework (Core) From Zero

> Learn the Spring that Spring Boot auto-configures: the IoC container and ApplicationContext, defining beans with @Configuration and @Bean, dependency injection in depth, bean scopes and lifecycle, AOP and the proxies that power @Transactional, and Spring MVC without Boot. Write the config Boot hides - and the magic disappears.


---

# Spring Framework (Core) From Zero

Here's a secret that makes [Spring Boot](/guides/spring-boot-from-zero) stop being magic:
**Spring Boot is just core Spring with the configuration written for you.** Every `@Bean` Boot
auto-creates, every component it wires, every default it sets - that's plain Spring Framework underneath,
which Boot generates based on what's on your classpath. This guide is the one where you write that
configuration *yourself*. It's more typing and less convenience - and that's exactly the point. Once
you've assembled a Spring app by hand, Boot's "magic" becomes "oh, that's the thing I now know how to do,
done automatically."

This is a **roots guide**: it's about *understanding*, not about the fastest way to ship (Boot is that).
But the payoff is large - you'll debug Spring apps with X-ray vision, understand error messages that
baffle Boot-only developers, and see why `@Transactional` and `@Async` behave the way they do. We build
the mental model first the whole way: the container, beans, injection, lifecycle, and the proxies that
make the annotations work.

> 📝 This assumes **Java** (classes, interfaces, generics, annotations) and is far more valuable *after*
> [Spring Boot From Zero](/guides/spring-boot-from-zero) - do Boot first to be productive, then this to
> understand it. New to frameworks generally? [What a Framework Even Is](/guides/what-a-framework-even-is).

## How to read this

Read in order - it assembles a small app by hand (a notification service, then a web layer), revealing
one piece of "what Boot does for you" per phase. Phases carry difficulty badges.

## The phases

**Part 1 - The container (🟢 Basic → 🟡)**
1. **[Spring Without Boot - Why Core Spring?](01-spring-without-boot.md)** 🟢 - what Spring *is* vs Boot, and standing up an `ApplicationContext` by hand.
2. **[The IoC Container & ApplicationContext](02-the-ioc-container.md)** 🟡 - the container deeply: what it is, how it bootstraps, what a "bean" really means.
3. **[Defining Beans: @Configuration & @Bean](03-defining-beans.md)** 🟡 - declaring beans by hand (the explicit version of Boot's auto-config) vs component scanning.

**Part 2 - Wiring & lifecycle (🟡 → 🔴)**
4. **[Dependency Injection, Deep](04-dependency-injection-deep.md)** 🟡 - constructor/setter/field injection, `@Autowired` resolution, `@Qualifier`/`@Primary`, ambiguity.
5. **[Bean Scopes & Lifecycle](05-bean-scopes-and-lifecycle.md)** 🔴 - singleton/prototype/web scopes, lazy beans, the full lifecycle, and `BeanPostProcessor`.
6. **[Spring AOP & Proxies](06-spring-aop-and-proxies.md)** 🔴 - aspect-oriented programming, and how proxies make `@Transactional`/`@Async` actually work (with the self-invocation gotcha explained at the root).

**Part 3 - Web & beyond (🟡 → 🟢)**
7. **[Spring MVC Without Boot](07-spring-mvc-without-boot.md)** 🟡 - the `DispatcherServlet`, `@Controller`, and the web config Boot auto-wires.
8. **[From Core Spring to Spring Boot](08-from-core-spring-to-boot.md)** 🟢 - tie it all back: Boot = core Spring + auto-config + starters + embedded server. The magic, fully seen.

> After this, re-read the Spring Boot guide - every annotation you used there will now have a visible
> mechanism underneath it. That's the whole goal.


---

# Spring Without Boot - Why Core Spring?

If you came here from Spring Boot, you already have a working app and a comfortable feeling that the framework "handles things." This guide deliberately takes that comfort away - temporarily - so you can see what's underneath. We're going to build a Spring application *without* Boot: more typing, more visible wiring, and at the end, a much deeper understanding of what Boot was doing on your behalf the whole time.

Think of this as the **roots guide**. The [Spring Boot guide](/guides/spring-boot-from-zero) showed you the fast, pleasant path. Here we walk the slow path once, on purpose, so when you go back to Boot you'll read its annotations with X-ray vision - you'll *know* what each one replaced.

We'll use one running example across the whole guide: a `NotificationService` that needs to send messages through a `MessageSender` interface, with implementations like `EmailSender` and `SmsSender`. It's a small domain, but it's exactly the shape that makes dependency injection, qualifiers, scopes, and AOP worth learning. The web layer doesn't arrive until phase 7 - for now everything runs in a plain `main`.

## The reveal: what Spring Boot actually is

Let's name the thing this whole guide is built around, because once it's clear, everything else falls into place.

📝 **Spring Boot = core Spring Framework + auto-configuration + starters + an embedded server.** That's the entire formula. Boot is not a separate framework that competes with Spring; it's Spring with three convenience layers bolted on top. Strip those three layers away and what remains - the part that actually constructs and connects your objects - is **core Spring**, and that's what this guide is about.

> 💡 **Key point.** When you write a Boot app, the IoC container doing the real work is plain Spring. Boot's contribution is removing the setup: it picks your dependencies (starters), configures libraries with sane defaults (auto-configuration), and starts a web server for you (embedded server). Take those away and you do that setup yourself - but the engine underneath is identical.

So this isn't a different stack. It's the *same* stack with the conveniences peeled off so you can watch each gear turn.

## What core Spring is (and isn't)

📝 The **heart of the Spring Framework is the IoC container** - the part that creates your objects, figures out what each one depends on, and wires them together. (We give the container its own deep dive in [Phase 2: The IoC Container & ApplicationContext](02-the-ioc-container.md); for now, just hold the idea that *something* builds and connects your objects for you.) Everything else Spring offers - data access, transactions, web, security - is built around that container.

When you drop Boot, here's specifically what disappears:

- **No auto-configuration.** Nothing inspects your classpath and sets up defaults. If you want a feature, you add the library and configure it yourself.
- **No embedded server.** There's no Tomcat starting up. A core-Spring app is a plain Java program with a `main` method (a web server comes later, and you wire it deliberately).
- **No magic defaults.** No port 8080 appearing, no JSON serializer auto-selected. You get exactly what you ask for and nothing more.

What you *do* get is the container. To pull it in, you add one dependency yourself - `spring-context`, the module that contains the container - instead of letting a Boot starter do it:

```xml
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.1.0</version>
</dependency>
```

*What just happened:* you declared a direct dependency on `spring-context`, Spring's core container module, and you pinned the version yourself (`6.1.0`). Notice the contrast with Boot: there's no `spring-boot-starter-*` here, no parent project managing versions for you, and no bundle of related libraries arriving alongside it. You asked for the container, you got the container - that's the whole transaction. This single dependency is enough to stand up an application context, which is exactly what we'll do next.

## Standing up an ApplicationContext by hand

In Boot, the line `SpringApplication.run(...)` creates the container for you. Without Boot, you create it yourself - and seeing that one line of "magic" expand into explicit code is the whole point of this section.

First, the domain. A service that depends on an interface, and one implementation of that interface:

```java
public interface MessageSender {
    void send(String to, String message);
}
```

```java
import org.springframework.stereotype.Component;

@Component
public class EmailSender implements MessageSender {
    @Override
    public void send(String to, String message) {
        System.out.println("EMAIL to " + to + ": " + message);
    }
}
```

```java
import org.springframework.stereotype.Service;

@Service
public class NotificationService {
    private final MessageSender sender;

    public NotificationService(MessageSender sender) {   // dependency injected by the container
        this.sender = sender;
    }

    public void notifyUser(String user) {
        sender.send(user, "Welcome aboard!");
    }
}
```

*What just happened:* `NotificationService` asks for a `MessageSender` in its constructor instead of building one - that's constructor injection, exactly as in the Boot guide. `EmailSender` is flagged `@Component` so the container will create it, and `NotificationService` is flagged `@Service` for the same reason. So far this is identical to Boot code; the annotations work the same because they *are* the same - they're core Spring annotations, not Boot ones.

Now the part Boot hides. We need a configuration class that tells the container where to look for these components, and then a `main` that builds the container by hand:

```java
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "com.example.notify")
public class AppConfig {
}
```

```java
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        var context = new AnnotationConfigApplicationContext(AppConfig.class);   // build the container

        NotificationService service = context.getBean(NotificationService.class); // pull a bean out
        service.notifyUser("ada@example.com");                                     // use it

        context.close();
    }
}
```

```console
EMAIL to ada@example.com: Welcome aboard!
```

*What just happened:* this is the entire lifecycle, in the open. `new AnnotationConfigApplicationContext(AppConfig.class)` constructs the container and points it at `AppConfig`. Because `AppConfig` carries `@ComponentScan`, the container walks the `com.example.notify` package, finds `EmailSender` and `NotificationService`, creates one of each, and - seeing that `NotificationService` needs a `MessageSender` - passes the `EmailSender` bean into its constructor. Then `context.getBean(...)` hands you the fully-wired service and you call it. Every step here - create container, scan, instantiate, inject - is precisely what `SpringApplication.run(...)` does in one line.

## What you had to do that Boot did automatically

Step back and tally the work. To get that one line of output, you personally had to:

1. **Pick the dependency.** You chose `spring-context` and its version. Boot's starter would have brought it in (with a managed version) the moment you added `spring-boot-starter-web`.
2. **Write a configuration class.** `AppConfig` with `@ComponentScan` is something Boot's `@SpringBootApplication` bundles in automatically (it includes a component scan rooted at your main class's package).
3. **Create the context yourself.** You wrote `new AnnotationConfigApplicationContext(...)` and called `getBean(...)` and `close()` by hand. Boot's `SpringApplication.run(...)` did all of that - and kept the context alive for you.
4. **Skip the web server.** There is no web server here. Boot would have started embedded Tomcat from the web starter; you got a program that prints to the console and exits.

> 💡 **Insight.** Boot didn't add *power* - every capability you'll use in this guide is core Spring. Boot removed *ceremony*. Each item on that list above is a piece of setup Boot performed silently. None of it is hard; there's just a lot of it, and Boot's whole value proposition is doing it for you so you can start with business logic instead of plumbing.

Here's the relationship in one picture:

```mermaid
flowchart LR
  A[Core Spring<br/>IoC container] --> E[Spring Boot]
  B[Auto-configuration] --> E
  C[Starters] --> E
  D[Embedded server] --> E
```

This guide lives entirely in that leftmost box. Everything to its right is convenience layered on top.

## Why bother learning this

Fair question - if Boot does all of this for you, why spend a guide undoing it? Because the people who are genuinely good with Spring are the ones who can name what's in each box when something goes wrong.

> 💡 **Insight.** Knowing core Spring pays off in exactly the moments Boot's convenience runs out: a bean isn't found and you understand it's a component-scan path problem, not "Spring being weird." A startup error mentions the `ApplicationContext` and you know precisely what that is and when it's built. An annotation behaves unexpectedly and you can reason about it, because you've seen the container that reads it. Boot's "magic" stops being magic once you've built the machine by hand once.

There's a concrete payoff waiting, too: when you finish this guide and reopen the [Spring Boot guide](/guides/spring-boot-from-zero), you'll read `@SpringBootApplication` and `SpringApplication.run(...)` and see straight through them to the container, the scan, and the wiring you now understand. Same code, completely different level of comprehension.

Next, we go inside that leftmost box and take the container itself apart - what the `ApplicationContext` really is, how it manages beans, and the lifecycle from construction to shutdown.

## Recap

- **Spring Boot = core Spring + auto-configuration + starters + embedded server.** Boot isn't a rival framework; it's Spring with three convenience layers on top. This guide works in the core-Spring box, with those layers removed.
- **The heart of core Spring is the IoC container** - the engine that creates and connects your objects. Without Boot there's no auto-config, no embedded server, and no magic defaults; you add `spring-context` yourself and configure what you need.
- **You build the `ApplicationContext` by hand** with `new AnnotationConfigApplicationContext(AppConfig.class)`, where a `@Configuration` class with `@ComponentScan` tells the container where to find your `@Component`/`@Service` beans. `getBean(...)` then hands you a fully-wired object.
- **Everything you did manually, Boot does silently:** pick the dependency, write the config class, create and manage the context, and start (or skip) a server. Boot removed ceremony, not power.
- **Learning this buys you debugging clarity** - you'll understand errors, bean-not-found problems, and annotation behavior - and you'll re-read the Boot guide with X-ray vision.

## Quick check

Make sure the core mental model landed before moving on to the container itself:

```quiz
[
  {
    "q": "What is Spring Boot, in terms of core Spring?",
    "choices": [
      "A replacement framework that removes the IoC container",
      "Core Spring plus auto-configuration, starters, and an embedded server",
      "A separate web server that runs Spring apps",
      "An older version of Spring before annotations existed"
    ],
    "answer": 1,
    "explain": "Boot is core Spring with three convenience layers added on top - auto-configuration, starters, and an embedded server. The IoC container underneath is plain Spring."
  },
  {
    "q": "In a core-Spring app with no Boot, what creates the ApplicationContext?",
    "choices": [
      "Spring Boot's auto-configuration starts it automatically",
      "An embedded Tomcat server builds it on the first request",
      "You create it yourself, e.g. new AnnotationConfigApplicationContext(AppConfig.class)",
      "It is created lazily the first time you call new on any class"
    ],
    "answer": 2,
    "explain": "Without Boot, you construct the context by hand in main. That explicit line is exactly what SpringApplication.run(...) does for you in a Boot app."
  },
  {
    "q": "Which of these did you have to do by hand here that Boot would have done for you?",
    "choices": [
      "Write the business logic inside notifyUser",
      "Implement the MessageSender interface",
      "Add the spring-context dependency, write a @ComponentScan config class, and create the context",
      "Decide that NotificationService should depend on an interface"
    ],
    "answer": 2,
    "explain": "Picking the dependency, writing the configuration/component-scan class, and creating and closing the context are all setup Boot performs silently. The business logic and interface-based design are yours either way."
  }
]
```


---

# The IoC Container & ApplicationContext

In [Phase 1](01-spring-without-boot.md) you stood up an `ApplicationContext` by hand and watched it
hand you back a fully-assembled object. That object came out of *the container* - the single thing the
entire rest of Spring is built on. Defining beans, dependency injection, scopes, AOP, `@Transactional` - 
every one of those is "the container doing one more job for you." Before we add features, let's look hard
at the machine itself: what it is, what it holds, and what actually happens when it boots.

**The container is a factory that owns your objects.** You don't `new` them; you give the container
recipes, and it builds them, wires them together, and manages them for as long as your app runs.
Everything else is detail on top of that one sentence.

## Inversion of Control, concretely

📝 In [What a Framework Even Is](/guides/what-a-framework-even-is) we met the principle "don't call us,
we'll call you" - a framework runs the show and calls into *your* code. **Inversion of Control** is that
principle applied to *object creation*: instead of your code building the objects it depends on, a
container builds them and hands each object the things it needs.

Picture our domain - a `NotificationService` that sends messages through a `MessageSender` (say, an
`EmailSender`). Without a container, *your* code does all the assembly:

```java
public class App {
    public static void main(String[] args) {
        MessageSender sender = new EmailSender("smtp.acme.com", 587);  // you build this
        NotificationService service = new NotificationService(sender); // you build this
        service.notify("ada@example.com", "Welcome aboard!");          // ...and you wire them
    }
}
```
*What just happened:* your `main` is the assembler. It decides every concrete class (`EmailSender`),
every constructor argument, and the exact order things get built - sender first, then service, because
the service needs the sender. For two objects this is fine. For a real app with fifty interdependent
objects, this hand-wiring becomes a sprawling, brittle web that you maintain by hand.

With IoC, you stop being the assembler. You describe the objects and let the container build the web:

```java
public class App {
    public static void main(String[] args) {
        ApplicationContext context =
            new AnnotationConfigApplicationContext(AppConfig.class); // hand over the recipes
        NotificationService service = context.getBean(NotificationService.class);
        service.notify("ada@example.com", "Welcome aboard!");        // already wired
    }
}
```
*What just happened:* you handed the container a configuration class and asked it for a
`NotificationService`. The container had already built the `EmailSender`, built the
`NotificationService`, and injected the former into the latter - *before* you asked. Control over
"who builds what, in what order" moved out of your `main` and into the container. That's the inversion.

## What a bean really is

📝 A **bean** is an object that the Spring container instantiates, configures, injects dependencies
into, and manages for its whole lifecycle. That last part matters: a bean isn't just "an object Spring
made once" - it's an object Spring *owns*, from creation through any init/destroy callbacks until the
context shuts down.

The crucial corner that trips people up: **not every object in your app is a bean.** Only the objects
the container owns are beans. In our example, `EmailSender` and `NotificationService` are beans - the
container made them and manages them. But a `Notification` value object you `new` up inside a method to
represent one message? That's just a plain object. The container never sees it, so it's not a bean.

⚠️ "Is this a bean?" has exactly one test: *did the container create and manage it?* If you wrote
`new` yourself and the container knows nothing about it, it is not a bean - and none of Spring's
features (injection, scopes, AOP) apply to it.

Internally the container keeps **two** things, and it's worth separating them in your head:

- **Bean definitions** - the *recipes*. "There is a bean of type `EmailSender`; here is how to build
  it; it depends on nothing." Definitions are metadata, built first, before any object exists.
- **Bean instances** - the actual constructed objects, created *from* the definitions.

📝 The container reads all the definitions first, then uses them to instantiate the real objects. Keep
that order in mind - it's exactly what the bootstrap sequence below walks through.

## BeanFactory vs ApplicationContext

You'll see two container types named in Spring docs, and the distinction is simpler than it looks.

📝 **`BeanFactory`** is the bare-bones container interface - the minimum: hold bean definitions, create
beans on demand (lazily), inject their dependencies. That's it. It's the foundational contract.

📝 **`ApplicationContext`** *extends* `BeanFactory` and adds the production features you actually rely
on every day:

- **Eager singleton instantiation** - builds your singleton beans at startup, so wiring errors surface
  immediately instead of on first use.
- **Application events** - publish/subscribe between beans.
- **Internationalization (i18n)** - message source resolution.
- **AOP integration** - the hook that makes `@Transactional` and friends work (Phase 6).
- **Environment & property resolution** - profiles, `@Value`, configuration.

💡 In practice you **always use `ApplicationContext`** (concretely
`AnnotationConfigApplicationContext` for Java config, as in Phase 1). `BeanFactory` is the bare engine;
`ApplicationContext` is the engine wrapped in everything that makes Spring *Spring*. Boot's
`SpringApplication.run(...)` returns an `ApplicationContext` too - same container, just bootstrapped
for you.

## How the container bootstraps

When you construct an `ApplicationContext`, a fixed sequence runs. Knowing it turns startup errors from
mysteries into "oh, it failed at *that* step."

```mermaid
flowchart TD
  A["Read configuration<br/>(@Configuration classes / scan)"] --> B["Build bean definitions<br/>(the recipes)"]
  B --> C["Instantiate singletons<br/>(call constructors)"]
  C --> D["Resolve & inject dependencies<br/>(wire the graph)"]
  D --> E["Run init callbacks<br/>(@PostConstruct, etc.)"]
  E --> F["Context ready<br/>(getBean works)"]
```

Walk it through our domain:

1. **Read configuration** - the container reads `AppConfig` (and any component scan) to discover what
   beans should exist.
2. **Build bean definitions** - it records two recipes: one for `EmailSender`, one for
   `NotificationService` (noting that the latter needs a `MessageSender`).
3. **Instantiate singletons** - it constructs `EmailSender` first, because `NotificationService`
   can't be built until its dependency exists.
4. **Resolve & inject dependencies** - it constructs `NotificationService`, passing the already-built
   `EmailSender` into its constructor.
5. **Run init callbacks** - any `@PostConstruct` methods fire on the freshly-built beans.
6. **Context ready** - the graph is fully assembled; calls like `getBean` now return wired objects.

```console
Building EmailSender bean...
Building NotificationService bean (injecting EmailSender)...
ApplicationContext ready - 2 beans
```
*What just happened:* the container did the assembly you used to write in `main`, in dependency order,
at startup. By the time the context is "ready," `NotificationService` already holds its `EmailSender`.
Because `ApplicationContext` instantiates singletons *eagerly* (step 3), a typo like "no bean of type
`MessageSender` found" blows up at startup - not three hours later when a request finally hits that code path.

## Getting beans & the mental shift

Once the context is ready, you *can* pull a bean out by hand:

```java
NotificationService service = context.getBean(NotificationService.class);
```
*What just happened:* you asked the container for the bean of type `NotificationService`, and it
returned the fully-wired singleton it built at startup. Note what you did *not* do: you never wrote
`new NotificationService(...)`, and you never built the `EmailSender` to pass in. The container already
owns the assembled graph; `getBean` just hands you a reference to a node in it.

💡 But here's the mental shift: **you rarely call `getBean` at all.** You typically call it exactly
once - at the very top, to grab the root object that kicks off your app (in Phase 1, that was the whole
program; in a web app, the framework does even this for you). *Inside* your beans, you never reach into
the container - you declare a constructor dependency and let injection deliver it, exactly the way
`NotificationService` receives its `MessageSender`. Reaching for `getBean` inside your own beans is a
code smell: it means a class is pulling from the container instead of declaring what it needs.

So the real picture: the container is the **assembler of your entire object graph.** You describe the
nodes (beans) and their dependencies; it builds and connects all of them at startup; your code just
*uses* the wired objects.

💡 This is why we called the container the foundation. It's the "factory of factories" - the one thing
Boot bootstraps for you, and the thing every other Spring feature stands on. Dependency injection
(Phase 4) is the container resolving constructor parameters. Scopes (Phase 5) are the container
deciding how many instances to make and how long to keep them. AOP (Phase 6) is the container handing
you a proxy in place of your bean. Master the container, and the rest of Spring stops being a pile of
annotations and becomes one machine doing variations on a single job.

## Recap

1. **Inversion of Control, concretely:** instead of your code building its dependencies with `new`,
   the container builds them and injects them - control over "who builds what, in what order" moves
   from your code into the container.
2. **A bean** is an object the container instantiates, configures, injects, and manages for its whole
   lifecycle. Not every object is a bean - only the ones the container owns. Objects you `new`
   yourself are invisible to Spring.
3. **The container holds two things:** bean *definitions* (the recipes, built first) and bean
   *instances* (the objects, built from the recipes).
4. **`BeanFactory`** is the bare container (lazy, minimal); **`ApplicationContext`** extends it with
   eager singletons, events, i18n, AOP integration, and environment support. 💡 You always use
   `ApplicationContext`.
5. **Bootstrap order:** read config → build definitions → instantiate singletons → resolve & inject
   dependencies → run init callbacks → ready. Eager singletons mean wiring errors fail fast at startup.
6. **You rarely call `getBean`:** you fetch the root object once and let injection wire everything
   else. The container is the assembler of your whole object graph - the foundation every other Spring
   feature is built on.

## Quick check

Make sure the container model has landed before we start defining beans by hand:

```quiz
[
  {
    "q": "What distinguishes a 'bean' from any other object in your app?",
    "choices": [
      "The container created, configured, and manages it for its lifecycle",
      "It is any object created with the `new` keyword",
      "It must implement a special Spring Bean interface",
      "It is any object that has a constructor with parameters"
    ],
    "answer": 0,
    "explain": "A bean is an object the Spring container owns - instantiated, injected, and managed by it. An object you `new` yourself is invisible to the container and is therefore not a bean."
  },
  {
    "q": "Why do you almost always use ApplicationContext instead of BeanFactory directly?",
    "choices": [
      "ApplicationContext extends BeanFactory with eager singletons, events, i18n, AOP integration, and environment support",
      "BeanFactory cannot create beans at all",
      "ApplicationContext is the only one that supports dependency injection",
      "BeanFactory was removed in modern Spring versions"
    ],
    "answer": 0,
    "explain": "BeanFactory is the bare container contract. ApplicationContext builds on it with the production features you actually use - including eager singleton instantiation that surfaces wiring errors at startup."
  },
  {
    "q": "In the bootstrap sequence, what happens right before dependencies are injected?",
    "choices": [
      "Singleton beans are instantiated (their constructors are called)",
      "Init callbacks like @PostConstruct run",
      "The context is marked ready and getBean works",
      "Bean instances are converted into bean definitions"
    ],
    "answer": 0,
    "explain": "The order is: read config → build definitions → instantiate singletons → resolve & inject dependencies → run init callbacks → ready. Objects must exist before their dependencies can be wired into them."
  }
]
```


---

# Defining Beans: @Configuration & @Bean

In the last phase you met the container - the `ApplicationContext` that builds your objects and hands them to each other so you stop writing `new` everywhere. But we glossed over a question that turns out to be the whole game: *how does the container know which objects to build in the first place?* It doesn't read your mind. You have to tell it, and there are exactly two ways to do that.

The container is a registry of recipes. Every bean started life as a *recipe* you registered - a description that says "here's a thing I want managed, and here's how to make it." The container reads all the recipes at startup, makes one of each, and keeps them. The two ways to define beans are just two ways of writing recipes: one where **you write the factory method by hand**, and one where **Spring discovers your classes and writes the recipe for you**. Same registry, same beans at the end - different ergonomics.

## Two ways to register a bean

📝 **The two doors:**

1. **`@Configuration` + `@Bean` methods.** You write a configuration class with methods annotated `@Bean`. Each method *is* the recipe - it constructs an object and returns it, and the return value becomes a bean. This is the **explicit** door: you control every detail of how the object is built.
2. **`@Component` (and friends like `@Service`) + component scanning.** You annotate the class itself, and Spring scans your packages, finds the annotated classes, and registers a recipe for each one automatically. This is the **discovery** door: Spring does the registering, you just mark what's eligible.

Both produce real beans the container manages identically. A bean defined by `@Bean` and a bean defined by `@Component` are indistinguishable once they're in the container - the difference is purely in *how the recipe got written*. Let's see our `NotificationService` and `MessageSender` through each door.

Here's the discovery door, which you already glimpsed in Phase 2:

```java
@Service
public class EmailSender implements MessageSender {
    public void send(String to, String body) {
        System.out.println("EMAIL → " + to + ": " + body);
    }
}

@Service
public class NotificationService {
    private final MessageSender sender;

    public NotificationService(MessageSender sender) {
        this.sender = sender;
    }

    public void notifyUser(String user, String message) {
        sender.send(user, message);
    }
}
```

*What just happened:* nothing here registers a bean *explicitly* - there's no factory method anywhere. The `@Service` annotation marks each class as something Spring should pick up during component scanning. At startup Spring finds both classes, sees `NotificationService` needs a `MessageSender` in its constructor, spots that `EmailSender` is a `MessageSender`, and wires them together. You described *what* the classes are; Spring figured out *how* to build and connect them.

Now the explicit door - same two beans, but you write the recipes yourself:

```java
@Configuration
public class AppConfig {

    @Bean
    public MessageSender messageSender() {
        return new EmailSender();
    }

    @Bean
    public NotificationService notificationService() {
        return new NotificationService(messageSender());
    }
}
```

*What just happened:* `AppConfig` is a plain class marked `@Configuration`, which tells Spring "this class contains bean recipes." Each `@Bean` method is a recipe: `messageSender()` builds an `EmailSender` and returns it, and `notificationService()` builds a `NotificationService`, passing it the sender by *calling the other bean method*. Notice the classes here need **no annotations at all** - `EmailSender` and `NotificationService` could be untouched plain Java. All the wiring lives in the config class. You did by hand exactly what component scanning did automatically.

## @Bean methods: the explicit version of auto-config

Look again at that `notificationService()` method. It's a factory: it returns an object, fully constructed, ready to use. The container calls it once, keeps the result, and that result is a bean. That's the entire idea of a `@Bean` method - **a method whose return value the container adopts and manages**.

The power of writing it yourself is *control*. You decide which implementation to use, what to pass into the constructor, how to configure it before returning:

```java
@Configuration
public class AppConfig {

    @Bean
    public MessageSender messageSender(
            @Value("${notify.from-address}") String fromAddress) {
        EmailSender sender = new EmailSender();
        sender.setFromAddress(fromAddress);
        return sender;
    }

    @Bean
    public NotificationService notificationService(MessageSender sender) {
        return new NotificationService(sender);
    }
}
```

*What just happened:* the `messageSender()` recipe now reads a configuration value (`notify.from-address`) and uses it to set up the `EmailSender` before returning it - that's construction *you* fully own. The `notificationService()` method takes `MessageSender` as a parameter instead of calling `messageSender()` directly; Spring sees the parameter and injects the existing sender bean. Both styles work (we'll get to the subtle difference in calling-vs-injecting shortly), but the point stands: inside a `@Bean` method, you have a normal Java method body and can do whatever construction the object needs.

💡 **This is exactly what auto-configuration is.** When you used Spring Boot and "magic" beans appeared - a `DataSource`, a JSON serializer, a web server - every one came from a `@Bean` method just like these, written by the Spring team instead of by you. Boot ships *thousands* of `@Bean` methods bundled into its auto-configuration classes. There is no separate, mysterious mechanism: auto-configuration is plain `@Bean` methods you didn't have to type. (More on Boot in [/guides/spring-boot-from-zero](/guides/spring-boot-from-zero).)

## Component scanning: let Spring find them

The discovery door is powered by `@ComponentScan`. In a plain Spring app you point it at a package; in Spring Boot, `@SpringBootApplication` includes it pointed at your main class's package (which is why Boot apps "just find" your services).

```java
@Configuration
@ComponentScan("com.example.app")
public class AppConfig {
    // No @Bean methods needed - scanning finds the @Service / @Component classes.
}
```

*What just happened:* `@ComponentScan("com.example.app")` tells Spring to walk that package and every sub-package, looking for classes marked with stereotype annotations - `@Component` and its specializations `@Service`, `@Repository`, `@Controller`. Each match becomes a bean automatically, no factory method required. This config class has *zero* `@Bean` methods because it doesn't need any; the recipes are written by Spring from the annotated classes themselves.

📝 The stereotype annotations are all `@Component` underneath - `@Service`, `@Repository`, and `@Controller` are `@Component` with a label that documents the class's role (and occasionally enables extra behavior, like exception translation for `@Repository`). For our `MessageSender` and `NotificationService`, `@Service` is the natural fit: they hold business logic.

So when do you use which door? Here's the rule that settles almost every case:

⚠️ **The rule: `@Component` for code you own, `@Bean` for code you don't.** If it's *your* class, annotate it with `@Service`/`@Component` and let scanning find it - that's the least ceremony. But you can't add an annotation to a class you didn't write: a `DataSource` from a database library, an HTTP client, a third-party `MessageSender`. You can't open their source and slap `@Component` on it. For those, you write a `@Bean` method that constructs the third-party object and returns it. That's the *only* way to get someone else's class into the container.

This is also exactly why Boot's auto-config is built from `@Bean` methods and not component scanning: Boot is configuring *other people's* libraries (Tomcat, Jackson, Hibernate). It can't annotate their classes, so it writes `@Bean` methods - the same tool you'd reach for.

## The @Configuration proxying gotcha

Now a subtle one that bites people who think they understand `@Bean`. Look back at this:

```java
@Configuration
public class AppConfig {

    @Bean
    public MessageSender messageSender() {
        return new EmailSender();
    }

    @Bean
    public NotificationService notificationService() {
        return new NotificationService(messageSender()); // calling another @Bean method
    }
}
```

That `messageSender()` call inside `notificationService()` looks like a plain Java method call. If it *were* a plain call, it would run `messageSender()` again and create a **second, brand-new** `EmailSender` - separate from the one the container made when it processed the `messageSender()` bean. You'd end up with two senders, and your singleton wouldn't be a singleton. That would quietly break things: anything else injected with `MessageSender` gets a *different* instance than `NotificationService` is holding.

📝 But that's not what happens. Inside a `@Configuration` class, calling one `@Bean` method from another returns the **same singleton instance** the container already created - not a new object. You get exactly one `EmailSender`, shared, as you'd expect.

💡 **Why it works: Spring proxies the `@Configuration` class.** At startup Spring doesn't use your `AppConfig` directly - it creates a *subclass proxy* of it and registers that. The proxy intercepts every call to a `@Bean` method: the first call really runs your method and stores the result; every later call returns the stored bean instead of running the method again. So `messageSender()` inside `notificationService()` is intercepted, and hands back the singleton. This interception is *the* reason `@Configuration` exists as its own annotation rather than just `@Component`.

⚠️ **Gotcha - the proxy only applies inside `@Configuration`.** If you put `@Bean` methods on a `@Component` class instead (which is technically allowed, "lite mode"), there's no proxy, and calling one `@Bean` method from another *does* create a new object every time. Same code, different annotation, completely different behavior - and no error to warn you. The safe habit: put `@Bean` methods in `@Configuration` classes, full stop. Then inter-bean calls always give you singletons. (We'll meet this proxy machinery again in [Phase 6](06-spring-aop-and-proxies.md) - it's the same trick Spring uses for transactions and AOP.)

## Conditions: the layer that turns @Bean into auto-config

One last piece connects everything back to Boot. You now know auto-configuration is just `@Bean` methods. But if Boot blindly ran every one of its thousands of `@Bean` methods, you'd get a `DataSource` even in an app with no database. That's clearly not what happens. The missing ingredient is **conditions**.

💡 **Boot's `@Bean` methods are guarded by `@Conditional` annotations.** Spring lets you attach a condition to a `@Bean` method so it only fires when the condition holds. Boot leans on a few constantly:

- `@ConditionalOnClass(...)` - "create this bean only if *this class is on the classpath*" (i.e. the relevant library is present).
- `@ConditionalOnMissingBean(...)` - "create this bean only if *the developer hasn't defined their own* of this type."

Conceptually, a Boot auto-config method reads:

```java
@Bean
@ConditionalOnClass(MessageSender.class)
@ConditionalOnMissingBean
public MessageSender messageSender() {
    return new EmailSender();   // a sensible default
}
```

*What just happened:* this is a normal `@Bean` method - but the two conditions turn it into auto-configuration. `@ConditionalOnClass(MessageSender.class)` means it only even *considers* running if a `MessageSender` type is on the classpath. `@ConditionalOnMissingBean` means it backs off entirely the moment *you* define your own `MessageSender` bean. So you get a working default for free, but the instant you want control, your bean wins and Boot's steps aside silently. That "default unless you override" behavior you felt in Boot? It's literally these two annotations on plain `@Bean` methods.

📝 So here's the full picture, end to end: **a `@Bean` method is a recipe → a condition is a guard on that recipe → thousands of guarded `@Bean` methods *is* auto-configuration.** There was never any magic. You now understand the mechanism from the bottom up: when Boot wires something for you, it's running a conditional `@Bean` method, and you could read every one of them. That's the payoff of learning core Spring before - or alongside - Boot.

## Recap

- **Two doors to register a bean:** `@Configuration` + `@Bean` methods (explicit, you write the factory), and `@Component`/`@Service` + `@ComponentScan` (discovery, Spring finds your classes). Both produce identical, container-managed beans.
- **A `@Bean` method is a factory recipe** - it constructs and returns an object the container adopts. You control which implementation, what gets passed in, and any setup before returning.
- **`@Bean` methods are auto-configuration.** Boot's "magic" beans are thousands of `@Bean` methods written by the Spring team. Same mechanism you use by hand - no separate machinery.
- **The rule: `@Component` for code you own, `@Bean` for code you don't.** You can't annotate a third-party class, so a `@Bean` method is the only way to put someone else's object in the container - which is exactly why Boot configures libraries with `@Bean` methods.
- **The `@Configuration` proxy gotcha:** inside a `@Configuration` class, calling one `@Bean` method from another returns the *same singleton* (Spring proxies the class to intercept the call). Put `@Bean` methods on a plain `@Component` and you lose that - you get new objects every call.
- **Conditions turn `@Bean` into auto-config.** `@ConditionalOnClass` and `@ConditionalOnMissingBean` guard Boot's `@Bean` methods so a sensible default appears only when the library is present and you haven't defined your own. Guarded `@Bean` methods *are* auto-configuration.

## Quick check

Make sure the two doors and the proxy gotcha actually stuck:

```quiz
[
  {
    "q": "You need to register a MessageSender that comes from a third-party library you can't modify. Which approach do you use?",
    "choices": [
      "Add @Component to the third-party class",
      "Write a @Bean method in a @Configuration class that constructs and returns it",
      "Nothing - Spring registers all classes on the classpath automatically",
      "Use @ComponentScan pointed at the library's package"
    ],
    "answer": 1,
    "explain": "You can't annotate a class you don't own, so component scanning can't reach it. A @Bean method is the only way to put a third-party object into the container - the same reason Boot's auto-config uses @Bean methods to configure libraries."
  },
  {
    "q": "Inside a @Configuration class, one @Bean method calls another @Bean method directly. What does that inner call return?",
    "choices": [
      "A brand-new object every time the call runs",
      "Null, because @Bean methods can't call each other",
      "The same singleton instance the container already created",
      "A copy of the original bean"
    ],
    "answer": 2,
    "explain": "Spring proxies @Configuration classes and intercepts @Bean method calls, returning the already-created singleton instead of running the method again. (This only holds inside @Configuration - on a plain @Component you'd get a new object each time.)"
  },
  {
    "q": "What makes Spring Boot's auto-configuration different from the @Bean methods you write by hand?",
    "choices": [
      "Auto-config uses a special hidden mechanism unrelated to @Bean",
      "Auto-config @Bean methods are guarded by @Conditional annotations so they only fire when a library is present and you haven't defined your own bean",
      "Auto-config beans are not managed by the container",
      "Auto-config can only register your own classes, not third-party ones"
    ],
    "answer": 1,
    "explain": "Auto-configuration IS plain @Bean methods - the only addition is conditions like @ConditionalOnClass and @ConditionalOnMissingBean, which make each default appear only when appropriate and back off the moment you define your own."
  }
]
```


---

# Dependency Injection, Deep

You already met dependency injection in [Spring Boot From Zero](/guides/spring-boot-from-zero) - a class
asks for what it needs in its constructor, and the container hands it over. That was the happy path: one
bean of each type, everything resolves cleanly. This phase is about what happens when reality gets messier
 - two beans that fit the same slot, optional dependencies, a whole *list* of implementations you want to
loop over. These are the situations where Boot users hit a wall and the error messages stop making sense.

**Injection is the container playing matchmaker.** Your class declares "I need a `MessageSender`," and
Spring goes shopping in its bag of beans for something that fits. Most of this phase is understanding the
*matching rules* - how Spring decides which bean fits, what it does when several fit, and how you can put
your thumb on the scale to pick the winner. Once you can predict those rules, the scary exceptions become
obvious and the fixes become one-liners.

Our cast for the whole phase: a `NotificationService` that needs to send messages, a `MessageSender`
interface, and **two** implementations - `EmailSender` and `SmsSender`. That second implementation is
deliberate: it's what creates the ambiguity that the back half of this phase is all about.

```java
public interface MessageSender {
    void send(String to, String message);
}
```
*What just happened:* one tiny interface - the slot. Anything that implements `MessageSender` can be
dropped into that slot, and `NotificationService` won't know or care which one it got. That's the
substitutability DI is built to give you.

## The three injection styles

There are three ways to get a dependency into a bean. They are not equal - one is recommended, one is
situational, one is discouraged. Let's see all three for `NotificationService`, then make the case.

📝 **Constructor injection** - dependencies arrive as constructor parameters. This is the one to reach for.

```java
import org.springframework.stereotype.Service;

@Service
public class NotificationService {
    private final MessageSender sender;

    public NotificationService(MessageSender sender) {   // Spring supplies one
        this.sender = sender;
    }

    public void notify(String to, String message) {
        sender.send(to, message);
    }
}
```
*What just happened:* `NotificationService` declares its dependency right in the constructor signature.
At startup Spring sees this class needs a `MessageSender`, finds the matching bean, and passes it in. The
field is `final`, so once set it can never change - the object is fully built and valid the instant it
exists. 💡 With exactly **one constructor**, you don't write `@Autowired` on it anymore - Spring uses the
sole constructor for injection automatically. (Older code puts `@Autowired` there; it's redundant now.)

📝 **Setter injection** - the dependency arrives through a setter method after construction.

```java
@Service
public class NotificationService {
    private MessageSender sender;

    @Autowired                       // setter injection
    public void setSender(MessageSender sender) {
        this.sender = sender;
    }
}
```
*What just happened:* Spring constructs the object first, then calls `setSender` to supply the dependency.
The field can't be `final` (it's assigned after construction), and there's a window where the object exists
but `sender` is still `null`. Setter injection earns its keep for genuinely **optional** dependencies - a
dependency the bean can work without - or for things you want to be able to reconfigure later. For required
collaborators, it's the weaker choice.

⚠️ **Field injection** - `@Autowired` straight onto a field. Avoid this.

```java
@Service
public class NotificationService {
    @Autowired                       // ⚠️ field injection - discouraged
    private MessageSender sender;
}
```
*What just happened:* Spring reaches in via reflection and sets the private field directly - no constructor,
no setter. It looks the shortest, and that's the bait. The field can't be `final`, the dependency is
**hidden** (the constructor no longer advertises what this class needs), and it's painful to test: you
can't write `new NotificationService(fake)`; you need reflection or a full Spring context.

**Why constructor injection wins:** it's explicit (the signature is a clear list of every dependency),
immutable (`final` fields, no half-built window), and trivially testable (the constructor *is* the seam - 
`new NotificationService(fakeSender)`, no Spring required). Reach for constructor injection by default;
use setter injection only for optional dependencies; skip field injection in new code.

## How `@Autowired` resolves a bean

When Spring needs to fill an injection point, it follows a definite order. Knowing it turns every wiring
error from a mystery into a lookup.

📝 The resolution order:

1. **By type first.** Spring looks for a bean whose type matches the parameter's type (`MessageSender`).
2. **If exactly one matches** - done, inject it. This is the common case.
3. **If several match** - Spring tries to break the tie **by name**: it compares the parameter/field name
   against the candidate bean names. If one bean's name equals the injection point's name, that one wins.
4. **If still ambiguous** (multiple matches, no name tiebreak) - Spring gives up and throws an error.

Let's make step 3 concrete. Suppose two `MessageSender` beans exist, named `emailSender` and `smsSender`.
This resolves cleanly *because of the parameter name*:

```java
@Service
public class NotificationService {
    private final MessageSender emailSender;   // name matches the bean "emailSender"

    public NotificationService(MessageSender emailSender) {
        this.emailSender = emailSender;
    }
}
```
*What just happened:* two beans match by type, so Spring goes to the name tiebreak. The parameter is named
`emailSender`, and there's a bean named `emailSender` - match. Spring injects that one. Rename the parameter
to `smsSender` and you'd get the SMS bean instead. This name-based fallback is real and easy to trip over:
a rename of a variable can silently change *which* bean you get. It works, but relying on it is fragile - 
the explicit tools in the next section say what you mean out loud.

## The ambiguity problem & fixes

Now the failure that sends Boot users to a search engine. Define both implementations as beans:

```java
@Service
public class EmailSender implements MessageSender {
    public void send(String to, String message) {
        System.out.println("EMAIL -> " + to + ": " + message);
    }
}

@Service
public class SmsSender implements MessageSender {
    public void send(String to, String message) {
        System.out.println("SMS -> " + to + ": " + message);
    }
}
```
*What just happened:* two beans now implement `MessageSender`. If `NotificationService` asks for a plain
`MessageSender` with a parameter name that matches *neither* bean (say, the parameter is just called
`sender`), Spring matches two beans by type, finds no name tiebreak, and refuses to guess:

```console
APPLICATION FAILED TO START

Description:
Parameter 0 of constructor in com.example.NotificationService required a single bean,
but 2 were found:
	- emailSender: defined in file [.../EmailSender.class]
	- smsSender: defined in file [.../SmsSender.class]

Action:
Consider marking one of the beans as @Primary, updating the consumer to accept
multiple beans, or using @Qualifier to identify the bean that should be consumed
```
⚠️ That's `NoUniqueBeanDefinitionException` - "I found more than one and I won't pick for you." Spring even
lists your two fixes. Here they are.

**Fix 1 - `@Primary`: declare a default winner.** Mark one bean as the one to choose when there's a tie.

```java
import org.springframework.context.annotation.Primary;

@Service
@Primary                              // the default MessageSender
public class EmailSender implements MessageSender {
    public void send(String to, String message) {
        System.out.println("EMAIL -> " + to + ": " + message);
    }
}
```
*What just happened:* now an unqualified `MessageSender` injection point resolves to `EmailSender`
everywhere, no other change needed. `@Primary` is for "there's an obvious default, and the exceptions are
rare." It's a property of the *bean*, set once, applied to every injection that doesn't ask for something
more specific.

**Fix 2 - `@Qualifier`: pick explicitly at the injection point.** Name exactly which bean you want.

```java
import org.springframework.beans.factory.annotation.Qualifier;

@Service
public class NotificationService {
    private final MessageSender sender;

    public NotificationService(@Qualifier("emailSender") MessageSender sender) {
        this.sender = sender;
    }
}
```
*What just happened:* `@Qualifier("emailSender")` tells Spring "skip the guessing - inject the bean named
`emailSender`." This is per-injection-point and beats the parameter-name guesswork: it's explicit and
survives renames. The default bean name is the class name with a lowercase first letter (`EmailSender` →
`emailSender`), which is what we're referencing here. 💡 Use `@Primary` for the project-wide default and
`@Qualifier` when a *particular* consumer needs a *particular* implementation - they compose: `@Primary`
sets the fallback, `@Qualifier` overrides it locally.

## Injecting collections & optionals

Here's a pattern most Boot users never realize they have. When several beans share a type, you don't have to
*choose* one - you can ask for **all of them**.

📝 Inject `List<MessageSender>` and Spring hands you every implementation:

```java
import java.util.List;

@Service
public class NotificationService {
    private final List<MessageSender> senders;

    public NotificationService(List<MessageSender> senders) {   // ALL MessageSender beans
        this.senders = senders;
    }

    public void broadcast(String to, String message) {
        for (MessageSender sender : senders) {
            sender.send(to, message);
        }
    }
}
```
*What just happened:* no ambiguity error this time - by asking for a `List<MessageSender>`, you told Spring
"I want the whole collection," so it injects both `EmailSender` and `SmsSender`. Now `broadcast` fires the
message through every channel at once. This is the backbone of the **strategy / plugin pattern**: add a new
`MessageSender` implementation (a `PushSender`, a `SlackSender`) and it joins the list automatically - no
edit to `NotificationService`. The consumer never changes; the set of strategies grows by itself.

💡 Prefer keying by name? Inject `Map<String, MessageSender>` and the keys are the bean names:

```java
import java.util.Map;

@Service
public class NotificationService {
    private final Map<String, MessageSender> sendersByName;   // "emailSender" -> EmailSender, etc.

    public NotificationService(Map<String, MessageSender> sendersByName) {
        this.sendersByName = sendersByName;
    }

    public void sendVia(String channel, String to, String message) {
        sendersByName.get(channel).send(to, message);   // pick a strategy by name at runtime
    }
}
```
*What just happened:* the map's keys are bean names and the values are the beans, so you can select a
strategy at runtime - `sendVia("smsSender", ...)`. Great for dispatching on a config value or a request
parameter.

📝 For a dependency that **might not exist**, you have three options:

```java
import java.util.Optional;

// 1. Optional<T> - empty if no bean exists
public NotificationService(Optional<MessageSender> maybeSender) { ... }

// 2. required = false - leaves the field null if absent
@Autowired(required = false)
private MessageSender sender;

// 3. @Nullable - same idea, on a constructor/setter param
public NotificationService(@Nullable MessageSender sender) { ... }
```
*What just happened:* all three tell Spring "don't fail if this bean is missing." `Optional<T>` is the
cleanest - you get an empty `Optional` instead of a `null` to forget about. Use these only when absence is
genuinely valid; a *required* collaborator should fail loudly at startup, not silently inject `null`.

## `@Value` and the bigger picture

Not everything you inject is a bean - sometimes it's a plain config value. `@Value("${...}")` pulls a value
from your properties (the same `application.properties` Boot uses) straight into a field or parameter.

```java
@Service
public class NotificationService {
    private final MessageSender sender;
    private final String fromAddress;

    public NotificationService(MessageSender sender,
                               @Value("${notifications.from:noreply@acme.com}") String fromAddress) {
        this.sender = sender;
        this.fromAddress = fromAddress;
    }
}
```
*What just happened:* Spring injects the `MessageSender` bean *and* reads `notifications.from` from config
into `fromAddress` in the same constructor. The `:noreply@acme.com` part is a default used when the property
isn't set. Same injection mechanism, different source - beans come from the container, `@Value` comes from
config.

💡 **Dependency injection is the reason the container exists.** You declare what you need *by type*, the
container finds it and supplies it, and because you depend on the `MessageSender` *interface* rather than a
concrete class, you can swap the implementation - real `EmailSender` in production, a recording fake in
tests - **without touching `NotificationService` at all.** Everything in this phase (qualifiers, primaries,
collections, optionals) is just refinements of that one matchmaking act. Next we look at the beans
themselves - how long they live, how many copies exist, and what happens from birth to shutdown.

## Recap

1. **Three injection styles, ranked.** Constructor injection is the default - explicit, immutable (`final`),
   testable. Setter injection suits *optional* dependencies. Field `@Autowired` is discouraged: hidden
   dependencies, no `final`, hard to test. A single constructor needs no `@Autowired`.
2. **`@Autowired` resolves by type first**, then breaks ties by **name** (parameter/field name vs bean
   name), then errors if it still can't choose. Relying on the name tiebreak is fragile.
3. **Two beans of one type → `NoUniqueBeanDefinitionException`.** Fix with `@Primary` (a project-wide
   default winner on the bean) or `@Qualifier("beanName")` (an explicit pick at the injection point); they
   compose.
4. **Inject the whole set.** `List<MessageSender>` gives you every implementation, `Map<String, MessageSender>`
   keys them by bean name - the foundation of the strategy/plugin pattern. New implementations join
   automatically.
5. **Optionals and config.** `Optional<T>`, `@Autowired(required=false)`, and `@Nullable` allow a missing
   bean; `@Value("${...}")` injects config values. DI's payoff: depend on interfaces, swap implementations
   without touching the consumer.

## Quick check

Make sure the matching rules and the ambiguity fixes have stuck:

```quiz
[
  {
    "q": "Why is constructor injection preferred over field @Autowired?",
    "choices": [
      "Dependencies are explicit in the constructor signature, fields can be final/immutable, and the class is trivially testable with `new Service(fake)` - no Spring needed",
      "It runs faster because Spring avoids reflection entirely",
      "Field injection is not supported outside Spring Boot",
      "Only constructor injection can inject interfaces"
    ],
    "answer": 0,
    "explain": "Constructor injection makes every dependency a visible parameter, allows final (immutable, never-null) fields, and lets a test construct the object directly with a fake. Field @Autowired hides dependencies, can't be final, and resists plain testing."
  },
  {
    "q": "Two beans implement MessageSender. You inject a plain `MessageSender` whose parameter name matches neither bean name. What happens?",
    "choices": [
      "Spring throws NoUniqueBeanDefinitionException - it found more than one and won't pick for you",
      "Spring picks the first one it scanned",
      "Spring injects null and logs a warning",
      "Spring merges both into one proxy bean"
    ],
    "answer": 0,
    "explain": "Resolution is by type, then by name. Two beans match by type and the name tiebreak fails, so Spring refuses to guess and throws NoUniqueBeanDefinitionException. Fix it with @Primary or @Qualifier."
  },
  {
    "q": "You want NotificationService to send through every available channel. What do you inject?",
    "choices": [
      "List<MessageSender> - Spring injects all beans of that type, and new implementations join the list automatically",
      "A single @Primary MessageSender and call it in a loop",
      "@Qualifier(\"allSenders\") MessageSender",
      "@Autowired(required=false) MessageSender to get them all"
    ],
    "answer": 0,
    "explain": "Injecting List<MessageSender> (or Map<String, MessageSender>) gives you every implementation - the strategy/plugin pattern. Adding another MessageSender bean later joins the collection with no change to NotificationService."
  }
]
```


---

# Bean Scopes & Lifecycle

[Phase 4](04-dependency-injection-deep.md) was about *which* bean lands in a slot. This phase is about
two questions the container answers underneath that: **how many copies of a bean exist, and how long
does each one live?** Those are the two dials Spring gives you - *scope* (how many) and *lifecycle*
(birth to death) - and almost every confusing Spring behavior, from "why is my data leaking between
requests" to "why does `@Transactional` even work," traces back to one of them.

**A bean isn't an object you made once - it's an object the container keeps on a schedule it controls.**
The container decides how many to build and when to build, initialize, hand out, and tear down each one.
Scope is the "how many" rule; lifecycle is the "when" timeline.

## Scopes: how many instances exist

📝 A **scope** tells the container how many instances of a bean to create and how widely to share them.
There are a handful, but two carry almost all the weight.

📝 **`singleton`** - the **default**. The container builds **exactly one** instance for the whole
container and hands that same instance to *everyone* who asks. Every injection point that wants a
`NotificationService` gets the *same* object. You don't write anything to get this; it's what you've
had all along.

📝 **`prototype`** - the container builds a **brand-new instance every time** the bean is requested.
Ask for it twice, get two distinct objects. Nothing is shared.

The web scopes only exist inside a web application, and each ties a bean's life to a web concept:

- **`request`** - one instance per HTTP request (dies when the request ends).
- **`session`** - one instance per user HTTP session.
- **`application`** - one instance per `ServletContext` (the whole web app).

You declare a non-default scope with `@Scope`:

```java
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

@Service
@Scope("prototype")
public class NotificationService {
    private final MessageSender sender;

    public NotificationService(MessageSender sender) {
        this.sender = sender;
    }
}
```
*What just happened:* `@Scope("prototype")` overrides the default, so the container now mints a fresh
`NotificationService` on every request for one. Without that annotation, `NotificationService` would be
a singleton - the single shared instance you've been getting since Phase 1.

⚠️ The singleton default is the single biggest surprise for newcomers. People picture "I have a
`NotificationService`" as "each part of my app has *its own*." It doesn't. **One** `NotificationService`
serves the entire application - every controller, every thread, every request shares that one object.
That's efficient and almost always what you want. But it has a sharp edge, and it's the next section.

## The singleton thread-safety implication

⚠️ Because a singleton is **one shared instance**, and a server handles many requests **on many threads
at once**, your singleton bean is touched by multiple threads simultaneously. The moment that bean holds
**mutable instance state**, you have a data race: two threads reading and writing the same field with no
coordination.

Here's the trap, made concrete:

```java
@Service
public class NotificationService {        // singleton - ONE instance, MANY threads
    private String lastRecipient;         // ⚠️ shared mutable state across all threads

    public void notify(String to, String message) {
        this.lastRecipient = to;          // thread A writes...
        sender.send(to, message);
        log("sent to " + this.lastRecipient);  // ...thread B may have overwritten it
    }
}
```
*What just happened:* `lastRecipient` looks like a harmless instance field, but because every thread
shares this one bean, thread A can set it to `"ada@..."` and, before A reads it back, thread B sets it
to `"grace@..."`. Now A logs the wrong recipient. There's no exception - just silently corrupted data
under load, the worst kind of bug to chase.

💡 The fix is a discipline, not an annotation: **keep singleton beans stateless.** A bean should hold
its *collaborators* (the injected `MessageSender`, config values) - things set once at startup and never
mutated - and nothing else. Per-call data belongs in **local variables** (each thread gets its own
stack), and per-request or per-user data belongs in a **`request`/`session`-scoped bean** built exactly
for that job. Stateless singletons are automatically thread-safe because there's no shared mutable state
to race over. This is *why* the web scopes exist: they give per-request state a home so your singletons
don't have to (and shouldn't) hold it.

## The prototype gotcha

Now the gotcha that bites people who reach for `prototype` to *avoid* the sharing problem. It's the most
counterintuitive behavior in this phase, so slow down here.

⚠️ **Injecting a prototype bean into a singleton does NOT give you a fresh instance per call.** You get
**one** prototype instance, resolved **once** at injection time, and then reused forever.

```java
@Service                                  // singleton (default)
public class NotificationService {
    private final RequestContext ctx;     // RequestContext is @Scope("prototype")

    public NotificationService(RequestContext ctx) {
        this.ctx = ctx;                   // resolved ONCE, when this singleton was built
    }

    public void notify(String to, String message) {
        ctx.recordCall(to);               // same ctx object every single time ⚠️
    }
}
```
*What just happened:* you marked `RequestContext` as prototype hoping each `notify` call gets a fresh
one. But `NotificationService` is a singleton, built **once** at startup - so its constructor runs once,
its dependencies are resolved **once**, and the prototype it received is frozen into the field. The
"new instance every request" rule fired exactly once (at injection) and never again. Every call reuses
the same `ctx`. The scope didn't do what you expected because **injection happens once, not per
method-call.**

💡 The fix: don't capture the instance - ask the container *each time you need one*. The clean tool is
`ObjectProvider`:

```java
import org.springframework.beans.factory.ObjectProvider;

@Service
public class NotificationService {
    private final ObjectProvider<RequestContext> ctxProvider;

    public NotificationService(ObjectProvider<RequestContext> ctxProvider) {
        this.ctxProvider = ctxProvider;   // a factory, not an instance
    }

    public void notify(String to, String message) {
        RequestContext ctx = ctxProvider.getObject();  // FRESH prototype each call ✅
        ctx.recordCall(to);
    }
}
```
*What just happened:* instead of injecting a `RequestContext`, you injected an `ObjectProvider` - a tiny
factory that calls back into the container. Each `ctxProvider.getObject()` asks for a new prototype, so
now you genuinely get a fresh instance per call. (Older code uses *lookup method injection* via
`@Lookup`, or method-level `@Scope(proxyMode = ...)`; `ObjectProvider` is the modern, readable choice.)
The lesson generalizes: **the lifecycle of an injected bean follows the scope of the bean it's injected
*into*, unless you reach back into the container deliberately.**

## The bean lifecycle

So far "lifecycle" has been a word; now let's make it the concrete sequence the container runs for every
bean. 📝 From birth to readiness:

1. **Instantiate** - the container calls the constructor (this is where constructor injection happens).
2. **Populate dependencies** - setter/field dependencies are filled in.
3. **Aware callbacks** - if the bean implements `*Aware` interfaces (`BeanNameAware`,
   `ApplicationContextAware`, ...), the container hands it those references.
4. **`BeanPostProcessor` - before-init** - every post-processor gets a crack at the bean (next section).
5. **Initialization callbacks** - `@PostConstruct` methods run, then `InitializingBean.afterPropertiesSet()`.
6. **`BeanPostProcessor` - after-init** - post-processors run again (this is where proxies get wrapped).
7. **Ready** - the fully-built bean is in service.
8. **Destruction** - on context shutdown, `@PreDestroy` methods (then `DisposableBean.destroy()`) run.

```mermaid
flowchart TD
  A["Instantiate<br/>(constructor)"] --> B["Populate<br/>dependencies"]
  B --> C["Aware callbacks"]
  C --> D["BeanPostProcessor<br/>before-init"]
  D --> E["@PostConstruct /<br/>InitializingBean"]
  E --> F["BeanPostProcessor<br/>after-init (proxy wrap)"]
  F --> G["Ready - in use"]
  G --> H["@PreDestroy<br/>(on shutdown)"]
```

The hook you'll actually use day-to-day is `@PostConstruct` - a method that runs *after* the bean is
fully constructed and injected, so it's the right place for setup or validation that needs the
dependencies present:

```java
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;

@Service
public class NotificationService {
    private final MessageSender sender;
    private final String fromAddress;

    public NotificationService(MessageSender sender,
                               @Value("${notifications.from:}") String fromAddress) {
        this.sender = sender;
        this.fromAddress = fromAddress;
    }

    @PostConstruct
    void validateConfig() {               // runs once, after injection, before first use
        if (fromAddress.isBlank()) {
            throw new IllegalStateException("notifications.from must be configured");
        }
        System.out.println("NotificationService ready, sending as " + fromAddress);
    }

    @PreDestroy
    void shutdown() {                     // runs on context close
        System.out.println("NotificationService shutting down");
    }
}
```
*What just happened:* `validateConfig` can't be a constructor check that's *fully* trustworthy in every
injection style (with setter/field injection the fields aren't set yet at construction time), so Spring
gives you `@PostConstruct` - guaranteed to run *after* everything is wired. Here it fails fast at startup
if `notifications.from` is missing, turning a silent misconfiguration into a loud, early error.
`@PreDestroy` is the mirror image: cleanup when the container shuts down.

```console
NotificationService ready, sending as noreply@acme.com
... app runs ...
NotificationService shutting down
```
*What just happened:* the init callback fired once during bootstrap (right after injection), and the
destroy callback fired once on shutdown - bookends around the bean's working life. ⚠️ Note: prototype
beans get init callbacks but **not** destroy callbacks - the container hands a prototype off and forgets
it, so it never calls `@PreDestroy`. Cleanup for prototypes is your responsibility.

## `BeanPostProcessor` - the extension point that powers everything

You've now seen "`BeanPostProcessor`" twice in the lifecycle (steps 4 and 6). It's worth understanding,
because **it's the seam Spring itself uses to perform almost all of its magic** - and once you see it,
the magic stops being magic.

📝 A `BeanPostProcessor` is a hook the container calls **for every bean**, twice, around initialization:
once *before* the init callbacks and once *after*. Its two methods receive the bean and can return it
unchanged - or return **something else entirely** in its place.

```java
import org.springframework.beans.factory.config.BeanPostProcessor;

public class LoggingPostProcessor implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String name) {
        return bean;                      // inspect/tweak before @PostConstruct
    }
    public Object postProcessAfterInitialization(Object bean, String name) {
        return bean;                      // return bean - OR a proxy that wraps it
    }
}
```
*What just happened:* the container calls these for `NotificationService`, `EmailSender`, and every
other bean during step 4 and step 6 of the lifecycle. This one's a no-op, but notice the crucial power:
`postProcessAfterInitialization` returns an `Object`. If it returns a *wrapper* instead of the original
bean, the container stores the wrapper - and everyone who injects that bean gets the wrapper, none the
wiser.

💡 That return-a-wrapper trick is **exactly how Spring implements proxies.** When you put
`@Transactional` on a method, a `BeanPostProcessor` spots it at step 6 and returns a **proxy** that opens
a transaction, calls your real method, then commits - in place of your bean. Same mechanism powers
`@Async`, caching, security, and the AOP you'll meet next phase.

So the big realization: **the "magic" annotations aren't magic - they're `BeanPostProcessor`s doing
work at a precise point in the lifecycle.** You now know *where* every annotation plugs in: the container
walks each bean through the timeline, and at steps 4 and 6 it lets these processors inspect, configure,
or wrap it. That's the whole trick. (One practical aside: 💡 `@Lazy` on a bean tells the container to
delay its instantiation until first use instead of building it eagerly at startup - handy for expensive
beans you might never touch, though it trades away fail-fast startup checks for that bean.)

Phase 6 zooms all the way into that "wrap it in a proxy" step - what a proxy is, how Spring builds one,
and why understanding it is the key to `@Transactional` and every cross-cutting concern.

## Recap

1. **Scope = how many.** `singleton` (the **default**) is one shared instance for the whole container;
   `prototype` is a new instance per request; `request`/`session`/`application` tie a bean's life to a
   web concept. Declare non-defaults with `@Scope`.
2. **Singletons are shared across all threads,** so mutable instance state is a data race. Keep
   singletons **stateless** - hold collaborators and config, put per-call data in locals and
   per-request data in a request/session-scoped bean.
3. **Prototype-in-singleton gotcha:** injection happens once, so a singleton captures **one** prototype
   forever. Use `ObjectProvider.getObject()` to fetch a fresh instance per call. An injected bean's
   effective lifetime follows the scope it's injected *into*.
4. **The lifecycle** runs: instantiate → populate → aware → `BeanPostProcessor` before → `@PostConstruct`
   → `BeanPostProcessor` after → ready → `@PreDestroy` on shutdown. Use `@PostConstruct` for setup that
   needs dependencies present (e.g. validating config). Prototypes get init but **not** destroy callbacks.
5. **`BeanPostProcessor`** is the hook the container calls around every bean's initialization, and it can
   return a *replacement* - that's how Spring injects `@Value`, and how it wraps beans in proxies for
   `@Transactional`/AOP. The "magic" annotations are post-processors firing at a known lifecycle moment.

## Quick check

Make sure the two dials - scope and lifecycle - and the gotchas have landed:

```quiz
[
  {
    "q": "What is the default scope of a Spring bean, and what does it mean?",
    "choices": [
      "singleton - the container creates one shared instance for the whole container and gives it to everyone who asks",
      "prototype - a new instance is created for every injection point",
      "request - a new instance per HTTP request",
      "There is no default; you must always declare @Scope"
    ],
    "answer": 0,
    "explain": "singleton is the default: exactly one instance per container, shared everywhere. Because it's shared across all threads, you must keep it stateless to stay thread-safe."
  },
  {
    "q": "You mark RequestContext as @Scope(\"prototype\") and inject it into a singleton NotificationService via the constructor. Each notify() call uses the SAME RequestContext. Why?",
    "choices": [
      "Injection happens once (when the singleton is built), so the prototype is resolved a single time and frozen into the field",
      "Prototype scope is ignored unless the class is annotated @Lazy",
      "Spring caches prototype beans by type after the first call",
      "The constructor is re-run on every method call, but returns a cached object"
    ],
    "answer": 0,
    "explain": "A singleton's constructor runs once at startup, so its dependencies resolve once. The 'new instance per request' rule fired exactly once - at injection. Use ObjectProvider.getObject() to fetch a fresh prototype per call."
  },
  {
    "q": "How does Spring make @Transactional work - wrapping your bean in a proxy that manages the transaction?",
    "choices": [
      "A BeanPostProcessor returns a proxy in place of the bean during the after-initialization step of the lifecycle",
      "The compiler rewrites your method at build time",
      "@PostConstruct opens the transaction when the bean is created",
      "The JVM intercepts every annotated method via bytecode flags"
    ],
    "answer": 0,
    "explain": "BeanPostProcessor.postProcessAfterInitialization can return a replacement object. Spring returns a proxy that opens/commits the transaction around your real method - the same mechanism behind @Async, caching, and AOP."
  }
]
```


---

# Spring AOP & Proxies

This is the phase where the rest of Spring stops being magic. You've used `@Transactional` in
[Spring Boot From Zero](/guides/spring-boot-from-zero) and trusted it to wrap your method in a transaction,
and maybe sprinkled `@Async` on something and watched it run on another thread. They felt like spells - 
annotate a method and behavior appears from nowhere. By the end of this phase you'll know exactly where
that behavior comes from, why it sometimes *doesn't* appear, and why one specific way of calling your own
method silently breaks it.

**Spring never edits your class. When a bean needs extra behavior, Spring hands out a wrapper instead of
the real object - and the wrapper does the extra work before delegating to you.** That wrapper is called a
*proxy*. Once you can see the proxy in the call path, every "why didn't my annotation fire?" answers itself.

We'll keep our cast from [Phase 4](04-dependency-injection-deep.md): a `NotificationService` that sends
messages through a `MessageSender`. This time the interesting part isn't *what* it does - it's the
cross-cutting behavior we want to bolt on without touching its code.

## Cross-cutting concerns

📝 Some behavior doesn't belong to any one method - it cuts *across* many of them. Logging every call.
Timing how long methods take. Starting a database transaction. Checking the user is allowed in. None of that
is the *business logic* of `NotificationService`, yet all of it wants to happen *around* its methods (and
around the methods of a dozen other services too).

The naive approach is to paste it into every method:

```java
public void notify(String to, String message) {
    long start = System.currentTimeMillis();      // timing noise
    System.out.println("ENTER notify");           // logging noise
    try {
        sender.send(to, message);                 // ← the one line that matters
    } finally {
        System.out.println("EXIT notify (" + (System.currentTimeMillis() - start) + "ms)");
    }
}
```

*What just happened:* one line of actual work is buried under four lines of plumbing. Now imagine that same
plumbing copy-pasted into every method of every service. It's noise, it drifts out of sync, and changing the
log format means editing a hundred methods. This repeated, scattered, non-business behavior is what we call a
**cross-cutting concern**.

📝 **Aspect-Oriented Programming (AOP)** is the answer: define that cross-cutting behavior *once*, in one
place, and declare *where* it should apply - instead of pasting it everywhere. Three words you'll need:

- **Aspect** - the module that holds the cross-cutting behavior (the timing/logging code, living in one class).
- **Advice** - the actual code that runs, and *when* it runs (before the method, after it, or around it).
- **Pointcut** - the expression that says *which* methods the advice applies to ("every method in the service package").

## A simple aspect

Here's the timing-and-logging plumbing pulled out of `notify` and turned into an aspect that applies to the
*entire* service package - no edit to `NotificationService` at all.

```java
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TimingAspect {

    // Pointcut: "any method in any class under com.example.service"
    @Around("execution(* com.example.service..*(..))")
    public Object timeIt(ProceedingJoinPoint pjp) throws Throwable {
        String name = pjp.getSignature().toShortString();
        System.out.println("ENTER " + name);
        long start = System.currentTimeMillis();
        try {
            return pjp.proceed();                 // ← run the real method
        } finally {
            long ms = System.currentTimeMillis() - start;
            System.out.println("EXIT  " + name + " (" + ms + "ms)");
        }
    }
}
```

*What just happened:* `@Aspect` marks this class as a holder of advice; `@Component` makes it a bean so
Spring finds it (you'd also enable AOP with `@EnableAspectJAutoProxy` on a `@Configuration` class - Boot does
this for you). The `@Around` advice wraps the *target* method: its `execution(...)` string is the
**pointcut** picking every method under the service package, and `pjp.proceed()` is the moment the real
method actually runs. Everything before `proceed()` happens on the way in; everything in the `finally`
happens on the way out. `NotificationService` knows nothing about any of this.

Call `notificationService.notify("a@b.com", "hi")` and you'd see:

```console
ENTER NotificationService.notify(..)
EMAIL -> a@b.com: hi
EXIT  NotificationService.notify(..) (3ms)
```

*What just happened:* the `EMAIL ->` line is your real method running inside `proceed()`. The `ENTER`/`EXIT`
lines came from the aspect, sandwiched around it - yet `NotificationService.notify` still contains only the
single line that matters. That's AOP's payoff: the concern lives in one class and applies everywhere the
pointcut matches. This is the *readable face* of AOP. Now the question that actually matters: how does code
in one class run "around" a method in a completely different class that never mentions it?

## How it actually works: proxies

Here's the reveal the whole phase is built on. 📝 **Spring does not rewrite `NotificationService`. It does
not inject the aspect into it. Instead, when a bean's methods are matched by some advice, Spring replaces the
bean it hands out with a *proxy* - a generated object that wraps your real bean, intercepts every incoming
call, runs the advice, and then delegates to the real method.**

Remember `BeanPostProcessor` from [Phase 5](05-bean-scopes-and-lifecycle.md)? This is its biggest payoff. A
`BeanPostProcessor` gets a chance to inspect and *replace* each bean after it's created but before it's
handed out. Spring's AOP infrastructure registers exactly such a processor: it sees that
`NotificationService` matches an aspect's pointcut, and in the post-processing step it swaps your raw bean
for a proxy that wraps it. So everywhere `NotificationService` gets injected - into a controller, another
service, a test - what actually lands in that slot is **the proxy**, not your object.

```mermaid
flowchart LR
  Caller[Some caller] -->|notify&#40;&#41;| Proxy[NotificationService proxy]
  Proxy -->|run advice ENTER + timer| Proxy
  Proxy -->|proceed&#40;&#41;| Real[real NotificationService]
  Real -->|return| Proxy
  Proxy -->|run advice EXIT| Caller
```

*What just happened:* the caller thinks it's holding a `NotificationService` and calls `notify()` normally.
The call actually lands on the **proxy**, which runs the advice (log + start timer), calls `proceed()` to
reach your real method, gets the result back, runs the rest of the advice (stop timer + log), and returns.
Your real object never knew it was wrapped. The interception only works because the proxy *is* the thing
everyone holds a reference to.

📝 Spring builds that proxy one of two ways, and which one matters later:

- **JDK dynamic proxy** - used when your bean implements an **interface**. Java's built-in
  `java.lang.reflect.Proxy` creates a new class implementing the *same interface*, forwarding each method to
  the advice and then your bean. The proxy is a sibling implementation of the interface, *not* a subclass of
  your class.
- **CGLIB proxy** - used when there's **no interface** (or Spring is configured to always use it, which Boot
  defaults to). CGLIB generates a runtime **subclass** of your concrete class and overrides each method to
  insert the advice. Because it works by subclassing and overriding, a `final` class or `final` method
  *can't* be proxied this way - there's nothing to override.

💡 The practical takeaway: if you program to interfaces, you might get a JDK proxy that is *not* an instance
of your concrete class (so `(EmailSenderImpl) bean` would fail - cast to the interface instead). With CGLIB
you get a subclass, so concrete-type casts work but `final` blocks proxying. Either way, the object other
beans hold is a stand-in, not your original.

## This is how @Transactional, @Async, and @Cacheable work

💡 Here's the payoff that makes all of Boot's "magic" annotations click at once. **They are all just
advice running in a proxy.** There is nothing else to them.

- **`@Transactional`** - Spring's transaction infrastructure registers advice whose pointcut is "any method
  annotated `@Transactional`." The proxy's advice opens a transaction *before* calling `proceed()`, then
  **commits** if your method returns normally or **rolls back** if it throws. Your method body just does its
  work; the begin/commit/rollback lives entirely in the proxy wrapped around it.
- **`@Async`** - the advice doesn't call `proceed()` on the caller's thread at all. It hands the real method
  off to a thread pool and returns immediately (a `Future`, or nothing). The "it runs on another thread"
  behavior is the proxy choosing *where* to invoke your method.
- **`@Cacheable`** - before calling `proceed()`, the advice checks a cache for the method's arguments. Hit?
  It returns the cached value and never touches your method. Miss? It calls `proceed()` and stores the
  result on the way out.

```java
@Service
public class NotificationService {

    @Transactional                 // advice: begin tx → proceed() → commit / rollback
    public void notifyAndLog(String to, String message) {
        sender.send(to, message);
        auditRepo.save(new AuditRecord(to));   // both writes in one transaction
    }
}
```

*What just happened:* there is no transaction code in this method - and yet both writes commit together or
roll back together. The `@Transactional` proxy is doing it: open a transaction, `proceed()` into your body,
commit on clean return. You're seeing the exact mechanism behind the [service-layer transactions in Spring
Boot](/guides/spring-boot-from-zero). The annotation was never a spell - it was a marker telling Spring's
proxy where to wrap.

## The self-invocation gotcha, explained at the root

Now the famous bug - the one that bites every Spring developer at least once, the one flagged back in the
[Spring Boot service-layer phase](/guides/spring-boot-from-zero). You now have everything you need to
understand *why* it happens, not just that it does.

⚠️ **Calling a `@Transactional` (or `@Async`, or `@Cacheable`) method from another method in the *same
class* does nothing.** No transaction. No new thread. No cache. Silently.

```java
@Service
public class NotificationService {

    public void notifyAll(List<String> recipients) {
        for (String to : recipients) {
            sendOne(to);                 // ⚠️ internal call - bypasses the proxy
        }
    }

    @Transactional
    public void sendOne(String to) {     // expected to run in its own transaction...
        sender.send(to, "hi");
        auditRepo.save(new AuditRecord(to));
    }
}
```

*What just happened:* you'd expect each `sendOne` to run in its own transaction. It doesn't - every call runs
with **no transaction at all**. Here's the root cause, now that you can see the proxy. The advice lives on
the proxy, *not* on your real object. When some outside caller invokes `notifyAll`, that call goes through
the proxy fine. But inside `notifyAll`, the call `sendOne(to)` is really `this.sendOne(to)` - and `this` is
your **real object**, not the proxy. The call goes straight from one method to another inside the same
object; it never leaves through the proxy, so the `@Transactional` advice never gets a chance to run.

```mermaid
flowchart LR
  Caller -->|notifyAll&#40;&#41;| Proxy[proxy]
  Proxy -->|proceed| Real[real bean]
  Real -->|this.sendOne&#40;&#41; - stays inside| Real
```

*What just happened:* the entry to `notifyAll` passes through the proxy, but the inner `this.sendOne()` is an
internal jump that the proxy never sees. No proxy in the path means no advice - and `@Transactional` *is*
advice. This is the whole gotcha in one sentence: **advice only fires when the call crosses the proxy
boundary, and `this.method()` never crosses it.**

The fixes all share one idea - make the call go *through* the proxy:

**Fix 1 - split into another bean.** Move the annotated method to a separate bean and inject it. Now the call
crosses a real bean boundary, which means it goes through *that* bean's proxy.

```java
@Service
public class NotificationService {
    private final SingleSender singleSender;   // injected: this is the PROXY of SingleSender

    public NotificationService(SingleSender singleSender) {
        this.singleSender = singleSender;
    }

    public void notifyAll(List<String> recipients) {
        for (String to : recipients) {
            singleSender.sendOne(to);          // ✅ goes through SingleSender's proxy
        }
    }
}

@Service
public class SingleSender {
    @Transactional
    public void sendOne(String to) { /* ... */ }
}
```

*What just happened:* `singleSender` is injected, so it's the *proxy* of `SingleSender`. Calling
`singleSender.sendOne(to)` is an external call that crosses the proxy boundary, so the `@Transactional`
advice fires and each call gets its own transaction. This is usually the cleanest fix - and it often reveals
a sensible separation of responsibilities you were missing anyway.

**Fix 2 - self-inject the proxy.** If you really want the method to stay in the same class, inject the bean
into *itself* and call through that reference instead of `this`.

```java
@Service
public class NotificationService {
    private final NotificationService self;    // the PROXY of this very bean

    public NotificationService(@Lazy NotificationService self) {
        this.self = self;
    }

    public void notifyAll(List<String> recipients) {
        for (String to : recipients) {
            self.sendOne(to);                  // ✅ through the proxy, not this
        }
    }

    @Transactional
    public void sendOne(String to) { /* ... */ }
}
```

*What just happened:* `self` is the proxy-wrapped version of this same bean (`@Lazy` breaks the chicken-and-egg
of injecting a bean into its own constructor). Calling `self.sendOne(to)` routes through the proxy, so the
advice runs. It works, but it's a bit of a head-scratcher to read - most teams prefer Fix 1.

💡 Every "why didn't my annotation fire?" in Spring has the same shape: *was there a proxy in the call
path?* Private methods can't be advised (the proxy can't override them). Internal `this.` calls skip
advice. A `new SomeService()` you constructed yourself has no proxy at all. Once you picture the proxy
standing between caller and bean, the whole category of mysteries collapses into one question.

## Recap

1. **Cross-cutting concerns** - logging, timing, transactions, security - repeat across many methods and
   aren't business logic. AOP lets you define them once and declare *where* they apply, instead of pasting
   them everywhere.
2. **Aspect / advice / pointcut.** An *aspect* holds the behavior, *advice* is the code that runs and when
   (`@Around` wraps the method via `proceed()`), and a *pointcut* (`execution(...)`) selects which methods it
   applies to.
3. **Proxies are the mechanism.** Spring never edits your class. A `BeanPostProcessor` swaps your bean for a
   *proxy* that intercepts calls, runs the advice, then delegates to your real method. The object injected
   everywhere is the proxy.
4. **JDK dynamic proxy vs CGLIB.** JDK proxies wrap a bean that implements an *interface* (sibling
   implementation); CGLIB *subclasses* a concrete class (so `final` classes/methods can't be proxied). Boot
   defaults to CGLIB.
5. **`@Transactional`, `@Async`, `@Cacheable` are all just advice in a proxy.** The proxy begins/commits a
   transaction, dispatches to a thread pool, or checks a cache *around* your untouched method body. That's
   the whole "magic."
6. **Self-invocation skips it.** ⚠️ Advice fires only when a call crosses the proxy boundary. `this.method()`
   stays inside the real object and never reaches the proxy, so the annotation silently does nothing. Fix by
   splitting into another bean, or self-injecting the proxy.

## Quick check

Make sure the proxy mental model and its famous gotcha have stuck:

```quiz
[
  {
    "q": "How does Spring make @Around advice run around a method in a class that doesn't mention the aspect at all?",
    "choices": [
      "A BeanPostProcessor replaces the bean with a proxy that intercepts calls, runs the advice, then delegates to the real method",
      "Spring rewrites your class's bytecode at compile time to paste the advice into each method",
      "Spring injects the aspect as a field into your class and calls it from each method",
      "The advice runs on a separate thread that watches your method execute"
    ],
    "answer": 0,
    "explain": "Spring doesn't edit your class. Its AOP BeanPostProcessor swaps your bean for a proxy that wraps it; the proxy runs the advice and calls proceed() to reach your real method. The proxy is what every other bean actually holds."
  },
  {
    "q": "Method a() and @Transactional method b() are in the same bean. a() calls this.b(). What transaction does b() run in?",
    "choices": [
      "None - this.b() stays inside the real object and never crosses the proxy boundary, so the @Transactional advice never fires",
      "Its own new transaction, exactly as if called from outside",
      "The same transaction a() is already in, because they share an object",
      "It throws an exception because @Transactional methods can't be called internally"
    ],
    "answer": 0,
    "explain": "The advice lives on the proxy, not the real object. An internal this.b() call goes object-to-object without leaving through the proxy, so no advice runs and b() executes with no transaction at all. Fix it by calling across a bean boundary (or a self-injected proxy)."
  },
  {
    "q": "When does Spring use a CGLIB proxy instead of a JDK dynamic proxy?",
    "choices": [
      "When the bean has no interface to implement (or Spring is configured to always use CGLIB) - it generates a runtime subclass, so final classes/methods can't be proxied",
      "When the bean implements at least one interface",
      "Only for @Async methods; @Transactional always uses JDK proxies",
      "Never - Spring only supports JDK dynamic proxies"
    ],
    "answer": 0,
    "explain": "JDK dynamic proxies require an interface (they create a sibling implementation). With no interface - or when forced, which Boot defaults to - Spring uses CGLIB, which subclasses your concrete class and overrides its methods. Because it overrides, final classes and final methods can't be proxied."
  }
]
```


---

# Spring MVC Without Boot

In [Phase 6](06-spring-aop-and-proxies.md) you saw how Spring wraps your beans in proxies to add behavior without touching your code. That was the last piece of *core* Spring's machinery. Now we point that machinery at the web - and here's the thing worth holding onto from the start: **the controllers you wrote in Spring Boot were never the magic.** They were ordinary classes the whole time. What Boot quietly did was stand up the *plumbing* around them. This phase rebuilds that plumbing by hand so you can see exactly what was happening behind the curtain.

**Every Spring web app has one object at its front door that catches every single request and decides where it goes.** That object is the `DispatcherServlet`. Once you understand it, the rest of Spring MVC stops feeling like a pile of annotations and starts feeling like one clean idea.

(One scoping note: Spring MVC is built *on top of* the Java Servlet API - the lower-level contract for how a Java web server hands HTTP requests to your code. That layer is its own roots topic; here we sit one floor up and treat "a request arrives" as the starting point.)

## The DispatcherServlet - the front controller

📝 Spring MVC is built around a single servlet called the **`DispatcherServlet`**. It receives *every* HTTP request that comes into your app, no matter the URL, and its only job is to route that request to the right method on the right controller. This is a classic design pattern called the **front controller**: instead of scattering "which request goes where" logic across many entry points, you funnel everything through one place that owns the routing.

Think of it like the front desk of a large office building. Visitors don't wander the halls looking for the right room - they all stop at one desk, state who they're there to see, and get directed. The `DispatcherServlet` is that desk for HTTP.

Here's the path a request takes once it reaches it:

```mermaid
flowchart TD
  A[HTTP request arrives] --> B[DispatcherServlet]
  B --> C[HandlerMapping: which controller method?]
  C --> D[HandlerAdapter: bind inputs and call it]
  D --> E[Your controller method runs]
  E --> F[Return value to response]
  F --> G[HTTP response sent back]
```

📝 Notice that *you* only own one box in that diagram - the controller method. Everything else is infrastructure Spring provides. Your code is the answer to "what should happen for this request"; the `DispatcherServlet` and friends handle "how did this request get here and how does the answer get back out."

💡 If `DispatcherServlet` sounds familiar, it should - it was the one object in the [Spring Boot REST controllers phase](/guides/spring-boot-from-zero) that you "never wrote." Boot registered it for you. We're about to write that registration ourselves.

## Wiring it by hand

Without Boot, nobody starts a web server for you and nobody registers the `DispatcherServlet`. You do both. There are three historical ways to do this - an old-school `web.xml` file, a programmatic `WebApplicationInitializer`, and the convenience base class `AbstractAnnotationConfigDispatcherServletInitializer` that wraps the common case. We'll use the last one, because it's the clearest expression of the idea.

First, a web configuration class that turns on Spring MVC:

```java
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.example.web")
public class WebConfig {
    // Empty for now - @EnableWebMvc brings in sensible defaults.
}
```

*What just happened:* `@Configuration` marks this as a bean-definition class (the same kind you met back in [Phase 3](03-defining-beans.md)). `@EnableWebMvc` is the important one: it tells Spring to register the whole standard set of MVC infrastructure beans - the handler mappings, the JSON converters, all of it - into this context. `@ComponentScan` points Spring at the package where your controllers live so it can find and register them as beans. An empty class body is fine; the annotations do the work.

Now the piece that registers the `DispatcherServlet` itself and maps it to URLs:

```java
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class AppInitializer
        extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;   // no separate root context for this small app
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { WebConfig.class };   // the web context
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };   // DispatcherServlet handles every URL
    }
}
```

*What just happened:* When your app starts, the servlet container finds this initializer and calls it. `getServletConfigClasses()` hands it your `WebConfig`, so the `DispatcherServlet` builds a Spring context from it. `getServletMappings()` returns `"/"`, which means "route *all* incoming URLs through this servlet" - that's what makes it the front controller for the whole app. You have now done, in about fifteen lines, the registration that Boot performs invisibly.

💡 This is the reveal in miniature: **everything in these two classes is what Spring Boot's web auto-configuration generates for you, plus the embedded Tomcat that Boot also starts to run it all.** Without Boot you'd deploy this into a servlet container (like a standalone Tomcat) yourself. With Boot, the server is embedded and the config is automatic - but it's the same `DispatcherServlet`, the same `@EnableWebMvc` defaults, underneath.

## Controllers - exactly what you already wrote

Here is the part that should feel anticlimactic, and that's the point.

```java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingController {

    @GetMapping("/greeting/{name}")
    public Greeting greet(@PathVariable String name) {
        return new Greeting("Hello, " + name + "!");
    }
}
```

*What just happened:* This is identical to the controller code from the [Spring Boot guide](/guides/spring-boot-from-zero) - same `@RestController`, same `@GetMapping`, same `@PathVariable`. Spring finds it via the `@ComponentScan` in `WebConfig`, registers it as a bean, and the `DispatcherServlet` routes `GET /greeting/Ada` to `greet`, pulling `Ada` out of the path. The return value, a plain `Greeting` object, gets serialized to JSON automatically.

A request and its response:

```http
GET /greeting/Ada HTTP/1.1
Host: localhost:8080
```

```json
{ "message": "Hello, Ada!" }
```

*What just happened:* The `Greeting` object came back as JSON, field by field - the same Jackson serialization you saw in Boot. ⚠️ The crucial observation: **not one line of this controller changed between Boot and no-Boot.** Boot never modified how you write MVC code. It only configured the surroundings. The controller was always plain Spring.

## The pieces the DispatcherServlet orchestrates

When a request lands, the `DispatcherServlet` doesn't do everything itself - it delegates to a small cast of specialist beans, each registered by that `@EnableWebMvc` you added. You rarely touch these directly, but knowing their names demystifies the whole flow:

- **HandlerMapping** - answers "which controller method handles this URL and HTTP method?" It's the lookup table built from all your `@GetMapping`/`@PostMapping` annotations.
- **HandlerAdapter** - actually *invokes* the chosen method, binding the path variables, query params, and request body into your method's parameters first.
- **Message converters** - translate between Java objects and the wire format. For a JSON API this is Jackson, turning your return value into JSON and an incoming body into an object.
- **View resolvers** - for apps that render HTML templates rather than JSON, these map a returned view name to an actual template file. (A pure REST API skips these.)
- **Exception resolvers** - catch exceptions thrown in your controllers and turn them into proper HTTP error responses.

📝 All of these are just beans in the web context. `@EnableWebMvc` registers a sensible default of each. Boot's auto-configuration registers the *same* defaults plus a few extras (and steps back the moment you define your own) - which is why a Boot REST app "just works" with Jackson and error handling you never configured.

## The reveal

Step back and put it all together. In the Boot guide, the entire web setup was: add the web starter dependency, write a controller, run the app. That felt like almost nothing. Here's what those three steps actually were:

1. **The web starter** → started an embedded Tomcat to receive HTTP, and registered the `DispatcherServlet` as the front controller. *(You just did this with `AppInitializer`.)*
2. **`@EnableWebMvc`-equivalent auto-config** → registered all the MVC beans: handler mappings, Jackson converters, exception resolvers. *(You just did this with `WebConfig`.)*
3. **Your controller** → the one part that was never magic, identical in both worlds.

💡 **Spring Boot's "web magic" is two things you've now built by hand - a server and a `DispatcherServlet` - plus a pile of default beans you've now seen by name.** Boot didn't invent a new web framework; it pre-wired *this* one. That's the pattern for everything Boot does, and it's exactly where the final phase picks up.

## Recap

1. **The `DispatcherServlet` is the front controller.** One servlet catches every request and routes it to the right controller method - the front-controller pattern. You own only the controller method; everything else is infrastructure.
2. **Without Boot you wire the web layer yourself.** A `@Configuration` class with `@EnableWebMvc` registers the MVC beans, and a `WebApplicationInitializer` (or `AbstractAnnotationConfigDispatcherServletInitializer`) registers the `DispatcherServlet` and maps it to `/`.
3. **Controllers are unchanged from Boot.** `@RestController`, `@GetMapping`, `@PathVariable`, `@RequestBody` work identically - Boot configured MVC, it didn't change how you write it.
4. **The DispatcherServlet delegates to specialist beans.** HandlerMapping picks the method, HandlerAdapter invokes it, message converters handle JSON, view resolvers handle templates, exception resolvers handle errors - all registered by `@EnableWebMvc`.
5. **Boot's web magic = embedded server + DispatcherServlet + default MVC beans.** You just built the second and third by hand; the controller was never the magic part.

## Quick check

Test the front-controller picture before moving on:

```quiz
[
  {
    "q": "What is the role of the DispatcherServlet in a Spring MVC application?",
    "choices": [
      "It is the front controller - one servlet that receives every request and routes it to the correct controller method",
      "It is the database connection pool that controllers use to query data",
      "It is the JSON library that serializes return values into response bodies",
      "It is one controller method per URL that you write yourself"
    ],
    "answer": 0,
    "explain": "The DispatcherServlet implements the front-controller pattern: a single servlet that catches every incoming request and dispatches it to the matching controller method. JSON serialization is done by message converters (Jackson), and you write the controller methods it routes to."
  },
  {
    "q": "In a non-Boot Spring MVC app, what does @EnableWebMvc on your @Configuration class do?",
    "choices": [
      "Registers the standard set of MVC infrastructure beans (handler mappings, message converters, etc.) into the web context",
      "Starts an embedded Tomcat server to listen for HTTP requests",
      "Replaces the need to write any controllers at all",
      "Connects the application to a database automatically"
    ],
    "answer": 0,
    "explain": "@EnableWebMvc brings in Spring MVC's default infrastructure beans - HandlerMapping, HandlerAdapter, message converters, and so on. It does not start a server (that's the container's job, or embedded Tomcat under Boot), and you still write your own controllers."
  },
  {
    "q": "Comparing a Boot REST app to the hand-wired version in this phase, what is true about the controller code itself?",
    "choices": [
      "It is identical - Boot configured MVC for you but never changed how controllers are written",
      "Boot controllers use special annotations that don't exist in core Spring",
      "The hand-wired version requires you to parse HTTP and serialize JSON manually",
      "Boot controllers must extend the DispatcherServlet class"
    ],
    "answer": 0,
    "explain": "The controller code is exactly the same in both worlds - same @RestController, @GetMapping, @PathVariable. Boot's contribution was the surrounding plumbing (embedded server, DispatcherServlet, default MVC beans), not the controller itself."
  }
]
```


---

# From Core Spring to Spring Boot

Think back to where you started. Spring Boot felt like a black box - you sprinkled some annotations, ran `main`, and somehow a web server appeared, beans wired themselves up, and a database connection materialized out of thin air. It worked, but you couldn't have told anyone *why*.

Look at what you can do now. You can stand up an `ApplicationContext` by hand. You can declare beans with `@Configuration` and `@Bean`, or let component scanning find them. You can wire dependencies through the constructor and explain exactly how `@Autowired` resolves them. You understand singleton versus prototype scope and the full bean lifecycle, callbacks and all. You know that `@Transactional` and `@Async` work because Spring wraps your bean in a **proxy** - and you know the self-invocation gotcha that trips up developers who *don't* know that. You can wire a `DispatcherServlet` and a `@Controller` without Boot anywhere in sight.

That's the whole machine, and you've had your hands inside it. This final phase isn't new material - it's the payoff. We're going to assemble everything you've learned into one sentence that makes Spring Boot stop being magic forever.

## The full equation, assembled

💡 Here's the headline, and it's worth reading slowly:

**Spring Boot = core Spring (everything in this guide) + auto-configuration + starters + an embedded server.**

That's it. There's no fourth secret ingredient. Every piece on the right-hand side is something you can now name and explain:

- **Core Spring** - the IoC container, beans, dependency injection, scopes, lifecycle, AOP, MVC. The engine. You built this by hand.
- **Auto-configuration** - conditional `@Bean` methods. Boot ships hundreds of `@Configuration` classes whose `@Bean` definitions only fire *if certain conditions are met* (a class is on the classpath, a property is set, you haven't already defined that bean yourself). It's exactly the `@Bean` work from [Phase 3](03-defining-beans.md) - written ahead of time, gated by conditions.
- **Starters** - curated dependency bundles. `spring-boot-starter-web` isn't code; it's a `pom.xml` that pulls in a coherent set of jars (Spring MVC, Jackson, an embedded server, validation) that are known to work together. It saves you from hand-picking compatible versions.
- **Embedded server** - instead of building a WAR and deploying it into an external Tomcat, Boot starts Tomcat *inside* your `main` method. Same servlet container you met in [Phase 7](07-spring-mvc-without-boot.md), started for you in-process.

And the annotation that ties the bow? `@SpringBootApplication` is three annotations you already know stacked together: `@Configuration` + `@ComponentScan` + `@EnableAutoConfiguration`. Configure beans, scan for components, switch on the conditional auto-config. Nothing you haven't done by hand.

```mermaid
flowchart LR
  CS[Core Spring<br/>container, beans, DI, AOP, MVC] --> SB[Spring Boot]
  AC[Auto-configuration<br/>conditional @Bean methods] --> SB
  ST[Starters<br/>curated dependency bundles] --> SB
  ES[Embedded server<br/>Tomcat in main] --> SB
```

*What this shows:* four inputs, one output. Boot is an assembly, not an invention. You now understand every input.

## What you can do that Boot-only developers can't

💡 This is the real prize, and it's the difference between using Spring and *understanding* it.

When a Boot-only developer hits a wiring failure - `NoSuchBeanDefinitionException`, or two beans of the same type and no `@Primary` - they're stuck staring at a stack trace that reads like a foreign language. You can read it. You know what the container was trying to do, where it looks for candidates, and why it gave up. You can debug the wiring because you've *done* the wiring.

A confusing stack trace full of `$$EnhancerBySpringCGLIB$$` frames? You know that's a proxy, you know why it's there, and you know to read past it to your actual code. An annotation that mysteriously *didn't* fire - the classic "my `@Transactional` method isn't rolling back"? You'll spot the self-invocation immediately, because you understand the call has to go *through* the proxy and an internal `this.method()` call never does.

And when the defaults aren't right, you can override auto-config - define your own bean and Boot's conditional one quietly steps aside (`@ConditionalOnMissingBean`), exactly as designed. You know when to lean on Boot and when to drop down to manual configuration, because you can see both layers at once.

That's senior-level Spring fluency. Not "I memorized more annotations" - "I understand the mechanism, so I can reason about anything the framework does."

## Where to go from here

A few straightforward directions, depending on what you want next:

- **Re-read [Spring Boot From Zero](/guides/spring-boot-from-zero) with new eyes.** This is the single highest-leverage thing you can do. Every annotation in that guide now has a visible mechanism underneath it. `@RestController`? A `@Controller` plus `@ResponseBody`, registered as a bean, dispatched by a `DispatcherServlet` you can now picture. Auto-configured `DataSource`? A conditional `@Bean`. The fog won't just thin - it'll lift.
- **Go one layer deeper: the Servlet API.** Underneath `DispatcherServlet` is the raw Java servlet model - `HttpServletRequest`, `HttpServletResponse`, filters, the servlet container contract. It's the foundation the whole web side of Spring stands on. You don't *need* it day to day, but knowing it exists (and roughly how it works) completes the picture from the metal up.
- **Explore Spring's ecosystem.** Spring Security, Spring Data, Spring Cloud, Spring for GraphQL - they're all built on the core you now understand. Filters and proxies (Security), the repository abstraction and its lifecycle (Data), beans and config wired across services (Cloud). The core isn't a prerequisite you'll outgrow; it's the foundation every one of them rests on.

## What to build - and a last word

📝 Reading got you here. Building is what locks it in. Two concrete suggestions, either of which will cement everything:

- **Take a Boot app and rebuild a slice of it without Boot.** Pick a tiny endpoint or service from something you've built with Boot. Now stand up the same thing with a manual `ApplicationContext`, hand-written `@Configuration`, and your own `DispatcherServlet` wiring. It's more typing - that's the point. Every line you write by hand is a line you'll *recognize* the next time Boot writes it for you. Nothing maps the two layers together like doing it once.
- **Write a tiny AOP aspect and watch it intercept.** Define an aspect that logs around a method call, register it, and step through with a debugger. Watching the proxy receive the call, run your advice, and *then* delegate to your real bean turns "proxies are how `@Transactional` works" from a fact you read into a thing you've seen with your own eyes.

When you want the authoritative answer on anything, the **Spring Framework reference documentation** is genuinely excellent - it explains the *why* behind the behavior, which is the habit that separates people who fight the framework from people who work with it.

The through-line of this whole guide: **Boot was never magic - it's the core Spring you now understand, configured for you.** You're leaving able to read what's underneath every annotation, assemble a Spring app from scratch, and reason about it when it breaks. Go build the small thing, and watch the magic stay gone.

## Recap

1. **Spring Boot = core Spring + auto-configuration + starters + an embedded server.** Four inputs you can now name and explain - Boot is an assembly, not an invention.
2. **Each input maps to something you learned:** auto-config is conditional `@Bean` methods; starters are curated dependency bundles; the embedded server is Tomcat started in `main`; `@SpringBootApplication` = `@Configuration` + `@ComponentScan` + `@EnableAutoConfiguration`.
3. **You can now do what Boot-only devs can't** - debug wiring failures, read proxy-laden stack traces, diagnose why an annotation didn't fire (self-invocation), override auto-config, and know when to drop to manual config.
4. **Re-read the [Spring Boot guide](/guides/spring-boot-from-zero) next** - every annotation now has a visible mechanism. Then go deeper (the Servlet API) or wider (Security, Data, Cloud), all built on this same core.
5. **Build to cement it:** rebuild a slice of a Boot app *without* Boot, or write a tiny AOP aspect and watch the proxy intercept. The reference docs are your authoritative source.

## Quick check

One last check - the ideas that turn Boot from magic into mechanism:

```quiz
[
  {
    "q": "What does the equation 'Spring Boot = ...' actually add on top of core Spring?",
    "choices": [
      "Auto-configuration, starters, and an embedded server",
      "A brand-new IoC container that replaces the core one",
      "A different dependency injection system unrelated to core Spring",
      "Nothing - Boot and core Spring are completely separate frameworks"
    ],
    "answer": 0,
    "explain": "Boot is core Spring plus three things: auto-configuration (conditional @Bean methods), starters (curated dependency bundles), and an embedded server (Tomcat started in main). The container, beans, DI, and MVC underneath are exactly the core Spring you learned."
  },
  {
    "q": "Spring Boot's auto-configuration is, mechanically, mostly what?",
    "choices": [
      "Conditional @Bean methods that fire only when certain conditions are met",
      "Runtime bytecode generation with no configuration classes involved",
      "A separate scripting language Boot interprets at startup",
      "Hard-coded beans that can never be overridden"
    ],
    "answer": 0,
    "explain": "Auto-config is the same @Bean work from Phase 3, written ahead of time and gated by conditions (a class on the classpath, a property set, or - via @ConditionalOnMissingBean - you not having already defined that bean). That's why defining your own bean cleanly overrides Boot's."
  },
  {
    "q": "What does @SpringBootApplication decompose into?",
    "choices": [
      "@Configuration + @ComponentScan + @EnableAutoConfiguration",
      "@Controller + @Service + @Repository",
      "@Autowired + @Qualifier + @Primary",
      "@Transactional + @Async + @Scope"
    ],
    "answer": 0,
    "explain": "It's three annotations you already know stacked together: configure beans (@Configuration), scan for components (@ComponentScan), and switch on the conditional auto-config (@EnableAutoConfiguration). Nothing you haven't done by hand."
  }
]
```
