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