# core-di Companion project for two articles on **[ankurm.com](https://ankurm.com)**. | Article | What it demonstrates | |---|---| | [Dependency Injection in Spring Boot 4: Constructor vs Setter vs Field (and Why Field Injection Hurts)](https://ankurm.com/spring-boot-4-dependency-injection-constructor-setter-field/) | 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](https://ankurm.com/spring-autowired-qualifier-primary-objectprovider-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 ```bash 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: ```bash 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`](output/01-three-styles-under-plain-new.txt) | The same OrderService built with plain 'new', no Spring anywhere | | [`02-fields-and-finality.txt`](output/02-fields-and-finality.txt) | Which injected fields can be final? (reflection over the three variants) | | [`03-injection-order.txt`](output/03-injection-order.txt) | The order Spring touches one bean that uses all three styles | | [`04-field-used-in-constructor.txt`](output/04-field-used-in-constructor.txt) | A field-injected collaborator used in the constructor | | [`05-multiple-constructors.txt`](output/05-multiple-constructors.txt) | Two constructors: which one does Spring pick? | | [`06-optional-setter.txt`](output/06-optional-setter.txt) | @Autowired(required = false) on a setter | | [`07-spring-wires-all-three.txt`](output/07-spring-wires-all-three.txt) | Inside a Spring context all three styles work | | [`08-circular-plain-vs-boot.txt`](output/08-circular-plain-vs-boot.txt) | Constructor cycle vs field cycle: plain Spring and Spring Boot | | [`09-lazy-breaks-constructor-cycle.txt`](output/09-lazy-breaks-constructor-cycle.txt) | @Lazy on one constructor parameter breaks a constructor cycle | | [`10-no-unique-bean.txt`](output/10-no-unique-bean.txt) | Two PaymentGateway beans and a consumer that asks for one | | [`11-resolution-ladder.txt`](output/11-resolution-ladder.txt) | Who wins when several rules apply at once? (one mini-context per row) | | [`12-missing-bean.txt`](output/12-missing-bean.txt) | No PaymentGateway bean at all | | [`13-optional-and-objectprovider.txt`](output/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`](output/14-list-map-set-injection.txt) | List, Map and Set injection; registered Push, Email, Sms in that order | | [`15-empty-collection.txt`](output/15-empty-collection.txt) | A List injection point when zero Plugin beans exist | | [`16-generics-and-resource.txt`](output/16-generics-and-resource.txt) | Generic type arguments and @Resource | | [`17-dependency-graph.txt`](output/17-dependency-graph.txt) | getDependenciesForBean: who did Spring wire into whom? | | [`18-parameters-flag.txt`](output/18-parameters-flag.txt) | The parameter-name fallback needs javac -parameters (compiled twice from the same source) | | [`19-boot-failure-analysis-ctor-cycle.txt`](output/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`](output/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`](output/21-boot-allow-circular-references.txt) | Same field cycle with --spring.main.allow-circular-references=true | | [`22-boot-lazy-cycle.txt`](output/22-boot-lazy-cycle.txt) | @Lazy on one constructor parameter: --spring.profiles.active=lazy-cycle | | [`23-determine-autowire-candidate-bytecode.txt`](output/23-determine-autowire-candidate-bytecode.txt) | DefaultListableBeanFactory.determineAutowireCandidate, read with javap | | [`24-early-reference-caches.txt`](output/24-early-reference-caches.txt) | DefaultSingletonBeanRegistry: the fields that hold early references, read with javap | | [`25-spring-nullable-deprecation.txt`](output/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`](output/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.