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