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:

  1. @Singleton
  2. public class V8Engine implements Engine { (3)
  3. @Override
  4. public String start() {
  5. return "Starting V8";
  6. }
  7. @Override
  8. public int getCylinders() {
  9. return 8;
  10. }
  11. }
  1. @Singleton
  2. class V8Engine implements Engine { (3)
  3. int cylinders = 8
  4. @Override
  5. String start() {
  6. "Starting V8"
  7. }
  8. }
  1. @Singleton
  2. class V8Engine : Engine {
  3. override var cylinders: Int = 8
  4. override fun start(): String {
  5. return "Starting V8"
  6. }
  7. }

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:

  1. @Singleton
  2. @Bean(typed = Engine.class) (1)
  3. public class V8Engine implements Engine { (2)
  4. @Override
  5. public String start() {
  6. return "Starting V8";
  7. }
  8. @Override
  9. public int getCylinders() {
  10. return 8;
  11. }
  12. }
  1. @Singleton
  2. @Bean(typed = Engine) (1)
  3. class V8Engine implements Engine { (2)
  4. @Override
  5. String start() { "Starting V8" }
  6. @Override
  7. int getCylinders() { 8 }
  8. }
  1. @Singleton
  2. @Bean(typed = [Engine::class]) (1)
  3. class V8Engine : Engine { (2)
  4. override fun start(): String {
  5. return "Starting V8"
  6. }
  7. override val cylinders: Int = 8
  8. }
1@Bean(typed=..) is used to only allow injection the interface Engine and not the concrete type
2The 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:

  1. @MicronautTest
  2. public class EngineSpec {
  3. @Inject
  4. BeanContext beanContext;
  5. @Test
  6. public void testEngine() {
  7. assertThrows(NoSuchBeanException.class, () ->
  8. beanContext.getBean(V8Engine.class) (1)
  9. );
  10. final Engine engine = beanContext.getBean(Engine.class); (2)
  11. assertTrue(engine instanceof V8Engine);
  12. }
  13. }
  1. class EngineSpec extends Specification {
  2. @Shared @AutoCleanup
  3. ApplicationContext beanContext = ApplicationContext.run()
  4. void 'test engine'() {
  5. when:'the class is looked up'
  6. beanContext.getBean(V8Engine) (1)
  7. then:'a no such bean exception is thrown'
  8. thrown(NoSuchBeanException)
  9. and:'it is possible to lookup by the typed interface'
  10. beanContext.getBean(Engine) instanceof V8Engine (2)
  11. }
  12. }
  1. @MicronautTest
  2. class EngineSpec {
  3. @Inject
  4. lateinit var beanContext: BeanContext
  5. @Test
  6. fun testEngine() {
  7. assertThrows(NoSuchBeanException::class.java) {
  8. beanContext.getBean(V8Engine::class.java) (1)
  9. }
  10. val engine = beanContext.getBean(Engine::class.java) (2)
  11. assertTrue(engine is V8Engine)
  12. }
  13. }
1Trying to lookup V8Engine throws a NoSuchBeanException
2Whilst looking up the Engine interface succeeds