3.6 Limiting Injectable Types
By default when you annotate a bean with a scope such as @Singleton
the bean class and all interfaces it implements and super classes it extends from become injectable via @Inject
.
Consider the following example from the previous section on defining beans:
@Singleton
public class V8Engine implements Engine { (3)
@Override
public String start() {
return "Starting V8";
}
@Override
public int getCylinders() {
return 8;
}
}
@Singleton
class V8Engine implements Engine { (3)
int cylinders = 8
@Override
String start() {
"Starting V8"
}
}
@Singleton
class V8Engine : Engine {
override var cylinders: Int = 8
override fun start(): String {
return "Starting V8"
}
}
In the above case other classes in your application can choose to either inject the interface Engine
or the concrete implementation V8Engine
.
If this is undesirable you can use the typed
member of the @Bean annotation to limit the exposed types. For example:
@Singleton
@Bean(typed = Engine.class) (1)
public class V8Engine implements Engine { (2)
@Override
public String start() {
return "Starting V8";
}
@Override
public int getCylinders() {
return 8;
}
}
@Singleton
@Bean(typed = Engine) (1)
class V8Engine implements Engine { (2)
@Override
String start() { "Starting V8" }
@Override
int getCylinders() { 8 }
}
@Singleton
@Bean(typed = [Engine::class]) (1)
class V8Engine : Engine { (2)
override fun start(): String {
return "Starting V8"
}
override val cylinders: Int = 8
}
1 | @Bean(typed=..) is used to only allow injection the interface Engine and not the concrete type |
2 | The class must implement the class or interface defined by typed otherwise a compilation error will occur |
The following test demonstrates the behaviour of typed
using programmatic lookup and the BeanContext API:
@MicronautTest
public class EngineSpec {
@Inject
BeanContext beanContext;
@Test
public void testEngine() {
assertThrows(NoSuchBeanException.class, () ->
beanContext.getBean(V8Engine.class) (1)
);
final Engine engine = beanContext.getBean(Engine.class); (2)
assertTrue(engine instanceof V8Engine);
}
}
class EngineSpec extends Specification {
@Shared @AutoCleanup
ApplicationContext beanContext = ApplicationContext.run()
void 'test engine'() {
when:'the class is looked up'
beanContext.getBean(V8Engine) (1)
then:'a no such bean exception is thrown'
thrown(NoSuchBeanException)
and:'it is possible to lookup by the typed interface'
beanContext.getBean(Engine) instanceof V8Engine (2)
}
}
@MicronautTest
class EngineSpec {
@Inject
lateinit var beanContext: BeanContext
@Test
fun testEngine() {
assertThrows(NoSuchBeanException::class.java) {
beanContext.getBean(V8Engine::class.java) (1)
}
val engine = beanContext.getBean(Engine::class.java) (2)
assertTrue(engine is V8Engine)
}
}
1 | Trying to lookup V8Engine throws a NoSuchBeanException |
2 | Whilst looking up the Engine interface succeeds |