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