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 * Use the given BeanScope as the parent. This becomes an additional 217 * source of beans that can be wired and accessed in this scope. 218 * 219 * @param parent The BeanScope that acts as the parent 220 */ 221 BeanScopeBuilder parent(BeanScope parent); 222 223 /** 224 * Deprecated - migrate to parent(). 225 */ 226 @Deprecated 227 default BeanScopeBuilder withParent(BeanScope parent) { 228 return parent(parent); 229 } 230 231 /** 232 * Use the given BeanScope as the parent additionally specifying if beans 233 * added will effectively override beans that exist in the parent scope. 234 * <p> 235 * By default, child scopes will override a bean that exists in a parent scope. 236 * For testing purposes, parentOverride=false is used such that bean provided 237 * in parent test scopes are used (unless we mock() or spy() them). 238 * <p> 239 * See TestBeanScope in avaje-inject-test which has helper methods to build 240 * BeanScopes for testing with the "Global test scope" as a parent scope. 241 * 242 * @param parent The BeanScope that acts as the parent 243 * @param parentOverride When false do not add beans that already exist on the parent. 244 * When true add beans regardless of whether they exist in the parent scope. 245 */ 246 BeanScopeBuilder parent(BeanScope parent, boolean parentOverride); 247 248 /** 249 * Deprecated - migrate to parent(). 250 */ 251 @Deprecated 252 default BeanScopeBuilder withParent(BeanScope parent, boolean parentOverride) { 253 return parent(parent, parentOverride); 254 } 255 256 /** 257 * Extend the builder to support testing using mockito with 258 * <code>withMock()</code> and <code>withSpy()</code> methods. 259 * 260 * @return The builder with extra testing support for mockito mocks and spies 261 */ 262 BeanScopeBuilder.ForTesting forTesting(); 263 264 /** 265 * Build and return the bean scope. 266 * <p> 267 * The BeanScope is effectively immutable in that all components are created 268 * and all PostConstruct lifecycle methods have been invoked. 269 * <p> 270 * The beanScope effectively contains eager singletons. 271 * 272 * @return The BeanScope 273 */ 274 BeanScope build(); 275 276 /** 277 * Extends the building with testing specific support for mocks and spies. 278 */ 279 interface ForTesting extends BeanScopeBuilder { 280 281 /** 282 * Use a mockito mock when injecting this bean type. 283 * 284 * <pre>{@code 285 * 286 * try (BeanScope scope = BeanScope.builder() 287 * .forTesting() 288 * .mock(Pump.class) 289 * .mock(Grinder.class) 290 * .build()) { 291 * 292 * 293 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 294 * coffeeMaker.makeIt(); 295 * 296 * // this is a mockito mock 297 * Grinder grinder = scope.get(Grinder.class); 298 * verify(grinder).grindBeans(); 299 * } 300 * 301 * }</pre> 302 */ 303 BeanScopeBuilder.ForTesting mock(Class<?> type); 304 305 /** 306 * Deprecated - migrate to mock(). 307 */ 308 @Deprecated 309 default BeanScopeBuilder.ForTesting withMock(Class<?> type) { 310 return mock(type); 311 } 312 313 /** 314 * Register as a Mockito mock with a qualifier name. 315 * 316 * <pre>{@code 317 * 318 * try (BeanScope scope = BeanScope.builder() 319 * .forTesting() 320 * .mock(Store.class, "red") 321 * .mock(Store.class, "blue") 322 * .build()) { 323 * 324 * ... 325 * } 326 * 327 * }</pre> 328 */ 329 BeanScopeBuilder.ForTesting mock(Class<?> type, String name); 330 331 /** 332 * Deprecated - migrate to mock(). 333 */ 334 @Deprecated 335 default BeanScopeBuilder.ForTesting withMock(Class<?> type, String name) { 336 return mock(type, name); 337 } 338 339 /** 340 * Use a mockito mock when injecting this bean type additionally 341 * running setup on the mock instance. 342 * 343 * <pre>{@code 344 * 345 * try (BeanScope scope = BeanScope.builder() 346 * .forTesting() 347 * .mock(Pump.class) 348 * .mock(Grinder.class, grinder -> { 349 * 350 * // setup the mock 351 * when(grinder.grindBeans()).thenReturn("stub response"); 352 * }) 353 * .build()) { 354 * 355 * 356 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 357 * coffeeMaker.makeIt(); 358 * 359 * // this is a mockito mock 360 * Grinder grinder = scope.get(Grinder.class); 361 * verify(grinder).grindBeans(); 362 * } 363 * 364 * }</pre> 365 */ 366 <D> BeanScopeBuilder.ForTesting mock(Class<D> type, Consumer<D> consumer); 367 368 /** 369 * Deprecated - migrate to mock(). 370 */ 371 @Deprecated 372 default <D> BeanScopeBuilder.ForTesting withMock(Class<D> type, Consumer<D> consumer) { 373 return mock(type, consumer); 374 } 375 376 /** 377 * Use a mockito spy when injecting this bean type. 378 * 379 * <pre>{@code 380 * 381 * try (BeanScope scope = BeanScope.builder() 382 * .forTesting() 383 * .spy(Pump.class) 384 * .build()) { 385 * 386 * // setup spy here ... 387 * Pump pump = scope.get(Pump.class); 388 * doNothing().when(pump).pumpSteam(); 389 * 390 * // act 391 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 392 * coffeeMaker.makeIt(); 393 * 394 * verify(pump).pumpWater(); 395 * verify(pump).pumpSteam(); 396 * } 397 * 398 * }</pre> 399 */ 400 BeanScopeBuilder.ForTesting spy(Class<?> type); 401 402 /** 403 * Deprecated - migrate to spy(). 404 */ 405 @Deprecated 406 default BeanScopeBuilder.ForTesting withSpy(Class<?> type) { 407 return spy(type); 408 } 409 410 /** 411 * Register a Mockito spy with a qualifier name. 412 * 413 * <pre>{@code 414 * 415 * try (BeanScope scope = BeanScope.builder() 416 * .forTesting() 417 * .spy(Store.class, "red") 418 * .spy(Store.class, "blue") 419 * .build()) { 420 * 421 * ... 422 * } 423 * 424 * }</pre> 425 */ 426 BeanScopeBuilder.ForTesting spy(Class<?> type, String name); 427 428 /** 429 * Deprecated - migrate to spy(). 430 */ 431 @Deprecated 432 default BeanScopeBuilder.ForTesting withSpy(Class<?> type, String name) { 433 return spy(type, name); 434 } 435 436 /** 437 * Use a mockito spy when injecting this bean type additionally 438 * running setup on the spy instance. 439 * 440 * <pre>{@code 441 * 442 * try (BeanScope scope = BeanScope.builder() 443 * .forTesting() 444 * .spy(Pump.class, pump -> { 445 * // setup the spy 446 * doNothing().when(pump).pumpWater(); 447 * }) 448 * .build()) { 449 * 450 * // or setup here ... 451 * Pump pump = scope.get(Pump.class); 452 * doNothing().when(pump).pumpSteam(); 453 * 454 * // act 455 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 456 * coffeeMaker.makeIt(); 457 * 458 * verify(pump).pumpWater(); 459 * verify(pump).pumpSteam(); 460 * } 461 * 462 * }</pre> 463 */ 464 <D> BeanScopeBuilder.ForTesting spy(Class<D> type, Consumer<D> consumer); 465 466 /** 467 * Deprecated - migrate to spy(). 468 */ 469 @Deprecated 470 default <D> BeanScopeBuilder.ForTesting withSpy(Class<D> type, Consumer<D> consumer) { 471 return spy(type, consumer); 472 } 473 474 } 475}