What Spring Really Is: From the Pain of EJB to the Birth of IoC

bee2026-10-0834 min read0 views
Why does a 2003 framework still rule Java? From the lessons of EJB to an intuitive explanation of IoC, the Spring family map, and the real difference between Spring and Spring Boot.
1 / 133
Section
0. Thirty seconds to get it
2 / 133

In one line: Spring is a container that builds objects for you, hands them to you, and manages them. Before it, whenever a Java class needed another, you wrote new. After it, you write only "I need something that can query orders", and everything else — where it comes from, how it is assembled, when it is destroyed — is arranged by the container. This article explains why it grew this shape: in 2003 the Java world was suffocating under EJB ceremony, Rod Johnson proved in a book that "plain Java classes plus dependency injection" could do the same job, Spring was born from that code, and later grew Boot (which removes configuration) and Cloud (which coordinates many services).

3 / 133
类比

**ordering takeout is inversion of control. You want a plate of tomato-and-egg stir-fry. The manual way is to shop, wash, chop and cook — you are both the diner and the kitchen. Ordering takeout only requires placing an order (declaring "I want this dish"); the wok, the ingredients and the packaging are the restaurant's (the container's) decision. The day they change chef, pan or box, your order does not change by a single word. That is exactly what "inversion" inverts: initiative over cooking moves from your hands to theirs.**

4 / 133
Diagram
Figure · Three steps: Spring → Boot → Cloud
Figure · Three steps: Spring → Boot → Cloud
5 / 133

After this article you should be able to answer three questions:

6 / 133
  1. What made EJB so painful, and why is "non-invasive" Spring's founding creed?
  2. "Inversion of control" sounds mystical — which four things actually get inverted, and how does that differ from "dependency injection"?
  3. If I already use Spring Boot, why go back and learn the Spring container?
7 / 133
Section
1. Back to the early 2000s
8 / 133

To understand why Spring was great, you have to know what it rescued us from. Twenty years ago the standard answer for Java enterprise development was EJB (Enterprise JavaBeans). It looked respectable and was painful to write:

9 / 133
java
// Typical EJB 2.x style: implement container interfaces, write a pile of callbackspublic class OrderServiceBean implements SessionBean {    private SessionContext context;    // Only the last line is business logic; the rest is container ceremony    public void ejbCreate() {}    public void ejbActivate() {}    public void ejbPassivate() {}    public void ejbRemove() {}    public void setSessionContext(SessionContext ctx) { this.context = ctx; }    public BigDecimal total(Order order) {        return order.getItems().stream()                .map(Item::getPrice)                .reduce(BigDecimal.ZERO, BigDecimal::add);    }}
10 / 133

And to use this bean, you cannot call new — you must look it up via JNDI:

11 / 133
java
// To reuse it elsewhere, look it up in the directory service and handle checked exceptionsContext ctx = new InitialContext();OrderServiceHome home = (OrderServiceHome) ctx.lookup("java:comp/env/ejb/OrderService");OrderService service = home.create();
12 / 133

Three fatal problems stand out today:

13 / 133
  • Heavy intrusion: business classes had to implement container interfaces, welding code to the runtime environment
  • Deploy to test: without a heavyweight EJB container nothing ran — unit testing was nearly impossible
  • Boilerplate drowned the business: five lines of logic needed fifty lines of ceremony
14 / 133
Trap

EJB's greatest sin was not "being complex" but complicating the simple. A method that only "sums a total" got wrapped in container interfaces, lifecycle callbacks and remote lookups — exactly the target Rod Johnson later attacked.

15 / 133
Section
2. One programmer's counterattack on complexity
16 / 133
Diagram
Figure 1 · Twenty years of Java enterprise
Figure 1 · Twenty years of Java enterprise
17 / 133

Cut the same timeline by "who does what for you" and you get the zoomed-in version of the map at the top of this article: Spring owns wiring, Boot owns configuration, Cloud owns service-to-service concerns:

18 / 133
Diagram
Figure · Three steps: Spring → Boot → Cloud and their division of labour
Figure · Three steps: Spring → Boot → Cloud and their division of labour
19 / 133

In 2002 the Australian developer Rod Johnson wrote Expert One-on-One J2EE Design and Development. The book did not teach "how to use EJB better"; it did something bolder: argue that many situations do not need EJB at all.

