Files

87 lines
6.5 KiB
Markdown

# 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<Plugin> 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.