001package io.avaje.inject; 002 003import java.lang.annotation.Annotation; 004import java.lang.reflect.Type; 005import java.util.List; 006import java.util.Map; 007import java.util.Optional; 008import java.util.Set; 009 010import org.jspecify.annotations.Nullable; 011 012/** 013 * Holds beans created by dependency injection. 014 * <p> 015 * The beans have singleton scope, support lifecycle methods for postConstruct and 016 * preDestroy and are created (wired) via dependency injection. 017 * </p> 018 * 019 * <h3>Create a BeanScope</h3> 020 * <p> 021 * We can programmatically create a BeanScope via {@code BeanScope.builder()}. 022 * </p> 023 * <pre>{@code 024 * 025 * // create a BeanScope ... 026 * 027 * try (BeanScope scope = BeanScope.builder() 028 * .build()) { 029 * 030 * CoffeeMaker coffeeMaker = context.get(CoffeeMaker.class); 031 * coffeeMaker.makeIt() 032 * } 033 * 034 * }</pre> 035 * 036 * <h3>External dependencies</h3> 037 * <p> 038 * We can supporting external dependencies when creating the BeanScope. 039 * Use the {@link io.avaje.inject.External @External} annotation. 040 * <p> 041 * For example, given we have Pump as an externally provided dependency. 042 * 043 * <pre>{@code 044 * 045 * class CoffeeMaker { 046 * // tell the annotation processor Pump is provided externally at runtime 047 * // otherwise it thinks we have a missing dependency 048 * @External Pump pump; 049 * } 050 * }</pre> 051 * <p> 052 * When building the BeanScope, the dependency must be provided manually via 053 * {@link BeanScopeBuilder#bean(Class, Object)}. 054 * 055 * <pre>{@code 056 * 057 * // provide external dependencies ... 058 * Pump pump = ... 059 * 060 * try (BeanScope scope = BeanScope.builder() 061 * .bean(Pump.class, pump) 062 * .build()) { 063 * 064 * CoffeeMaker coffeeMaker = context.get(CoffeeMaker.class); 065 * coffeeMaker.makeIt() 066 * } 067 * 068 * }</pre> 069 */ 070public interface BeanScope extends AutoCloseable { 071 072 /** 073 * Build a bean scope with options for shutdown hook and supplying external dependencies. 074 * <p> 075 * We can optionally: 076 * <ul> 077 * <li>Provide external dependencies</li> 078 * <li>Specify a parent BeanScope</li> 079 * <li>Specify specific modules to wire</li> 080 * <li>Specify to include a shutdown hook (to fire preDestroy lifecycle methods)</li> 081 * <li>Use {@code forTesting()} to specify mocks and spies to use when wiring tests</li> 082 * </ul> 083 * 084 * <pre>{@code 085 * 086 * // create a BeanScope ... 087 * 088 * try (BeanScope scope = BeanScope.builder() 089 * .build()) { 090 * 091 * CoffeeMaker coffeeMaker = context.get(CoffeeMaker.class); 092 * coffeeMaker.makeIt() 093 * } 094 * 095 * }</pre> 096 */ 097 static BeanScopeBuilder builder() { 098 return new DBeanScopeBuilder(); 099 } 100 101 /** 102 * Return a single bean given the type. 103 * 104 * <pre>{@code 105 * 106 * CoffeeMaker coffeeMaker = beanScope.get(CoffeeMaker.class); 107 * coffeeMaker.brew(); 108 * 109 * }</pre> 110 * 111 * @param type an interface or bean type 112 * @throws java.util.NoSuchElementException When no matching bean is found 113 */ 114 <T> T get(Class<T> type); 115 116 /** 117 * Return a single bean given the type and name. 118 * 119 * <pre>{@code 120 * 121 * Heater heater = beanScope.get(Heater.class, "electric"); 122 * heater.heat(); 123 * 124 * }</pre> 125 * 126 * @param type an interface or bean type 127 * @param name the name qualifier of a specific bean 128 * @throws java.util.NoSuchElementException When no matching bean is found 129 */ 130 <T> T get(Class<T> type, @Nullable String name); 131 132 /** 133 * Return a single bean given the full generic type. 134 * 135 * @param type The generic type 136 * @throws java.util.NoSuchElementException When no matching bean is found 137 */ 138 default <T> T get(Type type) { 139 return get(type, null); 140 } 141 142 /** 143 * Return a single bean given the full generic type and name. 144 * 145 * @param type The generic type 146 * @param name the name qualifier of a specific bean 147 * @throws java.util.NoSuchElementException When no matching bean is found 148 */ 149 <T> T get(Type type, @Nullable String name); 150 151 /** 152 * Optionally return a single bean given the type and empty if it is not found. 153 * 154 * @param type an interface or bean type 155 */ 156 <T> Optional<T> getOptional(Class<T> type); 157 158 /** 159 * Optionally return a single bean given the type and name and empty if it is not found. 160 * 161 * @param type an interface or bean type 162 * @param name the name qualifier of a specific bean 163 */ 164 <T> Optional<T> getOptional(Type type, @Nullable String name); 165 166 /** 167 * Return the list of beans that have an annotation. The annotation must have a @Retention policy of RUNTIME 168 * 169 * <pre>{@code 170 * 171 * // e.g. register all controllers with web a framework 172 * // .. where Controller is an annotation on the beans 173 * 174 * List<Object> controllers = beanScope.listByAnnotation(Controller.class); 175 * 176 * }</pre> 177 * 178 * @param annotation An annotation class. 179 */ 180 List<Object> listByAnnotation(Class<? extends Annotation> annotation); 181 182 /** 183 * Return the list of beans for a given type. 184 * 185 * <pre>{@code 186 * 187 * // e.g. register all routes for a web framework 188 * 189 * List<WebRoute> routes = beanScope.list(WebRoute.class); 190 * 191 * }</pre> 192 * 193 * @param type The type of beans to return. 194 */ 195 <T> List<T> list(Class<T> type); 196 197 /** 198 * Return the list of beans that implement the given type. 199 */ 200 <T> List<T> list(Type type); 201 202 /** 203 * Return the list of beans for the given type and name. 204 */ 205 <T> List<T> list(Type type, @Nullable String name); 206 207 /** 208 * Return the list of beans that implement the class sorting by priority. 209 */ 210 default <T> List<T> listByPriority(Class<T> type) { 211 return listByPriority((Type) type); 212 } 213 214 /** Return the list of beans that implement the type sorting by priority. */ 215 <T> List<T> listByPriority(Type type); 216 217 /** 218 * Return the beans for this type mapped by their qualifier name. 219 * <p> 220 * Beans with no qualifier name get a generated unique key to use instead. 221 */ 222 <T> Map<String, T> map(Type type); 223 224 /** 225 * Return all the bean entries from the scope. 226 * <p> 227 * The bean entries include entries from the parent scope if it has one. 228 * 229 * @return All bean entries from the scope. 230 */ 231 List<BeanEntry> all(); 232 233 /** 234 * Return true if the bean scope contains the given type. 235 */ 236 boolean contains(Type type); 237 238 /** 239 * Return true if the bean scope contains the given type. 240 */ 241 boolean contains(String type); 242 243 /** 244 * Close the scope firing any <code>@PreDestroy</code> lifecycle methods. 245 */ 246 @Override 247 void close(); 248 249 /** 250 * Return the custom scope annotations contained in this bean scope. 251 */ 252 default Set<String> customScopeAnnotations() { 253 return Set.of(); 254 } 255}