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