20 / 133

It shipped roughly 30,000 lines of example code that delivered EJB's core capabilities using plain Java objects (POJOs) plus dependency injection. Readers kept asking where that code was and how to reuse it — and it evolved into the Spring Framework, open-sourced in 2003.

21 / 133

The most memorable point is this: Spring did not start by "inventing new technology" but by "solving enterprise problems with ordinary Java classes". Its creed is non-invasiveness: your business class is a plain class, extends nothing, runs inside Spring, still runs without Spring, and can be tested with plain JUnit any time.

22 / 133
Note

the name Spring comes from the idea of "bringing spring to J2EE". Its tagline back then was "J2EE without EJB" — getting EJB's power the lightweight way.

23 / 133
Section
3. An intuitive explanation of IoC
24 / 133

Spring's core is called IoC (Inversion of Control), a term that scares off countless beginners. What it describes is very plain.

25 / 133

A metaphor: you are making tomato-and-egg stir-fry. The traditional way is to shop, wash, chop, and only then cook — you worry about getting the ingredients. The IoC way: you write only the recipe (declare "I need eggs and tomatoes"), and the kitchen (the container) delivers prepared ingredients to your side, so you focus on cooking.

26 / 133

In code, look first at the "new it yourself" world:

27 / 133
java
// Bad: the dependency is hard-coded inside the classpublic class OrderController {    // This class decides which implementation to use; business code is hostage to the detail    private final OrderService service = new OrderServiceImpl(new MysqlOrderRepository());    public BigDecimal checkout(Long orderId) {        return service.total(orderId);    }}
28 / 133

The problem is obvious: to switch to RedisOrderRepository you edit OrderController itself. To inject a fake service in a test? Impossible — it hard-codes new.

29 / 133

Now the IoC world:

30 / 133
Code
Codejava
// Good: declare only "I need an OrderService"; who provides it is not my concern@Servicepublic class OrderController {    private final OrderService service;    // The constructor is a contract: the container sees it and injects an OrderService    public OrderController(OrderService service) {        this.service = service;    }    public BigDecimal checkout(Long orderId) {        return service.total(orderId);    }}
Notes
  • There is no new in the class, hence no decision about which implementation
  • That decision moves to the container, which reads your constructor and injects the right implementation
  • Result: to swap the implementation, just have another OrderService bean; to test, simply new OrderController(mockService)
31 / 133

That is what "inversion" means: once you stop calling new and instead declare a dependency for someone else to supply, control over object creation has left your hands.

32 / 133

The wall between the jargon and its plain meaning is real, because nobody explains it. Play a round: the left column is what documentation says, the right column is what you will actually see at an incident scene — a wrong pick explains the gap immediately:

33 / 133
Match
MatchSpring jargon ↔ what it means at a crash sceneMatched 0/6 · Missed 0
Six terms you meet in articles 1 to 3. Both columns are shuffled, so positions tell you nothing
Pick a card on the left first
34 / 133
Section
4. What exactly gets inverted
35 / 133
Animation
Animation · Before and after inversion
Animation · Before and after inversion
36 / 133

"Inversion of control" sounds abstract until you split it into four observable dimensions:

37 / 133
Table
DimensionBefore (self new)After (container injection)
CreationThe consumer calls newThe container instantiates
WiringThe concrete implementation is hard-codedThe container wires per declaration
LifecycleThe consumer owns itThe container manages it (singleton, destroy callbacks)
Swapping implementationsRequires editing sourceChange an annotation / config
TestabilityPoor, dependencies welded inGood, mocks can be injected
38 / 133

In one line: what gets inverted is not "who calls whom" but "who creates, wires and manages the object end to end". Business code keeps only "what I need"; everything else is outsourced to the container.

39 / 133
Key point

interviews love to ask "are IoC and DI the same thing?" Precisely: IoC is the idea (hand over control), DI (dependency injection) is the mechanism (deliver dependencies via constructor, setter or field). Spring implements IoC using DI.

40 / 133
类比

hiring a renovation company is the same transaction. Before inversion you are both the owner and the foreman: you buy the cement, find the tiler, chase the schedule and fix the leaks yourself. After inversion you only sign one contract with the firm (write your constructor parameters); the firm sources materials, sequences the trades, inspects and honours the warranty (the container creates, wires, manages the lifecycle and destroys). What you hand over is the right to decide who does the work and how; what you get back is the freedom to only state requirements. That is precisely what the four rows of the table above say.

