001package io.avaje.inject; 002 003import io.avaje.inject.spi.Module; 004import io.avaje.inject.spi.PropertyRequiresPlugin; 005import io.avaje.lang.NonNullApi; 006import io.avaje.lang.Nullable; 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 * Specify the modules to include in dependency injection. 060 * <p> 061 * Only beans related to the module are included in the BeanScope that is built. 062 * <p> 063 * When we do not explicitly specify modules then all modules that are not "custom scoped" 064 * are found and used via service loading. 065 * 066 * <pre>{@code 067 * 068 * BeanScope scope = BeanScope.builder() 069 * .modules(new CustomModule()) 070 * .build()); 071 * 072 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 073 * coffeeMaker.makeIt(); 074 * 075 * }</pre> 076 * 077 * @param modules The modules that we want to include in dependency injection. 078 * @return This BeanScopeBuilder 079 */ 080 BeanScopeBuilder modules(Module... modules); 081 082 /** 083 * Set the PropertyPlugin used for this scope. This is serviceloaded automatically of not set 084 * 085 * @param propertyRequiresPlugin The plugin for conditions based on properties 086 */ 087 void propertyPlugin(PropertyRequiresPlugin propertyRequiresPlugin); 088 089 /** 090 * Return the PropertyPlugin used for this scope. This is useful for plugins that want to use 091 * the scopes wiring properties. 092 */ 093 PropertyRequiresPlugin propertyPlugin(); 094 095 /** 096 * Supply a bean to the scope that will be used instead of any similar bean in the scope. 097 * 098 * <p>This is typically expected to be used in tests and the bean supplied is typically a test 099 * double or mock. 100 * 101 * <p>If using this to provide a missing bean into the scope, Avaje will fail compilation unless 102 * it detects an {@code @InjectModule(requires)} including the missing class, or it detects a 103 * {@code @Nullable} annotation where the bean is wired. 104 * 105 * <pre>{@code 106 * // external dependencies 107 * Pump pump = ... 108 * Grinder grinder = ... 109 * 110 * BeanScope scope = BeanScope.builder() 111 * .beans(pump, grinder) 112 * .build(); 113 * 114 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 115 * coffeeMaker.makeIt(); 116 * 117 * }</pre> 118 * 119 * @param beans Externally provided beans used when injecting a dependency for the bean or the 120 * interface(s) it implements 121 * @return This BeanScopeBuilder 122 */ 123 BeanScopeBuilder beans(Object... beans); 124 125 /** 126 * Add a supplied bean instance with the given injection type (typically an interface type). 127 * 128 * <p>If using this to provide a missing bean into the scope, Avaje will fail compilation unless 129 * it detects an {@code @InjectModule(requires)} including the missing class, or it detects a 130 * {@code @Nullable} annotation where the bean is wired. 131 * 132 * <pre>{@code 133 * Pump externalDependency = ... 134 * 135 * try (BeanScope scope = BeanScope.builder() 136 * .bean(Pump.class, externalDependency) 137 * .build()) { 138 * 139 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 140 * coffeeMaker.makeIt(); 141 * 142 * Pump pump = scope.get(Pump.class); 143 * assertThat(pump).isSameAs(externalDependency); 144 * } 145 * 146 * }</pre> 147 * 148 * @param type The dependency injection type this bean is target for 149 * @param bean The supplied bean instance to use for injection 150 */ 151 <D> BeanScopeBuilder bean(Class<D> type, D bean); 152 153 /** 154 * Add a supplied bean instance with the given name and injection type. 155 * 156 * <p>If using this to provide a missing bean into the scope, Avaje will fail compilation unless 157 * it detects an {@code @InjectModule(requires)} including the missing class, or it detects a 158 * {@code @Nullable} annotation where the bean is wired. 159 * 160 * @param name The name qualifier 161 * @param type The dependency injection type this bean is target for 162 * @param bean The supplied bean instance to use for injection 163 */ 164 <D> BeanScopeBuilder bean(String name, Class<D> type, D bean); 165 166 /** 167 * Add a supplied bean instance with the given name and generic type. 168 * 169 * <p>If using this to provide a missing bean into the scope, Avaje will fail compilation unless 170 * it detects an {@code @InjectModule(requires)} including the missing class, or it detects a 171 * {@code @Nullable} annotation where the bean is wired. 172 * 173 * @param name The name qualifier 174 * @param type The dependency injection type this bean is target for 175 * @param bean The supplied bean instance to use for injection 176 */ 177 <D> BeanScopeBuilder bean(String name, Type type, D bean); 178 179 /** 180 * Add a supplied bean instance with a generic type. 181 * 182 * <p>If using this to provide a missing bean into the scope, Avaje will fail compilation unless 183 * it detects an {@code @InjectModule(requires)} including the missing class, or it detects a 184 * {@code @Nullable} annotation where the bean is wired. 185 * 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(Type type, D bean); 190 191 /** 192 * Set the explicit profiles to use when building the scope. 193 * 194 * <p>If profiles are not set explicitly here they are read from the properties plugin. 195 */ 196 BeanScopeBuilder profiles(String... profiles); 197 198 /** 199 * Add a supplied bean provider that acts as a default fallback for a dependency. 200 * <p> 201 * This provider is only called if nothing else provides the dependency. It effectively 202 * uses `@Secondary` priority. 203 * 204 * @param type The type of the dependency 205 * @param provider The provider of the dependency. 206 */ 207 default <D> BeanScopeBuilder provideDefault(Type type, Supplier<D> provider) { 208 return provideDefault(null, type, provider); 209 } 210 211 /** 212 * Add a supplied bean provider that acts as a default fallback for a dependency. 213 * <p> 214 * This provider is only called if nothing else provides the dependency. It effectively 215 * uses `@Secondary` priority. 216 * 217 * @param name The name qualifier 218 * @param type The type of the dependency 219 * @param provider The provider of the dependency. 220 */ 221 <D> BeanScopeBuilder provideDefault(@Nullable String name, Type type, Supplier<D> provider); 222 223 /** 224 * Adds hooks that will execute after this scope is built. 225 */ 226 BeanScopeBuilder addPostConstruct(Runnable postConstructHook); 227 228 /** 229 * Adds hook that will execute after this scope is built. 230 */ 231 BeanScopeBuilder addPostConstruct(Consumer<BeanScope> postConstructHook); 232 233 /** 234 * Add hook that will execute before this scope is destroyed. 235 */ 236 BeanScopeBuilder addPreDestroy(AutoCloseable preDestroyHook); 237 238 /** 239 * Add hook with a priority that will execute before this scope is destroyed. 240 * <p> 241 * Specify the priority of the destroy method to control its execution 242 * order relative to other destroy methods. 243 * <p> 244 * Low values for priority execute earlier than high values. All destroy methods 245 * without any explicit priority are given a value of 1000. 246 */ 247 BeanScopeBuilder addPreDestroy(AutoCloseable preDestroyHook, int priority); 248 249 /** 250 * Set the ClassLoader to use when loading modules. 251 * 252 * @param classLoader The ClassLoader to use 253 */ 254 BeanScopeBuilder classLoader(ClassLoader classLoader); 255 256 /** 257 * Use the given BeanScope as the parent. This becomes an additional 258 * source of beans that can be wired and accessed in this scope. 259 * 260 * @param parent The BeanScope that acts as the parent 261 */ 262 BeanScopeBuilder parent(BeanScope parent); 263 264 /** 265 * Use the given BeanScope as the parent additionally specifying if beans 266 * added will effectively override beans that exist in the parent scope. 267 * <p> 268 * By default, child scopes will override a bean that exists in a parent scope. 269 * For testing purposes, parentOverride=false is used such that bean provided 270 * in parent test scopes are used (unless we mock() or spy() them). 271 * <p> 272 * See TestBeanScope in avaje-inject-test which has helper methods to build 273 * BeanScopes for testing with the "Global test scope" as a parent scope. 274 * 275 * @param parent The BeanScope that acts as the parent 276 * @param parentOverride When false do not add beans that already exist on the parent. 277 * When true add beans regardless of whether they exist in the parent scope. 278 */ 279 BeanScopeBuilder parent(BeanScope parent, boolean parentOverride); 280 281 /** 282 * Extend the builder to support testing using mockito with 283 * <code>withMock()</code> and <code>withSpy()</code> methods. 284 * 285 * @return The builder with extra testing support for mockito mocks and spies 286 */ 287 BeanScopeBuilder.ForTesting forTesting(); 288 289 /** 290 * Build and return the bean scope. 291 * <p> 292 * The BeanScope is effectively immutable in that all components are created 293 * and all PostConstruct lifecycle methods have been invoked. 294 * <p> 295 * The beanScope effectively contains eager singletons. 296 * 297 * @return The BeanScope 298 */ 299 BeanScope build(); 300 301 /** 302 * Extends the building with testing specific support for mocks and spies. 303 */ 304 interface ForTesting extends BeanScopeBuilder { 305 306 /** 307 * Use a mockito mock when injecting this bean type. 308 * 309 * <pre>{@code 310 * 311 * try (BeanScope scope = BeanScope.builder() 312 * .forTesting() 313 * .mock(Pump.class) 314 * .mock(Grinder.class) 315 * .build()) { 316 * 317 * 318 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 319 * coffeeMaker.makeIt(); 320 * 321 * // this is a mockito mock 322 * Grinder grinder = scope.get(Grinder.class); 323 * verify(grinder).grindBeans(); 324 * } 325 * 326 * }</pre> 327 */ 328 BeanScopeBuilder.ForTesting mock(Class<?> type); 329 330 /** 331 * Register as a Mockito mock with a qualifier name. 332 * 333 * <pre>{@code 334 * 335 * try (BeanScope scope = BeanScope.builder() 336 * .forTesting() 337 * .mock(Store.class, "red") 338 * .mock(Store.class, "blue") 339 * .build()) { 340 * 341 * ... 342 * } 343 * 344 * }</pre> 345 */ 346 BeanScopeBuilder.ForTesting mock(Class<?> type, String name); 347 348 /** 349 * Use a mockito mock when injecting this bean type additionally 350 * running setup on the mock instance. 351 * 352 * <pre>{@code 353 * 354 * try (BeanScope scope = BeanScope.builder() 355 * .forTesting() 356 * .mock(Pump.class) 357 * .mock(Grinder.class, grinder -> { 358 * 359 * // setup the mock 360 * when(grinder.grindBeans()).thenReturn("stub response"); 361 * }) 362 * .build()) { 363 * 364 * 365 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 366 * coffeeMaker.makeIt(); 367 * 368 * // this is a mockito mock 369 * Grinder grinder = scope.get(Grinder.class); 370 * verify(grinder).grindBeans(); 371 * } 372 * 373 * }</pre> 374 */ 375 <D> BeanScopeBuilder.ForTesting mock(Class<D> type, Consumer<D> consumer); 376 377 /** 378 * Use a mockito spy when injecting this bean type. 379 * 380 * <pre>{@code 381 * 382 * try (BeanScope scope = BeanScope.builder() 383 * .forTesting() 384 * .spy(Pump.class) 385 * .build()) { 386 * 387 * // setup spy here ... 388 * Pump pump = scope.get(Pump.class); 389 * doNothing().when(pump).pumpSteam(); 390 * 391 * // act 392 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 393 * coffeeMaker.makeIt(); 394 * 395 * verify(pump).pumpWater(); 396 * verify(pump).pumpSteam(); 397 * } 398 * 399 * }</pre> 400 */ 401 BeanScopeBuilder.ForTesting spy(Class<?> type); 402 403 /** 404 * Register a Mockito spy with a qualifier name. 405 * 406 * <pre>{@code 407 * 408 * try (BeanScope scope = BeanScope.builder() 409 * .forTesting() 410 * .spy(Store.class, "red") 411 * .spy(Store.class, "blue") 412 * .build()) { 413 * 414 * ... 415 * } 416 * 417 * }</pre> 418 */ 419 BeanScopeBuilder.ForTesting spy(Class<?> type, String name); 420 421 /** 422 * Use a mockito spy when injecting this bean type additionally 423 * running setup on the spy instance. 424 * 425 * <pre>{@code 426 * 427 * try (BeanScope scope = BeanScope.builder() 428 * .forTesting() 429 * .spy(Pump.class, pump -> { 430 * // setup the spy 431 * doNothing().when(pump).pumpWater(); 432 * }) 433 * .build()) { 434 * 435 * // or setup here ... 436 * Pump pump = scope.get(Pump.class); 437 * doNothing().when(pump).pumpSteam(); 438 * 439 * // act 440 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 441 * coffeeMaker.makeIt(); 442 * 443 * verify(pump).pumpWater(); 444 * verify(pump).pumpSteam(); 445 * } 446 * 447 * }</pre> 448 */ 449 <D> BeanScopeBuilder.ForTesting spy(Class<D> type, Consumer<D> consumer); 450 } 451}