Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JoVmf2fWvcpoXndcDSwRf7
core-di
Companion project for two articles on ankurm.com.
| Article | What it demonstrates |
|---|---|
| Dependency Injection in Spring Boot 4: Constructor vs Setter vs Field (and Why Field Injection Hurts) | the same service in three injection styles built without Spring, injection order, a field-injected dependency used in a constructor, two-constructor ambiguity, circular dependencies in plain Spring vs Boot, and what @Lazy really injects |
| @Autowired Explained: By-Type Resolution, @Qualifier, @Primary, ObjectProvider and List Injection | the real NoUniqueBeanDefinitionException, the resolution ladder read from Spring 7.0.9 bytecode, Optional vs @Nullable vs ObjectProvider, List/Map injection order, and the empty-collection trap |
Every console block, exception message and count quoted in those articles came out of output/,
and every file there is regenerated by one script. Most are written by the test suite, so if a
claim stops being true the build goes red.
There is deliberately no docs/ folder: the deeper material lives in collapsible "going
deeper" sections inside the articles themselves, next to the paragraph each one extends.
Versions
| Spring Boot | 4.1.1 |
| Spring Framework | 7.0.9 |
| JDK | 25 (Temurin 25.0.4.1+1) |
| Maven | 3.9 |
Quickstart
export JAVA_HOME=/path/to/jdk-25
mvn test # runs the scenarios and rewrites the test-written files in output/
./scripts/run-all.sh # everything, including the script-captured files
Reproduce the Boot start-up failure report by hand:
mvn -DskipTests package
java -jar target/core-di-1.0.0.jar --spring.profiles.active=ctor-cycle
java -jar target/core-di-1.0.0.jar --spring.profiles.active=field-cycle
java -jar target/core-di-1.0.0.jar --spring.profiles.active=field-cycle --spring.main.allow-circular-references=true
Source layout
| Package | What it holds |
|---|---|
injection/ |
one OrderService written three ways (constructor, setter, field), the trap classes (field used in a constructor, two constructors, circular pairs, @Lazy) |
resolution/ |
two gateways and every way to choose between them (@Primary, @Qualifier, name, @Priority, @Fallback, custom qualifier), optional injection, collections, generics, @Resource |
boot/ |
profile-gated cycle beans for the real "APPLICATION FAILED TO START" report |
Captured output
Files 01-18 (tests) and 19-22 (capture-failure-analysis.sh), 23-26 (capture-bytecode.sh). Timing rows assert coarse thresholds, not exact milliseconds; treat them as indicative.
| File | What it shows |
|---|---|
01-three-styles-under-plain-new.txt |
The same OrderService built with plain 'new', no Spring anywhere |
02-fields-and-finality.txt |
Which injected fields can be final? (reflection over the three variants) |
03-injection-order.txt |
The order Spring touches one bean that uses all three styles |
04-field-used-in-constructor.txt |
A field-injected collaborator used in the constructor |
05-multiple-constructors.txt |
Two constructors: which one does Spring pick? |
06-optional-setter.txt |
@Autowired(required = false) on a setter |
07-spring-wires-all-three.txt |
Inside a Spring context all three styles work |
08-circular-plain-vs-boot.txt |
Constructor cycle vs field cycle: plain Spring and Spring Boot |
09-lazy-breaks-constructor-cycle.txt |
@Lazy on one constructor parameter breaks a constructor cycle |
10-no-unique-bean.txt |
Two PaymentGateway beans and a consumer that asks for one |
11-resolution-ladder.txt |
Who wins when several rules apply at once? (one mini-context per row) |
12-missing-bean.txt |
No PaymentGateway bean at all |
13-optional-and-objectprovider.txt |
Five ways to say 'this may not exist', with zero and with two Notifier beans (Email registered before Sms) |
14-list-map-set-injection.txt |
List, Map and Set injection; registered Push, Email, Sms in that order |
15-empty-collection.txt |
A List injection point when zero Plugin beans exist |
16-generics-and-resource.txt |
Generic type arguments and @Resource |
17-dependency-graph.txt |
getDependenciesForBean: who did Spring wire into whom? |
18-parameters-flag.txt |
The parameter-name fallback needs javac -parameters (compiled twice from the same source) |
19-boot-failure-analysis-ctor-cycle.txt |
Boot start-up failure for a constructor cycle: --spring.profiles.active=ctor-cycle |
20-boot-failure-analysis-field-cycle.txt |
Boot start-up failure for a field-injection cycle: --spring.profiles.active=field-cycle |
21-boot-allow-circular-references.txt |
Same field cycle with --spring.main.allow-circular-references=true |
22-boot-lazy-cycle.txt |
@Lazy on one constructor parameter: --spring.profiles.active=lazy-cycle |
23-determine-autowire-candidate-bytecode.txt |
DefaultListableBeanFactory.determineAutowireCandidate, read with javap |
24-early-reference-caches.txt |
DefaultSingletonBeanRegistry: the fields that hold early references, read with javap |
25-spring-nullable-deprecation.txt |
org.springframework.lang.Nullable in spring-core-7.0.9.jar, read with javap -v |
26-boot-parent-parameters-flag.txt |
spring-boot-starter-parent-4.1.1.pom: the compiler flag that parameter-name matching depends on |
Licence
MIT, see the repository root.