41 / 133

Unpack that contract and the firm takes over six jobs. This animation plays them in order — note frame ③: a missing dependency explodes before the object is built, not when you finally call it:

42 / 133
Animation
Animation · Six jobs the container takes over
Animation · Six jobs the container takes over
43 / 133

The sixth job (destroy callbacks) is the one beginners file under "the JVM handles that". Switch to "watch destroy" and see whether those callbacks really fire, and why prototype beans are not on the list:

44 / 133
Kernel lab
TeaVMA bean's life: how far the container actually managesidle
Start with singleton for a baseline, switch to prototype to see the difference, then destroy callbacks to find the edge of 'the container both creates and cleans up'
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
45 / 133
Section
5. The Spring family map
46 / 133

Spring is not a single framework but a whole family. Ordered by how deeply you will touch it:

47 / 133
Table
ModuleWhat it solvesWhere you meet it
spring-core / beansThe IoC container and dependency injection coreThe foundation of everything
spring-contextApplicationContext, events, i18nReading config, publishing events
spring-aopAspect-oriented programmingLogging, security, the base of declarative features
spring-txDeclarative transactions@Transactional
spring-jdbc / ormData access and ORM integrationJdbcTemplate, JPA
spring-web / webmvcWeb and the MVC layers@RestController, @GetMapping
spring-securityAuthentication and authorizationLogin, permissions
spring-testTesting support@SpringBootTest
spring-bootAuto-configuration and startersThe default entry for modern projects
48 / 133

Keep one thread in mind: the higher you go, the closer to business; the lower you go, the closer to the core. Questions like "why does this annotation work" or "why didn't the transaction roll back" are usually answered in the lower layers (aop / tx / context).

49 / 133
Tip

don't start by chewing through the whole family. Master core + context (that is IoC) first; every later module just adds capability on top of the same container — the essence is unchanged, only the capabilities grow.

50 / 133
Section
6. How Spring Framework relates to Spring Boot
51 / 133

This confuses beginners and interviewers alike. Think of Spring Boot as "a scaffold for the scaffold": it is not a replacement framework but a launcher built on top of Spring that removes configuration for you.

52 / 133
Table
AspectSpring FrameworkSpring Boot
RoleProvides IoC, AOP, MVC and other core capabilitiesAuto-configuration and packaging on top of Spring
ConfigurationWrite XML / Java config by handConvention over configuration, works out of the box
DependenciesYou pick compatible versions yourselfStarters bring a compatible set at once
StartupNeeds an external container or manual wiringEmbedded Tomcat, run main directly
RelationshipThe foundationThe show home built on it
53 / 133

Three sentences on what Boot actually does for you:

54 / 133
  • Auto-configuration: sees spring-webmvc on the classpath and configures a DispatcherServlet for you
  • Starters: one spring-boot-starter-web pulls a whole compatible set of web dependencies
  • Embedded container: Tomcat ships inside the jar, so java -jar app.jar starts the service — no external deployment
55 / 133
Trap

"with Spring Boot you don't need to understand Spring" is the biggest misconception. Auto-configuration decides for you, but when something breaks you must be able to read what it did. To know why a bean was created, you return to spring-context knowledge and debug — you don't expect Boot to explain itself.

56 / 133

That timeline appears in Section 2 as five names, but what each step is really worth remembering is "what it saved you and what it left you owing". Click through it — the last two boxes are where you are standing today:

57 / 133
Diagram
FlowFive steps of evolution: what each one saved, what each one owed1 / 5
Click ① to ⑤. The first two are history, the third is the interview answer, the last two are what you use daily
→
→
→
→
① EJB: forced container interfaces
Saved: hand-written transactions and remote invocation. Owed: your business classes could only ever be EJBs — untestable outside the container, their structure dictated by the framework. This is the pain Spring was born from.
All clearEvery layer removes boilerplate and pushes complexity deeper: the configuration you saved becomes behaviour you cannot read.
58 / 133
Section
7. Why learn Spring's internals today
59 / 133

"But I can use the annotations — isn't that enough?" Here are three real reasons it is not:

