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.newBuilder() 021 * .withBean(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.newBuilder() 044 * .withShutdownHook(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 withShutdownHook(boolean shutdownHook); 054 055 /** 056 * Specify the modules to include in dependency injection. 057 * <p> 058 * Only beans related to the module are included in the BeanScope that is built. 059 * <p> 060 * When we do not explicitly specify modules then all modules that are not "custom scoped" 061 * are found and used via service loading. 062 * 063 * <pre>{@code 064 * 065 * BeanScope scope = BeanScope.newBuilder() 066 * .withModules(new CustomModule()) 067 * .build()); 068 * 069 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 070 * coffeeMaker.makeIt(); 071 * 072 * }</pre> 073 * 074 * @param modules The modules that we want to include in dependency injection. 075 * @return This BeanScopeBuilder 076 */ 077 BeanScopeBuilder withModules(Module... modules); 078 079 /** 080 * Supply a bean to the scope that will be used instead of any 081 * similar bean in the scope. 082 * <p> 083 * This is typically expected to be used in tests and the bean 084 * supplied is typically a test double or mock. 085 * </p> 086 * 087 * <pre>{@code 088 * 089 * // external dependencies 090 * Pump pump = ... 091 * Grinder grinder = ... 092 * 093 * BeanScope scope = BeanScope.newBuilder() 094 * .withBeans(pump, grinder) 095 * .build(); 096 * 097 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 098 * coffeeMaker.makeIt(); 099 * 100 * }</pre> 101 * 102 * @param beans Externally provided beans used when injecting a dependency 103 * for the bean or the interface(s) it implements 104 * @return This BeanScopeBuilder 105 */ 106 BeanScopeBuilder withBeans(Object... beans); 107 108 /** 109 * Add a supplied bean instance with the given injection type (typically an interface type). 110 * 111 * <pre>{@code 112 * 113 * Pump externalDependency = ... 114 * 115 * try (BeanScope scope = BeanScope.newBuilder() 116 * .withBean(Pump.class, externalDependency) 117 * .build()) { 118 * 119 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 120 * coffeeMaker.makeIt(); 121 * 122 * Pump pump = scope.get(Pump.class); 123 * assertThat(pump).isSameAs(externalDependency); 124 * } 125 * 126 * }</pre> 127 * 128 * @param type The dependency injection type this bean is target for 129 * @param bean The supplied bean instance to use for injection 130 */ 131 <D> BeanScopeBuilder withBean(Class<D> type, D bean); 132 133 /** 134 * Add a supplied bean instance with the given name and injection type. 135 * 136 * @param name The name qualifier 137 * @param type The dependency injection type this bean is target for 138 * @param bean The supplied bean instance to use for injection 139 */ 140 <D> BeanScopeBuilder withBean(String name, Class<D> type, D bean); 141 142 /** 143 * Add a supplied bean instance with the given name and generic type. 144 * 145 * @param name The name qualifier 146 * @param type The dependency injection type this bean is target for 147 * @param bean The supplied bean instance to use for injection 148 */ 149 <D> BeanScopeBuilder withBean(String name, Type type, D bean); 150 151 /** 152 * Add a supplied bean instance with a generic type. 153 * 154 * @param type The dependency injection type this bean is target for 155 * @param bean The supplied bean instance to use for injection 156 */ 157 <D> BeanScopeBuilder withBean(Type type, D bean); 158 159 /** 160 * Use the given BeanScope as the parent. This becomes an additional 161 * source of beans that can be wired and accessed in this scope. 162 * 163 * @param parent The BeanScope that acts as the parent 164 */ 165 BeanScopeBuilder withParent(BeanScope parent); 166 167 /** 168 * Use the given BeanScope as the parent additionally specifying if beans 169 * added will effectively override beans that exist in the parent scope. 170 * 171 * @param parent The BeanScope that acts as the parent 172 * @param parentOverride When false do not add beans that already exist on the parent. 173 * When true add beans regardless of whether they exist in the parent scope. 174 */ 175 BeanScopeBuilder withParent(BeanScope parent, boolean parentOverride); 176 177 /** 178 * Extend the builder to support testing using mockito with 179 * <code>withMock()</code> and <code>withSpy()</code> methods. 180 * 181 * @return The builder with extra testing support for mockito mocks and spies 182 */ 183 BeanScopeBuilder.ForTesting forTesting(); 184 185 /** 186 * Build and return the bean scope. 187 * <p> 188 * The BeanScope is effectively immutable in that all components are created 189 * and all PostConstruct lifecycle methods have been invoked. 190 * <p> 191 * The beanScope effectively contains eager singletons. 192 * 193 * @return The BeanScope 194 */ 195 BeanScope build(); 196 197 /** 198 * Extends the building with testing specific support for mocks and spies. 199 */ 200 interface ForTesting extends BeanScopeBuilder { 201 202 /** 203 * Use a mockito mock when injecting this bean type. 204 * 205 * <pre>{@code 206 * 207 * try (BeanScope scope = BeanScope.newBuilder() 208 * .forTesting() 209 * .withMock(Pump.class) 210 * .withMock(Grinder.class) 211 * .build()) { 212 * 213 * 214 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 215 * coffeeMaker.makeIt(); 216 * 217 * // this is a mockito mock 218 * Grinder grinder = scope.get(Grinder.class); 219 * verify(grinder).grindBeans(); 220 * } 221 * 222 * }</pre> 223 */ 224 BeanScopeBuilder.ForTesting withMock(Class<?> type); 225 226 /** 227 * Register as a Mockito mock with a qualifier name. 228 * 229 * <pre>{@code 230 * 231 * try (BeanScope scope = BeanScope.newBuilder() 232 * .forTesting() 233 * .withMock(Store.class, "red") 234 * .withMock(Store.class, "blue") 235 * .build()) { 236 * 237 * ... 238 * } 239 * 240 * }</pre> 241 */ 242 BeanScopeBuilder.ForTesting withMock(Class<?> type, String name); 243 244 /** 245 * Use a mockito mock when injecting this bean type additionally 246 * running setup on the mock instance. 247 * 248 * <pre>{@code 249 * 250 * try (BeanScope scope = BeanScope.newBuilder() 251 * .forTesting() 252 * .withMock(Pump.class) 253 * .withMock(Grinder.class, grinder -> { 254 * 255 * // setup the mock 256 * when(grinder.grindBeans()).thenReturn("stub response"); 257 * }) 258 * .build()) { 259 * 260 * 261 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 262 * coffeeMaker.makeIt(); 263 * 264 * // this is a mockito mock 265 * Grinder grinder = scope.get(Grinder.class); 266 * verify(grinder).grindBeans(); 267 * } 268 * 269 * }</pre> 270 */ 271 <D> BeanScopeBuilder.ForTesting withMock(Class<D> type, Consumer<D> consumer); 272 273 /** 274 * Use a mockito spy when injecting this bean type. 275 * 276 * <pre>{@code 277 * 278 * try (BeanScope scope = BeanScope.newBuilder() 279 * .forTesting() 280 * .withSpy(Pump.class) 281 * .build()) { 282 * 283 * // setup spy here ... 284 * Pump pump = scope.get(Pump.class); 285 * doNothing().when(pump).pumpSteam(); 286 * 287 * // act 288 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 289 * coffeeMaker.makeIt(); 290 * 291 * verify(pump).pumpWater(); 292 * verify(pump).pumpSteam(); 293 * } 294 * 295 * }</pre> 296 */ 297 BeanScopeBuilder.ForTesting withSpy(Class<?> type); 298 299 /** 300 * Register a Mockito spy with a qualifier name. 301 * 302 * <pre>{@code 303 * 304 * try (BeanScope scope = BeanScope.newBuilder() 305 * .forTesting() 306 * .withSpy(Store.class, "red") 307 * .withSpy(Store.class, "blue") 308 * .build()) { 309 * 310 * ... 311 * } 312 * 313 * }</pre> 314 */ 315 BeanScopeBuilder.ForTesting withSpy(Class<?> type, String name); 316 317 /** 318 * Use a mockito spy when injecting this bean type additionally 319 * running setup on the spy instance. 320 * 321 * <pre>{@code 322 * 323 * try (BeanScope scope = BeanScope.newBuilder() 324 * .forTesting() 325 * .withSpy(Pump.class, pump -> { 326 * // setup the spy 327 * doNothing().when(pump).pumpWater(); 328 * }) 329 * .build()) { 330 * 331 * // or setup here ... 332 * Pump pump = scope.get(Pump.class); 333 * doNothing().when(pump).pumpSteam(); 334 * 335 * // act 336 * CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class); 337 * coffeeMaker.makeIt(); 338 * 339 * verify(pump).pumpWater(); 340 * verify(pump).pumpSteam(); 341 * } 342 * 343 * }</pre> 344 */ 345 <D> BeanScopeBuilder.ForTesting withSpy(Class<D> type, Consumer<D> consumer); 346 347 } 348}