001package io.avaje.inject; 002 003import java.lang.reflect.Type; 004import java.util.function.Consumer; 005import java.util.function.Supplier; 006 007import org.jspecify.annotations.Nullable; 008 009import io.avaje.inject.spi.AvajeModule; 010import io.avaje.inject.spi.ConfigPropertyPlugin; 011import io.avaje.inject.spi.PropertyRequiresPlugin; 012 013/** 014 * Build a bean scope with options for shutdown hook and supplying external dependencies. 015 * <p> 016 * We can provide external dependencies that are then used in wiring the components. 017 * </p> 018 * 019 * <pre>{@code 020 * 021 * // external dependencies 022 * Pump pump = ... 023 * 024 * BeanScope scope = BeanScope.builder() 025 * .bean(pump) 026 * .build(); 027 * 028 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 029 * coffeeMaker.makeIt(); 030 * 031 * }</pre> 032 */ 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(AvajeModule... modules); 081 082 /** 083 * Return the PropertyRequiresPlugin used for this scope. This is useful for plugins that want to 084 * use the scopes wiring properties. 085 * 086 * @deprecated use {@link #configPlugin()} instead 087 */ 088 @Deprecated(forRemoval = true) 089 PropertyRequiresPlugin propertyPlugin(); 090 091 /** 092 * Set the ConfigPropertyPlugin used for this scope. This is serviceloaded automatically of not set 093 * 094 * @param propertyPlugin The plugin for conditions based on properties 095 */ 096 void configPlugin(ConfigPropertyPlugin propertyPlugin); 097 098 /** 099 * Return the ConfigPropertyPlugin used for this scope. This is useful for plugins that want to use 100 * the scopes wiring properties. 101 */ 102 ConfigPropertyPlugin configPlugin(); 103 104 /** 105 * Supply a bean to the scope that will be used instead of any similar bean in the scope. 106 * 107 * <p>This is typically expected to be used in tests and the bean supplied is typically a test 108 * double or mock. 109 * 110 * <p>If using this to provide a missing bean into the scope, Avaje will fail compilation unless 111 * it detects an {@code @InjectModule(requires)} including the missing class, or it detects a 112 * {@code @Nullable} annotation where the bean is wired. 113 * 114 * <pre>{@code 115 * // external dependencies 116 * Pump pump = ... 117 * Grinder grinder = ... 118 * 119 * BeanScope scope = BeanScope.builder() 120 * .beans(pump, grinder) 121 * .build(); 122 * 123 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 124 * coffeeMaker.makeIt(); 125 * 126 * }</pre> 127 * 128 * @param beans Externally provided beans used when injecting a dependency for the bean or the 129 * interface(s) it implements 130 * @return This BeanScopeBuilder 131 */ 132 BeanScopeBuilder beans(Object... beans); 133 134 /** 135 * Add a supplied bean instance with the given injection type (typically an interface type). 136 * 137 * <p>If using this to provide a missing bean into the scope, Avaje will fail compilation unless 138 * it detects an {@code @InjectModule(requires)} including the missing class, or it detects a 139 * {@code @Nullable} annotation where the bean is wired. 140 * 141 * <pre>{@code 142 * Pump externalDependency = ... 143 * 144 * try (BeanScope scope = BeanScope.builder() 145 * .bean(Pump.class, externalDependency) 146 * .build()) { 147 * 148 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 149 * coffeeMaker.makeIt(); 150 * 151 * Pump pump = scope.get(Pump.class); 152 * assertThat(pump).isSameAs(externalDependency); 153 * } 154 * 155 * }</pre> 156 * 157 * @param type The dependency injection type this bean is target for 158 * @param bean The supplied bean instance to use for injection 159 */ 160 <D> BeanScopeBuilder bean(Class<D> type, D bean); 161 162 /** 163 * Add a supplied bean instance with the given name and injection type. 164 * 165 * <p>If using this to provide a missing bean into the scope, Avaje will fail compilation unless 166 * it detects an {@code @InjectModule(requires)} including the missing class, or it detects a 167 * {@code @Nullable} annotation where the bean is wired. 168 * 169 * @param name The name qualifier 170 * @param type The dependency injection type this bean is target for 171 * @param bean The supplied bean instance to use for injection 172 */ 173 <D> BeanScopeBuilder bean(String name, Class<D> type, D bean); 174 175 /** 176 * Add a supplied bean instance with the given name and generic type. 177 * 178 * <p>If using this to provide a missing bean into the scope, Avaje will fail compilation unless 179 * it detects an {@code @InjectModule(requires)} including the missing class, or it detects a 180 * {@code @Nullable} annotation where the bean is wired. 181 * 182 * @param name The name qualifier 183 * @param type The dependency injection type this bean is target for 184 * @param bean The supplied bean instance to use for injection 185 */ 186 <D> BeanScopeBuilder bean(String name, Type type, D bean); 187 188 /** 189 * Add a supplied bean instance with a generic type. 190 * 191 * <p>If using this to provide a missing bean into the scope, Avaje will fail compilation unless 192 * it detects an {@code @InjectModule(requires)} including the missing class, or it detects a 193 * {@code @Nullable} annotation where the bean is wired. 194 * 195 * @param type The dependency injection type this bean is target for 196 * @param bean The supplied bean instance to use for injection 197 */ 198 <D> BeanScopeBuilder bean(Type type, D bean); 199 200 /** 201 * Set the explicit profiles to use when building the scope. 202 * 203 * <p>If profiles are not set explicitly here they are read from the properties plugin. 204 */ 205 BeanScopeBuilder profiles(String... profiles); 206 207 /** 208 * Add a supplied bean provider that acts as a default fallback for a dependency. 209 * <p> 210 * This provider is only called if nothing else provides the dependency. It effectively 211 * uses `@Secondary` priority. 212 * 213 * @param type The type of the dependency 214 * @param provider The provider of the dependency. 215 */ 216 default <D> BeanScopeBuilder provideDefault(Type type, Supplier<D> provider) { 217 return provideDefault(null, type, provider); 218 } 219 220 /** 221 * Add a supplied bean provider that acts as a default fallback for a dependency. 222 * <p> 223 * This provider is only called if nothing else provides the dependency. It effectively 224 * uses `@Secondary` priority. 225 * 226 * @param name The name qualifier 227 * @param type The type of the dependency 228 * @param provider The provider of the dependency. 229 */ 230 <D> BeanScopeBuilder provideDefault(@Nullable String name, Type type, Supplier<D> provider); 231 232 /** 233 * Adds hooks that will execute after this scope is built. 234 */ 235 BeanScopeBuilder addPostConstruct(Runnable postConstructHook); 236 237 /** 238 * Adds hook that will execute after this scope is built. 239 */ 240 BeanScopeBuilder addPostConstruct(Consumer<BeanScope> postConstructHook); 241 242 /** 243 * Add hook that will execute before this scope is destroyed. 244 */ 245 BeanScopeBuilder addPreDestroy(AutoCloseable preDestroyHook); 246 247 /** 248 * Add hook with a priority that will execute before this scope is destroyed. 249 * <p> 250 * Specify the priority of the destroy method to control its execution 251 * order relative to other destroy methods. 252 * <p> 253 * Low values for priority execute earlier than high values. All destroy methods 254 * without any explicit priority are given a value of 1000. 255 */ 256 BeanScopeBuilder addPreDestroy(AutoCloseable preDestroyHook, int priority); 257 258 /** 259 * Set the ClassLoader to use when loading modules. 260 * 261 * @param classLoader The ClassLoader to use 262 */ 263 BeanScopeBuilder classLoader(ClassLoader classLoader); 264 265 /** 266 * Use the given BeanScope as the parent. This becomes an additional 267 * source of beans that can be wired and accessed in this scope. 268 * 269 * @param parent The BeanScope that acts as the parent 270 */ 271 BeanScopeBuilder parent(BeanScope parent); 272 273 /** 274 * Use the given BeanScope as the parent additionally specifying if beans 275 * added will effectively override beans that exist in the parent scope. 276 * <p> 277 * By default, child scopes will override a bean that exists in a parent scope. 278 * For testing purposes, parentOverride=false is used such that bean provided 279 * in parent test scopes are used (unless we mock() or spy() them). 280 * <p> 281 * See TestBeanScope in avaje-inject-test which has helper methods to build 282 * BeanScopes for testing with the "Global test scope" as a parent scope. 283 * 284 * @param parent The BeanScope that acts as the parent 285 * @param parentOverride When false do not add beans that already exist on the parent. 286 * When true add beans regardless of whether they exist in the parent scope. 287 */ 288 BeanScopeBuilder parent(BeanScope parent, boolean parentOverride); 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(Type type); 338 339 /** 340 * Register as a Mockito mock with a qualifier name. 341 * 342 * <pre>{@code 343 * 344 * try (BeanScope scope = BeanScope.builder() 345 * .forTesting() 346 * .mock(Store.class, "red") 347 * .mock(Store.class, "blue") 348 * .build()) { 349 * 350 * ... 351 * } 352 * 353 * }</pre> 354 */ 355 BeanScopeBuilder.ForTesting mock(Type type, String name); 356 357 /** 358 * Use a mockito mock when injecting this bean type additionally 359 * running setup on the mock instance. 360 * 361 * <pre>{@code 362 * 363 * try (BeanScope scope = BeanScope.builder() 364 * .forTesting() 365 * .mock(Pump.class) 366 * .mock(Grinder.class, grinder -> { 367 * 368 * // setup the mock 369 * when(grinder.grindBeans()).thenReturn("stub response"); 370 * }) 371 * .build()) { 372 * 373 * 374 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 375 * coffeeMaker.makeIt(); 376 * 377 * // this is a mockito mock 378 * Grinder grinder = scope.get(Grinder.class); 379 * verify(grinder).grindBeans(); 380 * } 381 * 382 * }</pre> 383 */ 384 <D> BeanScopeBuilder.ForTesting mock(Class<D> type, Consumer<D> consumer); 385 386 /** 387 * Use a mockito spy when injecting this bean type. 388 * 389 * <pre>{@code 390 * 391 * try (BeanScope scope = BeanScope.builder() 392 * .forTesting() 393 * .spy(Pump.class) 394 * .build()) { 395 * 396 * // setup spy here ... 397 * Pump pump = scope.get(Pump.class); 398 * doNothing().when(pump).pumpSteam(); 399 * 400 * // act 401 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 402 * coffeeMaker.makeIt(); 403 * 404 * verify(pump).pumpWater(); 405 * verify(pump).pumpSteam(); 406 * } 407 * 408 * }</pre> 409 */ 410 BeanScopeBuilder.ForTesting spy(Type type); 411 412 /** 413 * Register a Mockito spy with a qualifier name. 414 * 415 * <pre>{@code 416 * 417 * try (BeanScope scope = BeanScope.builder() 418 * .forTesting() 419 * .spy(Store.class, "red") 420 * .spy(Store.class, "blue") 421 * .build()) { 422 * 423 * ... 424 * } 425 * 426 * }</pre> 427 */ 428 BeanScopeBuilder.ForTesting spy(Type type, String name); 429 430 /** 431 * Use a mockito spy when injecting this bean type additionally 432 * running setup on the spy instance. 433 * 434 * <pre>{@code 435 * 436 * try (BeanScope scope = BeanScope.builder() 437 * .forTesting() 438 * .spy(Pump.class, pump -> { 439 * // setup the spy 440 * doNothing().when(pump).pumpWater(); 441 * }) 442 * .build()) { 443 * 444 * // or setup here ... 445 * Pump pump = scope.get(Pump.class); 446 * doNothing().when(pump).pumpSteam(); 447 * 448 * // act 449 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 450 * coffeeMaker.makeIt(); 451 * 452 * verify(pump).pumpWater(); 453 * verify(pump).pumpSteam(); 454 * } 455 * 456 * }</pre> 457 */ 458 <D> BeanScopeBuilder.ForTesting spy(Class<D> type, Consumer<D> consumer); 459 } 460}