10

I've learned that in spring, i can autowire/inject into Map<String, SomeBeanInterface> by configured name like below:

public interface DummyInterface{
}

@Component("impl1")
public class Impl1 implement DummyInterface{
}

@Component("impl2")
public class Impl2 implement DummyInterface{
}

public class SomeUsage{
    @Autowired
    private Map<String, DummyInterface> mapping;
    // ...
}

and retrieve the Component by string as key like:

SomeUsage use = new SomeUsage();
DummyInterface dummy = use.getMapping().get("impl1");
// do sth...

However, if the key of bean mapping is not the type of String, but the type of user defined Enum, how should i inject the beans into the enumMap?

I've read some post and learned that it can be configured by xml file. But it seems to be that the xml configuration is tightly coupled with the <Enum, Bean> pair, which means that each time if i add a new <Enum, Bean> pair, i have to synchronize the configuration file, it seems that there's no difference comparing to my current solution, that is, still using the <String, Bean> collection and maintain the <Enum, String> mapping in java code by my own. Are there any better solution to handle this? Or do i miss something?

user8510613
  • 1,242
  • 9
  • 27

2 Answers2

19

You always have to define mapping between Enum and Spring Bean but you can enforce that components have to declare to which enumeration they are mapped to. You can acheive that creating interface like:

public interface EnumMappedBean {
    SomeEnum getSomeEnum();
}

Then every component that you want to be mapped has to implement it.

@Component
public class Bean1 implements EnumMappedBean {
    @Override
    public SomeEnum getSomeEnum() {
        return SomeEnum.ENUM1;
    }
}

@Component
public class Bean2 implements EnumMappedBean {
    @Override
    public SomeEnum getSomeEnum() {
        return SomeEnum.ENUM2;
    }
}

Then you can map each of this components by it's enumaration.

@Configuration
public class AppConfig {
    @Bean
    public Map<SomeEnum, EnumMappedBean> getBeansMappedByEnum(@NonNull Collection<EnumMappedBean> enumBeans) {
        return enumBeans.stream()
                .collect(toMap(EnumMappedBean::getSomeEnum, Function.identity()));
    }
}

And inject wherever you want.

@Service
public class SomeOtherBean {

    private Map<SomeEnum, EnumMappedBean> beansMappedByEnum;

    @Autowired
    public SomeOtherBean(@NonNull Map<SomeEnum, EnumMappedBean> beansMappedByEnum) {
        this.beansMappedByEnum = beansMappedByEnum;
    }
}

In config class you can also validate that every component declare uniqe, non-null enum value.

kemot90
  • 551
  • 3
  • 14
  • if i have a ```Map``` need to be ```@Autowired```. For class ```SomeBean```, it has a method ```getName```. I want to replace the default inject key, to use method like your post to replace the default map behaviour. How should i do? use ```@Resource(name= "...")``` ? – user8510613 Sep 19 '19 at 09:54
  • I'm afraid there is not out of the box solution to do that. Default DI behaviour is based on types/names of beans and it can't be changed by simple passing some parameters to annotations. – kemot90 Sep 19 '19 at 11:46
  • i know that i can use injection by name to handle this, but this way needs all user of the Bean to inject by name like ```@Resource(name = "...")```. – user8510613 Sep 20 '19 at 01:55
  • Only if you use `@Resource` annotation you need to know bean name. With `@Autowire` or `@Inject` you can obtain managed instances by its type or supertype. In example `SomeOtherBean` knows nothing about name in configuration class. Dependencies are resolved based on check if `Map` exists as manged component in application context. – kemot90 Sep 20 '19 at 07:53
-3

You must add @Qualifier("impl1") or @Qualifier("impl2") with @Autowired to inject your implementation class since the DummyInterface has multiple implementations.

    @Autowired
    @Qualifier("impl2") //Injects impl2 class
    private EnumMap<Your Enum, DummyInterface> mapping;

Further info on EnumMap, you can refer here

AMOGH G
  • 15
  • 6
  • The point is that i did want to inject multiple implementation of ```DummyInterface``` into the collection, and retrieve them by some key, just like injecting into string map. – user8510613 Sep 12 '19 at 04:20
  • When an interface has multiple implementations, the autowiring cannot happen without naming the qualifier class to pick the implementation. You are adding an Interface to a map, while retrieving you must know which type of implementation to retrieve. If you retrieve an interface, how can you use it without knowing which implementation required at the end? – AMOGH G Sep 12 '19 at 04:49
  • Please check the @Component annotation's value field, and https://stackoverflow.com/a/20180762/8510613 – user8510613 Sep 12 '19 at 06:01