001package io.avaje.inject; 002 003import io.avaje.inject.spi.Module; 004import io.avaje.lang.NonNullApi; 005import io.avaje.lang.Nullable; 006import jakarta.inject.Provider; 007 008import java.lang.reflect.Type; 009import java.util.function.Consumer; 010import java.util.function.Supplier; 011 012/** 013 * Build a bean scope with options for shutdown hook and supplying external dependencies. 014 * <p> 015 * We can provide external dependencies that are then used in wiring the components. 016 * </p> 017 * 018 * <pre>{@code 019 * 020 * // external dependencies 021 * Pump pump = ... 022 * 023 * BeanScope scope = BeanScope.builder() 024 * .bean(pump) 025 * .build(); 026 * 027 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 028 * coffeeMaker.makeIt(); 029 * 030 * }</pre> 031 */ 032@NonNullApi 033public interface BeanScopeBuilder { 034 035 /** 036 * Create the bean scope registering a shutdown hook (defaults to false, no shutdown hook). 037 * <p> 038 * With {@code withShutdownHook(true)} a shutdown hook will be registered with the Runtime 039 * and executed when the JVM initiates a shutdown. This then will run the {@code preDestroy} 040 * lifecycle methods. 041 * </p> 042 * <pre>{@code 043 * 044 * // automatically closed via try with resources 045 * 046 * BeanScope scope = BeanScope.builder() 047 * .shutdownHook(true) 048 * .build()); 049 * 050 * // on JVM shutdown the preDestroy lifecycle methods are executed 051 * 052 * }</pre> 053 * 054 * @return This BeanScopeBuilder 055 */ 056 BeanScopeBuilder shutdownHook(boolean shutdownHook); 057 058 /** 059 * Deprecated - migrate to shutdownHook(). 060 */ 061 @Deprecated 062 default BeanScopeBuilder withShutdownHook(boolean shutdownHook) { 063 return shutdownHook(shutdownHook); 064 } 065 066 /** 067 * Specify the modules to include in dependency injection. 068 * <p> 069 * Only beans related to the module are included in the BeanScope that is built. 070 * <p> 071 * When we do not explicitly specify modules then all modules that are not "custom scoped" 072 * are found and used via service loading. 073 * 074 * <pre>{@code 075 * 076 * BeanScope scope = BeanScope.builder() 077 * .modules(new CustomModule()) 078 * .build()); 079 * 080 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 081 * coffeeMaker.makeIt(); 082 * 083 * }</pre> 084 * 085 * @param modules The modules that we want to include in dependency injection. 086 * @return This BeanScopeBuilder 087 */ 088 BeanScopeBuilder modules(Module... modules); 089 090 /** 091 * Deprecated - migrate to modules() 092 */ 093 @Deprecated 094 default BeanScopeBuilder withModules(Module... modules) { 095 return modules(modules); 096 } 097 098 /** 099 * Supply a bean to the scope that will be used instead of any 100 * similar bean in the scope. 101 * <p> 102 * This is typically expected to be used in tests and the bean 103 * supplied is typically a test double or mock. 104 * </p> 105 * 106 * <pre>{@code 107 * 108 * // external dependencies 109 * Pump pump = ... 110 * Grinder grinder = ... 111 * 112 * BeanScope scope = BeanScope.builder() 113 * .beans(pump, grinder) 114 * .build(); 115 * 116 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 117 * coffeeMaker.makeIt(); 118 * 119 * }</pre> 120 * 121 * @param beans Externally provided beans used when injecting a dependency 122 * for the bean or the interface(s) it implements 123 * @return This BeanScopeBuilder 124 */ 125 BeanScopeBuilder beans(Object... beans); 126 127 /** 128 * Deprecated - migrate to beans(). 129 */ 130 @Deprecated 131 default BeanScopeBuilder withBeans(Object... beans) { 132 return beans(beans); 133 } 134 135 /** 136 * Add a supplied bean instance with the given injection type (typically an interface type). 137 * 138 * <pre>{@code 139 * 140 * Pump externalDependency = ... 141 * 142 * try (BeanScope scope = BeanScope.builder() 143 * .bean(Pump.class, externalDependency) 144 * .build()) { 145 * 146 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 147 * coffeeMaker.makeIt(); 148 * 149 * Pump pump = scope.get(Pump.class); 150 * assertThat(pump).isSameAs(externalDependency); 151 * } 152 * 153 * }</pre> 154 * 155 * @param type The dependency injection type this bean is target for 156 * @param bean The supplied bean instance to use for injection 157 */ 158 <D> BeanScopeBuilder bean(Class<D> type, D bean); 159 160 /** 161 * Deprecated - migrate to bean(). 162 */ 163 @Deprecated 164 default <D> BeanScopeBuilder withBean(Class<D> type, D bean) { 165 return bean(type, bean); 166 } 167 168 /** 169 * Add a supplied bean instance with the given name and injection type. 170 * 171 * @param name The name qualifier 172 * @param type The dependency injection type this bean is target for 173 * @param bean The supplied bean instance to use for injection 174 */ 175 <D> BeanScopeBuilder bean(String name, Class<D> type, D bean); 176 177 /** 178 * Deprecated - migrate to bean(). 179 */ 180 @Deprecated 181 default <D> BeanScopeBuilder withBean(String name, Class<D> type, D bean) { 182 return bean(name, type, bean); 183 } 184 185 /** 186 * Add a supplied bean instance with the given name and generic type. 187 * 188 * @param name The name qualifier 189 * @param type The dependency injection type this bean is target for 190 * @param bean The supplied bean instance to use for injection 191 */ 192 <D> BeanScopeBuilder bean(String name, Type type, D bean); 193 194 /** 195 * Deprecated - migrate to bean(). 196 */ 197 @Deprecated 198 default <D> BeanScopeBuilder withBean(String name, Type type, D bean) { 199 return bean(name, type, bean); 200 } 201 202 /** 203 * Add a supplied bean instance with a generic type. 204 * 205 * @param type The dependency injection type this bean is target for 206 * @param bean The supplied bean instance to use for injection 207 */ 208 <D> BeanScopeBuilder bean(Type type, D bean); 209 210 /** 211 * Deprecated - migrate to use Supplier rather than Provider. 212 * <p> 213 * Doing this deprecation so that plugins can be used with both the jakarta and javax versions of avaje-inject 214 * so we need to use Supplier instead of Provider. 215 */ 216 @Deprecated 217 default <D> BeanScopeBuilder provideDefault(Type type, Provider<D> provider) { 218 return provideDefault(null, type, provider::get); 219 } 220 221 /** 222 * Add a supplied bean provider that acts as a default fallback for a dependency. 223 * <p> 224 * This provider is only called if nothing else provides the dependency. It effectively 225 * uses `@Secondary` priority. 226 * 227 * @param type The type of the dependency 228 * @param provider The provider of the dependency. 229 */ 230 default <D> BeanScopeBuilder provideDefault(Type type, Supplier<D> provider) { 231 return provideDefault(null, type, provider); 232 } 233 234 /** 235 * Add a supplied bean provider that acts as a default fallback for a dependency. 236 * <p> 237 * This provider is only called if nothing else provides the dependency. It effectively 238 * uses `@Secondary` priority. 239 * 240 * @param name The name qualifier 241 * @param type The type of the dependency 242 * @param provider The provider of the dependency. 243 */ 244 <D> BeanScopeBuilder provideDefault(@Nullable String name, Type type, Supplier<D> provider); 245 246 /** 247 * Deprecated - migrate to bean(). 248 */ 249 @Deprecated 250 default <D> BeanScopeBuilder withBean(Type type, D bean) { 251 return bean(type, bean); 252 } 253 254 /** 255 * Set the ClassLoader to use when loading modules. 256 * 257 * @param classLoader The ClassLoader to use 258 */ 259 BeanScopeBuilder classLoader(ClassLoader classLoader); 260 261 /** 262 * Use the given BeanScope as the parent. This becomes an additional 263 * source of beans that can be wired and accessed in this scope. 264 * 265 * @param parent The BeanScope that acts as the parent 266 */ 267 BeanScopeBuilder parent(BeanScope parent); 268 269 /** 270 * Deprecated - migrate to parent(). 271 */ 272 @Deprecated 273 default BeanScopeBuilder withParent(BeanScope parent) { 274 return parent(parent); 275 } 276 277 /** 278 * Use the given BeanScope as the parent additionally specifying if beans 279 * added will effectively override beans that exist in the parent scope. 280 * <p> 281 * By default, child scopes will override a bean that exists in a parent scope. 282 * For testing purposes, parentOverride=false is used such that bean provided 283 * in parent test scopes are used (unless we mock() or spy() them). 284 * <p> 285 * See TestBeanScope in avaje-inject-test which has helper methods to build 286 * BeanScopes for testing with the "Global test scope" as a parent scope. 287 * 288 * @param parent The BeanScope that acts as the parent 289 * @param parentOverride When false do not add beans that already exist on the parent. 290 * When true add beans regardless of whether they exist in the parent scope. 291 */ 292 BeanScopeBuilder parent(BeanScope parent, boolean parentOverride); 293 294 /** 295 * Deprecated - migrate to parent(). 296 */ 297 @Deprecated 298 default BeanScopeBuilder withParent(BeanScope parent, boolean parentOverride) { 299 return parent(parent, parentOverride); 300 } 301 302 /** 303 * Extend the builder to support testing using mockito with 304 * <code>withMock()</code> and <code>withSpy()</code> methods. 305 * 306 * @return The builder with extra testing support for mockito mocks and spies 307 */ 308 BeanScopeBuilder.ForTesting forTesting(); 309 310 /** 311 * Build and return the bean scope. 312 * <p> 313 * The BeanScope is effectively immutable in that all components are created 314 * and all PostConstruct lifecycle methods have been invoked. 315 * <p> 316 * The beanScope effectively contains eager singletons. 317 * 318 * @return The BeanScope 319 */ 320 BeanScope build(); 321 322 /** 323 * Extends the building with testing specific support for mocks and spies. 324 */ 325 interface ForTesting extends BeanScopeBuilder { 326 327 /** 328 * Use a mockito mock when injecting this bean type. 329 * 330 * <pre>{@code 331 * 332 * try (BeanScope scope = BeanScope.builder() 333 * .forTesting() 334 * .mock(Pump.class) 335 * .mock(Grinder.class) 336 * .build()) { 337 * 338 * 339 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 340 * coffeeMaker.makeIt(); 341 * 342 * // this is a mockito mock 343 * Grinder grinder = scope.get(Grinder.class); 344 * verify(grinder).grindBeans(); 345 * } 346 * 347 * }</pre> 348 */ 349 BeanScopeBuilder.ForTesting mock(Class<?> type); 350 351 /** 352 * Deprecated - migrate to mock(). 353 */ 354 @Deprecated 355 default BeanScopeBuilder.ForTesting withMock(Class<?> type) { 356 return mock(type); 357 } 358 359 /** 360 * Register as a Mockito mock with a qualifier name. 361 * 362 * <pre>{@code 363 * 364 * try (BeanScope scope = BeanScope.builder() 365 * .forTesting() 366 * .mock(Store.class, "red") 367 * .mock(Store.class, "blue") 368 * .build()) { 369 * 370 * ... 371 * } 372 * 373 * }</pre> 374 */ 375 BeanScopeBuilder.ForTesting mock(Class<?> type, String name); 376 377 /** 378 * Deprecated - migrate to mock(). 379 */ 380 @Deprecated 381 default BeanScopeBuilder.ForTesting withMock(Class<?> type, String name) { 382 return mock(type, name); 383 } 384 385 /** 386 * Use a mockito mock when injecting this bean type additionally 387 * running setup on the mock instance. 388 * 389 * <pre>{@code 390 * 391 * try (BeanScope scope = BeanScope.builder() 392 * .forTesting() 393 * .mock(Pump.class) 394 * .mock(Grinder.class, grinder -> { 395 * 396 * // setup the mock 397 * when(grinder.grindBeans()).thenReturn("stub response"); 398 * }) 399 * .build()) { 400 * 401 * 402 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 403 * coffeeMaker.makeIt(); 404 * 405 * // this is a mockito mock 406 * Grinder grinder = scope.get(Grinder.class); 407 * verify(grinder).grindBeans(); 408 * } 409 * 410 * }</pre> 411 */ 412 <D> BeanScopeBuilder.ForTesting mock(Class<D> type, Consumer<D> consumer); 413 414 /** 415 * Deprecated - migrate to mock(). 416 */ 417 @Deprecated 418 default <D> BeanScopeBuilder.ForTesting withMock(Class<D> type, Consumer<D> consumer) { 419 return mock(type, consumer); 420 } 421 422 /** 423 * Use a mockito spy when injecting this bean type. 424 * 425 * <pre>{@code 426 * 427 * try (BeanScope scope = BeanScope.builder() 428 * .forTesting() 429 * .spy(Pump.class) 430 * .build()) { 431 * 432 * // setup spy here ... 433 * Pump pump = scope.get(Pump.class); 434 * doNothing().when(pump).pumpSteam(); 435 * 436 * // act 437 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 438 * coffeeMaker.makeIt(); 439 * 440 * verify(pump).pumpWater(); 441 * verify(pump).pumpSteam(); 442 * } 443 * 444 * }</pre> 445 */ 446 BeanScopeBuilder.ForTesting spy(Class<?> type); 447 448 /** 449 * Deprecated - migrate to spy(). 450 */ 451 @Deprecated 452 default BeanScopeBuilder.ForTesting withSpy(Class<?> type) { 453 return spy(type); 454 } 455 456 /** 457 * Register a Mockito spy with a qualifier name. 458 * 459 * <pre>{@code 460 * 461 * try (BeanScope scope = BeanScope.builder() 462 * .forTesting() 463 * .spy(Store.class, "red") 464 * .spy(Store.class, "blue") 465 * .build()) { 466 * 467 * ... 468 * } 469 * 470 * }</pre> 471 */ 472 BeanScopeBuilder.ForTesting spy(Class<?> type, String name); 473 474 /** 475 * Deprecated - migrate to spy(). 476 */ 477 @Deprecated 478 default BeanScopeBuilder.ForTesting withSpy(Class<?> type, String name) { 479 return spy(type, name); 480 } 481 482 /** 483 * Use a mockito spy when injecting this bean type additionally 484 * running setup on the spy instance. 485 * 486 * <pre>{@code 487 * 488 * try (BeanScope scope = BeanScope.builder() 489 * .forTesting() 490 * .spy(Pump.class, pump -> { 491 * // setup the spy 492 * doNothing().when(pump).pumpWater(); 493 * }) 494 * .build()) { 495 * 496 * // or setup here ... 497 * Pump pump = scope.get(Pump.class); 498 * doNothing().when(pump).pumpSteam(); 499 * 500 * // act 501 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 502 * coffeeMaker.makeIt(); 503 * 504 * verify(pump).pumpWater(); 505 * verify(pump).pumpSteam(); 506 * } 507 * 508 * }</pre> 509 */ 510 <D> BeanScopeBuilder.ForTesting spy(Class<D> type, Consumer<D> consumer); 511 512 /** 513 * Deprecated - migrate to spy(). 514 */ 515 @Deprecated 516 default <D> BeanScopeBuilder.ForTesting withSpy(Class<D> type, Consumer<D> consumer) { 517 return spy(type, consumer); 518 } 519 520 } 521}