60 / 133
  1. Debugging: errors about circular dependencies, broken AOP proxies or a transaction that did not roll back — without the container's mechanics you are stuck with search-and-guess; with them you locate the cause directly
  2. Reading source and docs: Spring's official docs and community articles assume you know IoC and the Bean lifecycle; without the core, much of it cannot be applied
  3. Interview essentials: Bean lifecycle, circular dependency three-level cache, AOP proxying, transaction propagation are guaranteed Java backend interview topics, almost always asked as principles
61 / 133

Try the container yourself and watch how beans get wired — flip the switch in the lower right and see how the assembly changes:

62 / 133
Kernel lab
63 / 133
Key point

learning Spring goes "use it, then understand it, then debug it". Skip the middle step and you stay stuck at "copy-paste works, errors don't" — exactly why this course insists on explaining why first.

64 / 133
Section
8. Two decisions you will actually face
65 / 133
Decision
Decisionyou join a team and find a brand-new Spring Boot project with not a single line of XML in its config. A colleague says "since everything is annotations now, you can skip XML configuration knowledge entirely." Is that right?
66 / 133
Decision
Decisionyou have just finished Java syntax and want to "learn Spring". Which road first?
67 / 133

If you picked B, the next question is "which dependencies does this project need". Do not copy somebody's pom — tick it once instead: for every box the generator explains why that line exists and what disappears without it.

68 / 133
Generator
GeneratorAfter deciding on the container: what the first pom holdspom.xml2 / 7
Start with just Web and Test to get a project that runs and tests, then add JPA / MySQL as needed. Notice MySQL comes out as scope=runtime and Test as scope=test — that is precisely article 2, section 4
Output
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.4</version> <!-- 版本由 BOM 统管,子依赖不写 version -->
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>demo-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
Why each choice matters
parentInheriting 3.3.4 starter-parent means no spring-boot-starter-* needs a version; the moment someone adds an explicit version to one starter, that one wins — the most common source of dependency drift.
WebAnything that serves HTTP needs it: DispatcherServlet, embedded Tomcat and JSON mapping come inside this starter.
Testscope=test: @SpringBootTest, MockMvc and AssertJ live here; without it @Test is unresolved.
69 / 133
Section
11. Kernel lab 1: walk the twelve manual steps yourself
70 / 133

"EJB is heavy, Spring is light" is a historical claim. What actually sticks is counting the steps yourself. The lab below has four presets — press them in order. First 「12 manual steps」 to see how much you must write without a framework; then 「Let the container」 to see which few steps remain; then 「One step more: Boot」 to see what happens when even configuration is removed; finally 「Cost and payoff」 to see what the framework charges you. Do not read on until you have pressed all four.

71 / 133
Kernel lab
TeaVMWithout Spring vs with Spring: how twelve steps disappearidle
Go raw → spring → boot → cost, and watch whether anything but business intent survives in the code
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
72 / 133

After the lab, lock the conclusion with this side-by-side diagram: every line in the left column serves the environment; every line in the right column expresses business.

73 / 133
Diagram
Figure · Twelve manual steps versus the container
Figure · Twelve manual steps versus the container
74 / 133
Note

the hand-written version is not something to avoid. Quite the opposite — writing one full JDBC round trip by hand is how you learn precisely what Spring deleted. Article 27 of this course goes back and writes it.

75 / 133
Section
12. Kernel lab 2: how a dependency actually reaches your hands
76 / 133

Section 4 says inversion moves the right to create. But once that right is handed over, how is the object physically assembled? This lab puts the three styles side by side: constructor injection (the engine must be present when you collect the car), setter injection (collect the chassis, fit the tyres later), field injection (someone slips a part into your car while you are away). Finally switch to 「With a cycle」 and see why the same deadlock kills constructor injection on the spot while field and setter survive.

77 / 133
Kernel lab
TeaVMThree injection stylesidle
Cycle constructor → setter → field → cycle, and note exactly when orderRepository stops being null
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
78 / 133
类比

the three styles are three ways of receiving a delivery — constructor injection is "sign only when the parcel is complete" (fail fast); setter injection is "drop the box at the door, install it later" (re-fittable, swappable); field injection is "something appeared in your home and you never saw who put it there" (least code, zero visibility).

79 / 133

