$npx -y skills add giuseppe-trisciuoglio/developer-kit --skill spring-boot-dependency-injectionProvides dependency injection patterns for Spring Boot projects, including constructor-first design, optional collaborator handling, bean selection, and wiring validation. Use when creating services and configurations, replacing field injection, or troubleshooting ambiguous or fr
| 1 | # Spring Boot Dependency Injection |
| 2 | |
| 3 | ## Overview |
| 4 | |
| 5 | Provides constructor-first dependency injection patterns for Spring Boot: |
| 6 | - mandatory collaborators via constructor injection |
| 7 | - optional collaborators via `ObjectProvider` or no-op fallbacks |
| 8 | - bean selection via `@Primary` and `@Qualifier` |
| 9 | - validation via minimal context tests before full integration |
| 10 | |
| 11 | ## When to Use |
| 12 | |
| 13 | Use this skill when: |
| 14 | - creating a new `@Service`, `@Component`, `@Repository`, or `@Configuration` class |
| 15 | - replacing field injection in legacy Spring code |
| 16 | - resolving multiple beans of the same type with qualifiers or primary beans |
| 17 | - handling optional features, adapters, or integrations without null-driven wiring |
| 18 | - reviewing circular dependencies or brittle context startup failures |
| 19 | - preparing code for direct constructor-based unit testing |
| 20 | |
| 21 | |
| 22 | ## Instructions |
| 23 | |
| 24 | ### 1. Separate mandatory and optional collaborators |
| 25 | |
| 26 | For each class, identify: |
| 27 | - mandatory collaborators required for correct behavior |
| 28 | - optional collaborators that enable integrations, caching, notifications, or feature-flagged behavior |
| 29 | |
| 30 | Mandatory collaborators belong in the constructor. Optional ones need an explicit strategy such as `ObjectProvider`, conditional beans, or a no-op implementation. |
| 31 | |
| 32 | ### 2. Default to constructor injection |
| 33 | |
| 34 | For application services and adapters: |
| 35 | - inject mandatory dependencies through the constructor |
| 36 | - keep injected fields `final` |
| 37 | - instantiate the class directly in unit tests without starting Spring |
| 38 | |
| 39 | A single constructor is usually enough; `@Autowired` is unnecessary in that case. |
| 40 | |
| 41 | ### 3. Resolve optional behavior intentionally |
| 42 | |
| 43 | Good options include: |
| 44 | - `ObjectProvider<T>` when lazy access is useful |
| 45 | - `@ConditionalOnProperty` or `@ConditionalOnMissingBean` when wiring should change by configuration |
| 46 | - a no-op implementation when the caller should not care whether the feature is enabled |
| 47 | |
| 48 | Avoid nullable collaborators that leave runtime behavior ambiguous. |
| 49 | |
| 50 | ### 4. Use bean selection annotations only when needed |
| 51 | |
| 52 | When multiple beans share the same type: |
| 53 | - use `@Primary` for the default implementation |
| 54 | - use `@Qualifier` for named variants |
| 55 | - keep the qualifier names stable and easy to grep |
| 56 | |
| 57 | If selection rules become complex, move them into a dedicated configuration class instead of spreading them across services. |
| 58 | |
| 59 | ### 5. Keep wiring in configuration, not business code |
| 60 | |
| 61 | Use `@Configuration` and `@Bean` methods when: |
| 62 | - the object comes from a third-party library |
| 63 | - conditional creation logic is needed |
| 64 | - you need environment-specific wiring or explicit composition |
| 65 | |
| 66 | Business services should not know how infrastructure collaborators are instantiated. |
| 67 | |
| 68 | ### 6. Validate wiring explicitly |
| 69 | |
| 70 | After writing a new service or configuration: |
| 71 | |
| 72 | 1. **Verify the bean loads** with a minimal context test: |
| 73 | ```java |
| 74 | @SpringBootTest |
| 75 | @ContextConfiguration(classes = UserService.class) |
| 76 | class UserServiceWiringTest { |
| 77 | @Autowired UserService userService; |
| 78 | @Test void serviceIsInstantiated() { assertNotNull(userService); } |
| 79 | } |
| 80 | ``` |
| 81 | 2. **Run constructor-based unit tests** for service behavior (no Spring needed). |
| 82 | 3. **Add slice tests** only when MVC, JPA, or messaging integration must be verified. |
| 83 | 4. **Reserve `@SpringBootTest`** for container-wide wiring validation. |
| 84 | |
| 85 | Failures at step 1 indicate wiring issues before business logic is added. |
| 86 | |
| 87 | ## Examples |
| 88 | |
| 89 | ### Example 1: Constructor-first application service |
| 90 | |
| 91 | ```java |
| 92 | @Service |
| 93 | public class UserService { |
| 94 | |
| 95 | private final UserRepository userRepository; |
| 96 | private final EmailSender emailSender; |
| 97 | |
| 98 | public UserService(UserRepository userRepository, EmailSender emailSender) { |
| 99 | this.userRepository = userRepository; |
| 100 | this.emailSender = emailSender; |
| 101 | } |
| 102 | |
| 103 | public User register(UserRegistrationRequest request) { |
| 104 | User user = userRepository.save(User.from(request)); |
| 105 | emailSender.sendWelcome(user); |
| 106 | return user; |
| 107 | } |
| 108 | } |
| 109 | ``` |
| 110 | |
| 111 | This class is easy to instantiate directly in a unit test with mocks. |
| 112 | |
| 113 | ### Example 2: Optional dependency with a no-op fallback |
| 114 | |
| 115 | ```java |
| 116 | @Service |
| 117 | public class ReportService { |
| 118 | |
| 119 | private final ReportRepository reportRepository; |
| 120 | private final NotificationGateway notificationGateway; |
| 121 | |
| 122 | public ReportService( |
| 123 | ReportRepository reportRepository, |
| 124 | ObjectProvider<NotificationGateway> notificationGatewayProvider |
| 125 | ) { |
| 126 | this.reportRepository = reportRepository; |
| 127 | this.notificationGateway = notificationGatewayProvider.getIfAvailable(NotificationGateway::noOp); |
| 128 | } |
| 129 | } |
| 130 | ``` |
| 131 | |
| 132 | This keeps optional behavior explicit without leaking `null` handling th |