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