How does that "sign on delivery" step actually happen? Spread it into a single-step run: five lines of main on the left, the current variables and the container's stack depth on the right. Step through and watch beats 3 and 4 — the object is already built by beat 3; beat 4 only reads it out of the pool:

80 / 133
Stepper
StepperBefore your getBean: how the container hands the dependency over1 / 6
Six beats. Beat 2 decides startup cost, beat 6 decides whether your tests need a container at all
Code under debug
1ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
2// ① read config: every @Service becomes a BeanDefinition
3// ② refresh(): pre-instantiate all non-lazy singletons
4// ③ build orderController: first ask what its constructor wants
5// ④ find candidates by type → orderService → build it recursively
6HelloController h = ctx.getBean(HelloController.class); // just a pool read
Variables now
ctxAnnotationConfigApplicationContext
registered blueprints0, about to be read
Call stack
1new AnnotationConfigApplicationContext
1This one line does everything: read config, register blueprints, refresh. You hold nothing yet, but the container already knows how many objects it owes.
81 / 133

Now run the most common real change through the animation: product asks the order repository to move from MySQL to Redis. The manual road means hunting every new; miss one and you get a runtime NullPointerException. The container road changes only the declaration, and the container validates the rest at startup.

82 / 133
Animation
Animation · Two fates of one implementation swap
Animation · Two fates of one implementation swap
83 / 133
Section
13. Kernel lab 3: what the container really does with an XML file
84 / 133

Many people believe "write a <bean> tag and the object appears". There are two clearly separated legs: **first the XML is read into blueprints (BeanDefinition — metadata describing how to build the object; not the object itself)**, then objects are built from those blueprints. The lab has four presets: Read & parse, Register definitions, getBean in action, A typo in class. Press the last one especially — it explains why a wrong class name in XML produces no editor warning yet explodes at startup.

85 / 133
Kernel lab
TeaVMInside the XML containeridle
Start with parse and register (no object exists yet), then getbean hitting the singleton pool, and finally reproduce the beginner's first error with typo
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
86 / 133
Tip

these legs become your own runnable project in article 5. For now keep one impression: annotations and XML are two spellings of the same thing, and both end up as the same blueprint.

87 / 133
Section
14. Kernel lab 4: the wiring sandbox — flip one condition, watch the chain react
88 / 133

The three labs above show process; this one shows decisions. It runs a small Java container for real inside your browser: toggle a condition switch and the container re-wires beans live, writing "why this bean was skipped" into the log. Try turning the data-source switch off and see what happens to everything downstream that depends on it.

89 / 133
Kernel lab
90 / 133

After four labs, switch to typing. This console is wired to the same container running in your browser and every answer is computed there — start with beans to see how many objects the container really holds, then di, then work through the lab lines:

91 / 133
Console
92 / 133
Tip

run di straight after beans — the same objects appear in two views: "what exists" and "who depends on whom". The second one is what the wiring-rights row of Section 4's table is talking about.

93 / 133
Section
15. Sandbox: should *my* project adopt a container
94 / 133

"Should we use Spring" is not a question of faith; it is arithmetic. The sandbox gives you three variables: how many objects collaborate, whether implementations must be swappable, whether unit tests are required. Move them and the verdict plus its cost estimate appear immediately.

95 / 133
Sandbox
SandboxAdopt a container? Three switches decide
Result
# Only 3 objects, no swapping, no tests
Manual assembly cost: ~10 minutes (three new lines)
Container adoption cost: ~2 days (dependency + config + team ramp-up)
Verdict: not worth it -- just call new
Typical one-off script, CLI helper, a main that exits. The container has nothing to grab onto.
96 / 133
Key point

the only question that matters is whether your code contains multiple objects that must collaborate. If yes, the container saves you glue code; if not, forcing one in only slows the cold start and adds a black box.

97 / 133
Section
16. Check yourself
98 / 133

The first question tests intuition — get it right and the takeout metaphor has landed:

99 / 133
Quiz
Check yourselfA colleague replaced `new MysqlOrderRepository()` with 'declare an OrderRepository as a constructor parameter and do not care who supplies it'. What did that change mainly buy?
Pick one — you get feedback right away
100 / 133

The second is broader, tying sections 6 and 7 together:

101 / 133
Quiz
Check yourselfA team starts an internal tool: one main method reads a CSV, cleans it, prints it. No database, no web, and it will be thrown away in three months. What is the soundest call about using Spring Boot?
Pick one — you get feedback right away
102 / 133
Section
17. Error quick-reference
103 / 133

This section is your self-rescue entry point. Copy the verbatim fragments into a search engine; do not paraphrase them — search matches strings, not meanings.

104 / 133
Table
Verbatim fragmentReal cause30-second fixWhere it goes deep
Exception in thread "main" java.lang.NullPointerException at com.example.OrderController.checkout(OrderController.java:14)You never constructed the collaborator, or you expected the container to fill the field while the class itself never entered the containerFollow the top stack frame to the field that is null. Either construct it honestly with new, or let the container build this class (@Service plus fetch from the container)Section 4 here; article 6
An @Autowired field is null at runtime and startup printed no errorThe object came from new XxxService(), so it never passed through the container and nobody injected itGrep for new in front of that class name. Any class needing injection, transactions or aspects must be taken from the container, never hand-builtArticle 5 section 8; article 6
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.OrderService' availableNo bean of that type exists in the container: the class lacks @Service/@Component, or its package is outside the scan pathCheck the component annotation first, then whether the main class sits in a parent package. If both look right, log getBeanNamesForType to confirmArticles 5 and 7
org.springframework.beans.factory.NoUniqueBeanDefinitionException: ... expected single matching bean but found 2: mysqlOrderRepository,redisOrderRepositoryTwo implementations of one interface were both scanned and the container cannot pickAdd @Qualifier("mysqlOrderRepository") at the injection point to name it, or mark the default with @PrimaryArticle 6 section 3; article 7
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'orderService': Requested bean is currently in creation: Is there an unresolvable circular reference?A's constructor needs B while B's constructor needs A, so each waits for the other to be bornDo not switch on allow-circular-references yet. Extract the shared logic into a third bean, or put @Lazy on one injection pointArticle 10
java.lang.ClassNotFoundException: com.mysql.cj.jdbc.DriverLesson one of the hand-written era: the driver jar is not on the classpath, or driver registration was never triggeredVerify the coordinates (mvn dependency:tree) and confirm the class exists in the packaged jar; with JdbcTemplate the template owns this stepArticles 2 and 27
"Should I use BeanFactory or ApplicationContext?" (a choice, not an exception)They differ in layer, not in age: the former is the lowest-level interface (create on demand, lazy), the latter adds events, i18n, resources and auto-proxying and pre-instantiates every non-lazy singleton at startupReal projects always use ApplicationContext. Consider a bare BeanFactory only for embedded, cold-start-critical cases; remember that failing-at-boot errors are the benefit of pre-instantiationArticle 5 section 6; article 8
105 / 133
Tip

when searching an error, query only the first English fragment after the colon (for example expected single matching bean but found 2). Hit rates beat full sentences, since the tail wording varies across versions.

106 / 133

The NoSuchBeanDefinitionException in the third row above is almost everybody's first Spring error. Do not memorise it — click the guilty frame instead, and note that the top line is not the answer:

107 / 133
Triage
Error triageNoSuchBeanDefinitionException: No qualifying bean of type 'com.example.OrderRepository'
The container says 'I have no such bean': read from the bottom up, the answer is the last sentence

You added a constructor parameter exactly as Section 4 prescribes, and on startup the console fills with red — before a single line of your code ran.

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'orderService' defined in class path resource: Unsatisfied dependency expressed through constructor parameter 0: No qualifying bean of type 'com.example.OrderRepository' available
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:797)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:238)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1204)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:599)
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.OrderRepository' available: expected at least 1 bean which qualifies as autowire candidate
Click the frame you blame — guessing is allowed
No pressure: guess the exception first, then which line actually made the call.
108 / 133
Section
18. Hands-on exercises
109 / 133
Section
Tier 1 · Follow along
110 / 133

Goal: run the minimal "ordering takeout" version in five minutes — one interface, two implementations, and you personally play the container deciding which one is handed over. No Maven needed; a JDK is enough.

111 / 133
java
package com.example.warmup;// (1) The abstraction: this is the only menu the customer knowsinterface CoffeeMaker {    String make();}// (2) Implementation one: the in-shop machineclass ShopCoffeeMaker implements CoffeeMaker {    @Override    public String make() {        return "freshly ground in shop";    }}// (3) Implementation two: delivered coffeeclass DeliveryCoffeeMaker implements CoffeeMaker {    @Override    public String make() {        return "delivered to my door";    }}// (4) Business class: declares what it needs, chooses nothingclass Customer {    private final CoffeeMaker maker;   // constructor injection lets the field be final    Customer(CoffeeMaker maker) {        this.maker = maker;    }    String drink() {        return "I am having: " + maker.make();    }}public class Main {    public static void main(String[] args) {        // (5) Right now *this* block is the container: the choice lives outside the user        CoffeeMaker chosen = new DeliveryCoffeeMaker();        Customer customer = new Customer(chosen);        System.out.println(customer.drink());        // (6) Swap the implementation: the business class is untouched        Customer another = new Customer(new ShopCoffeeMaker());        System.out.println(another.drink());    }}
112 / 133

Compile and run:

113 / 133
bash
javac -d out src/com/example/warmup/Main.javajava -cp out com.example.warmup.Main
114 / 133

Expected output:

115 / 133
text
I am having: delivered to my doorI am having: freshly ground in shop
116 / 133

Check three things: ① Customer contains no new CoffeeMaker anywhere — it only names the interface; ② swapping the implementation touched only Main; ③ maker is final, immutable after construction — the two reasons article 6 recommends constructor injection.

117 / 133
Section
Tier 2 · Variant
118 / 133

Goal: watch "forgot to inject" happen with your own eyes.

119 / 133

Do it: delete that hand-written-container block from Main and instead declare private CoffeeMaker maker = null; inside Customer, changing nothing else, then call customer.drink().

120 / 133

You will observe: it compiles, it starts, and only the moment you call it does NullPointerException explode. Copy that stack trace into your notes and label it — it is the prototype of row one in the error table above. Extend the variant: add setMaker(...) to Customer, call drink() right after construction (boom), then add one setMaker(...) (alive). That is exactly the price of setter injection: an object can exist in a half-built state.

121 / 133
Section
Tier 3 · Build one
122 / 133

Goal: build a tiny "three restaurants + one customer" project and feel what a container actually does for you.

123 / 133

Requirements: define a Restaurant interface (with a dish() method), write HotpotRestaurant, SushiRestaurant and CanteenRestaurant; then write a minimal MyContainer holding a Map<String, Restaurant> of "name → instance" and exposing only pick(String name). Customer obtains restaurants through MyContainer and never news an implementation.

124 / 133

Acceptance checklist:

125 / 133
  • [ ] Grepping Customer finds no concrete implementation class name (interface only)
  • [ ] Adding a fourth restaurant requires registering it in MyContainer and changing no business class
  • [ ] MyContainer.pick("not-exist") is handled deliberately, and you can name the Spring exception it corresponds to
  • [ ] You can point at the four rows and say who now owns creation, wiring, lifecycle and implementation swapping
  • [ ] Write down which three capabilities your MyContainer lacks compared with Spring (hints: scope, init callbacks, lookup by type)
126 / 133
Section
19. Self-check
127 / 133
自检

without notes, explain inversion of control to someone who has never coded using the takeout metaphor, and be clear about what you hand over and what you keep.

128 / 133
自检

section 4 lists four dimensions of inversion. Can you recall three from memory? Miss one and go back.

129 / 133
自检

which of IoC and DI is the means? Answer in one sentence that must contain the words "idea" and "mechanism".

130 / 133
自检

what is the practical difference between BeanFactory and ApplicationContext? The keyword should be instantiation timing, not old versus new.

131 / 133
自检

for NoUniqueBeanDefinitionException in the error table, can you say when to use each of the two fixes? (Hint: one says "this particular call site is special", the other says "normally, this one wins".)

132 / 133
口诀

Order, do not cook — inversion hands over creation; blueprints (BeanDefinition) land first, the container ships objects after.

133 / 133
Summary

Spring has proven its values over twenty years — non-invasive, testable, interface-oriented. Its core, IoC, does one thing: it frees you from "newing everything yourself", letting you declare dependencies and handing creation and wiring to the container. Understand that layer and every Spring module (AOP, transactions, MVC, Boot, Cloud) is just capability stacked on the same container — no longer a pile of annotations to memorize.