Let's say I have a "SomeInterface" and I have two springComponentImpl that implements that "SomeInterface".
I know I can autowire both implementations, together at once, with:
#Autowire
private List<SomeInterface> springComponentsImplList;
Could I inject them in a way like this?:
#Autowire
private Map<String,SomeInterface> springComponentsImplList;
So that way I could get the implementation by a "key"? It would be perfect if that key is the class name or something and generated automatically.
The following should work out-of-the-box, where the map contains the bean names as keys and the corresponding bean instances as values:
#Autowired
private Map<String, Foo> allBeansOfType;
But you may also try to get all bean instances of a particular type along with their names using ListableBeanFactory:
private Map<String, Foo> allBeansOfType;
#Autowired
public MyClass(ListableBeanFactory beanFactory) {
this.allBeansOfType = beanFactory.getBeansOfType(Foo.class);
}
You can inject the spring beans into two separate List
First approach : If you have only each of them, then name them differently and use #Qualifier
Service Interface
public interface ServiceInterf {
}
ImplementOne
#Service("implementOne")
public class ImplementOne implements ServiceInterf {
}
ImplementTwo
#Service("implementTwo")
public class ImplementTwo implements ServiceInterf {
}
And you can use #Qualifier
#Autowired
#Qualifier("implementOne")
private List<ServiceInterf> implementOne;
#Autowired
#Qualifier("implementTwo")
private List<ServiceInterf> implementTwo;
Second approach : If you have multiple of them, then you can declare them in config class using #Qualifier 5.2. Using #Qualifier to Select Beans
#Bean
#Qualifier("implementOne")
public ServiceInterf getServiceInterf1() {
return new ImplementOne();
}
#Bean
#Qualifier("implementOne")
public ServiceInterf getServiceInterf2() {
return new ImplementOne();
}
#Bean
#Qualifier("implementTwo")
public ServiceInterf getServiceInterf3() {
return new ImplementTwo();
}
#Bean
#Qualifier("implementTwo")
public ServiceInterf getServiceInterf4() {
return new ImplementTwo();
}
And also if you want to do them into Map use constructor injection
private Map<String, List<ServiceInterf>> mapOfLists;
#Autowired
public TestMap(#Qualifier("implementOne") List<ServiceInterf> implementOne,
#Qualifier("implementTwo") List<ServiceInterf> implementTwo) {
mapOfLists = Map.of("implementOne",implementOne,"implementTwo",implementTwo);
// Map.of is from java 9
}
Related
I'm trying to create a class that Autowire an object of type T.
#component
public class TaskScheduler<T extends TaskService>{
#Autowired
private T taskService;
}
the problem is that I have two components that extend TaskService.
#component
public class firstTaskService extends TaskService {
}
and
#component
public class secondTaskService extends TaskService {
}
so when this line is executed (ts is being created)
#Autowired
TaskScheduler<firstTaskService> ts;
I get this error :
Description:
Parameter 1 of constructor in TaskScheduler required a single bean, but 2 were found
the message I got suggested this :
Action: Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify
the bean that should be consumed.
But from what I understood, the #Primary and #Qualifier annotations make me choose 1 of the components, which not what I want because I want to use firstTaskService and secondTaskService with that same class (TaskScheduler).
How could this be done?
Edit: Clarification: My objective is to reuse the TaskScheduler class with different classes that extend the TaskService class (not to use multiple classes that extend TaskService together in TaskScheduler).
If you want to autowire all beans that extends TaskService maybe you should change the autowired field to a List:
#Component
public class TaskScheduler<T extends TaskService>{
#Autowired
private List<T> taskService;
}
In this way Spring should put in the List all autowireable beans that extends TaskService.
EDIT: since you want to dinamically select the type of TaskService the only way I've found is the following. First, redefine your TaskScheduler:
public class TaskScheduler <T extends TaskService>{
private T taskService;
public void setTaskService(T taskService) {
this.taskService = taskService;
}
}
Your TaskService and related subclasses should remain untouched. Set up a configuration class as it follows:
#Configuration
public class TaskConf {
#Autowired
private FirstTaskService firstTaskService;
#Autowired
private SecondTaskService secondTaskService;
#Bean
public TaskScheduler<FirstTaskService> firstTaskServiceTaskScheduler(){
TaskScheduler<FirstTaskService> t = new TaskScheduler<>();
t.setTaskService(firstTaskService);
return t;
}
#Bean
public TaskScheduler<SecondTaskService> secondTaskServiceTaskScheduler(){
TaskScheduler<SecondTaskService> t = new TaskScheduler<>();
t.setTaskService(secondTaskService);
return t;
}
}
And then test your TaskScheduler in this way:
#Autowired
TaskScheduler<firstTaskService> ts;
I have a service that implements an interface. I now want to write a mapper, that basically says, when I pass in this enum type, use that service. This is what I have
#Service
MyService implements Service {}
#Component
#RequiredArgsConstructor
MyMapper implements Mapper<Enum, Service> {
private final MyService myService;
private ImmutableMap<Enum, Service> MAPPER = ImmutableMap.<MyEnum, MyService>builder()
.put(Enum.A, myService)
.build();;
#Override
public Service map(Enum input) {
return MAPPER.get(input);
}
}
However, it seems that this doesn't work. I think I am not allowed to use an (autowired) instance variable for the instantiation of another instance variable.
To solve this I now used a singleton pattern.
#Service
MyService implements Service {}
#Component
#RequiredArgsConstructor
MyMapper implements Mapper<Enum, Service> {
private final MyService myService;
private ImmutableMap<Enum, Service> MAPPER = null;
#Override
public Service map(Enum input) {
if(MAPPER == null){
MAPPER = createMapper();
}
return MAPPER.get(input);
}
private ImmutableMap<Enum, Service> createMapper(){
return ImmutableMap.<MyEnum, MyService>builder()
.put(Enum.A, myService)
.build();;
}
}
This seems to work, but I was wondering if there were other options to solve this.
For this problem service locator is best fit.
My Enum:-
public enum MyEnum {
A,
B
}
Create service and with the name "A" and "B" (Name of your enum as string):-
#Service("A")
MyService1 implements Service {}
#Service("B")
MyService2 implements Service {}
Create MyMapper interface:-
public interface MyMapper {
Service map(MyEnum myEnum);
}
Configure ServiceLocatorFactoryBean :-
#Bean
public ServiceLocatorFactoryBean serviceLocatorFactoryBean(){
ServiceLocatorFactoryBean bean = new ServiceLocatorFactoryBean();
bean.setServiceLocatorInterface(MyMapper.class);
return bean;
}
Start using:-
#Autowired
MyMapper mapper;
You are running into an edge case regarding final variables; even though it's marked final, the map's initializer statement runs before instance initializer blocks (which would otherwise be useful), which run before the constructor body that makes the assignment to the variable.
I'm not certain why you're wanting to create a map just to hold a singleton value, but you'll need to assign the map inside your constructor body. If you really want this setup for some reason, my suggestion would be to do this:
private final Map<Enum, Service> MAPPER;
public MyMapper(MyService myService) {
MAPPER = Map.of(Enum.A, myService);
}
I am using SpringBoot in my application and am currently using applicationContext.getBean(beanName,beanClass) to get my bean before performing operations on it. I saw in a couple of questions that it is discouraged to use getBean(). Since I am very new to Spring I don't know all the best practices and am conflicted. The solutions posed in the above linked question probably won't work in my use case. How should I approach this?
#RestController
#RequestMapping("/api")
public class APIHandler {
#Value("${fromConfig}")
String fromConfig;
private ApplicationContext applicationContext;
public Bot(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#PostMapping(value = "")
public ResponseEntity post(#RequestBody HandlingClass requestBody) {
SomeInterface someInterface = applicationContext.getBean(fromConfig, SomeInterface.class);
someInterface.doSomething();
}
}
I have an interface called SomeInterface defined like:
public interface SomeInterface {
void doSomething();
}
And I have 2 classes which implements this interface called UseClass1 and UseClass2. My config file stores a string with the bean name of a class which I need to know in run-time and call the appropriate implementation of the method.
Any directions would be appreciated.
Since Spring 4.3 you can autowire all implementations into a Map consisting of pairs beanName <=> beanInstance:
public class APIHandler {
#Autowired
private Map<String, SomeInterface> impls;
public ResponseEntity post(#RequestBody HandlingClass requestBody) {
String beanName = "..."; // resolve from your requestBody
SomeInterface someInterface = impls.get(beanName);
someInterface.doSomething();
}
}
assuming you have two implementations like following
// qualifier can be omitted, then it will be "UseClass1" by default
#Service("beanName1")
public class UseClass1 implements SomeInterface { }
// qualifier can be omitted, then it will be "UseClass2" by default
#Service("beanName2")
public class UseClass2 implements SomeInterface { }
This is only code works for me to get beans dynamically from ApplicationContext
#Service
public class AuthenticationService {
#Autowired
private ApplicationContext сontext;
public boolean authenticate(...) {
boolean useDb = ...; //got from db
IAuthentication auth = context.getBean(useDb ? DbAuthentication.class : LdapAuthentication.class);
return auth.authenticate(...);
}
}
You can define your spring bean component with
#Profile("dev") , #Profile("test")
and inject as mention comment, then switch profile with
-Dspring.profiles.active=test jvm argument
The real question is not how to solve this, but why would you inject something different based on a configuration value?
If the answer is testing, then perhaps it's better to use #Profiles as #murat suggested.
Why are different implementations of an interface there on your classpath?
Can't you package your application in a way that only one is there for one use case? (see ContextConfiguration)
I think you should probably use a configuration class to produce your bean based on the fromConfig string value:
Your controller:
#RestController
#RequestMapping("/api")
public class APIHandler {
#Autowired
SomeInterface someInterface;
#PostMapping(value = "")
public ResponseEntity post(#RequestBody HandlingClass requestBody) {
someInterface.doSomething();
}
}
The bean producer:
#Configuration
public class SomeInterfaceProducer {
#Value("${fromConfig}")
String fromConfig;
#Bean
public SomeInterface produce() {
if (fromConfig.equals("aValueForUseClass1") {
return new UseClass1();
} else {
return new UseClass2();
}
//...
}
}
or if you have DI in UseClass1 and/or UseClass2:
#Configuration
public class SomeInterfaceProducer {
#Value("${fromConfig}")
String fromConfig;
#Bean
public SomeInterface produce(#Autowired YourComponent yourComponent) {
SomeInterface someInterface;
if (fromConfig.equals("aValueForUseClass1") {
someInterface = new UseClass1();
someInterface.setYourComponent(yourComponent);
// or directly with the constructor if you have one with yourComponent as parameter.
} else {
someInterface = new UseClass2();
someInterface.setYourComponent(yourComponent);
}
//...
}
}
In a java-spring web-app I would like to be able to dynamically inject beans.
For example I have an interface with 2 different implementations:
In my app I'm using some properties file to configure injections:
#Determines the interface type the app uses. Possible values: implA, implB
myinterface.type=implA
My injections actually loaded conditionally relaying on the properties values in the properties file. For example in this case myinterface.type=implA wherever I inject MyInterface the implementation that will be injected will be ImplA (I accomplished that by extending the Conditional annotation).
I would like that during runtime - once the properties are changed the following will happen (without server restart):
The right implementation will be injected. For example when setting myinterface.type=implB ImplB will be injected where-ever MyInterface is used
Spring Environment should be refreshed with the new values and re-injected as well to beans.
I thought of refreshing my context but that creates problems.
I thought maybe to use setters for injection and re-use those setters once properties are re-configured. Is there a working practice for such a requirement?
Any ideas?
UPDATE
As some suggested I can use a factory/registry that holds both implementations (ImplA and ImplB) and returns the right one by querying the relevant property.
If I do that I still have the second challenge - the environment. for example if my registry looks like this:
#Service
public class MyRegistry {
private String configurationValue;
private final MyInterface implA;
private final MyInterface implB;
#Inject
public MyRegistry(Environmant env, MyInterface implA, MyInterface ImplB) {
this.implA = implA;
this.implB = implB;
this.configurationValue = env.getProperty("myinterface.type");
}
public MyInterface getMyInterface() {
switch(configurationValue) {
case "implA":
return implA;
case "implB":
return implB;
}
}
}
Once property has changed I should re-inject my environment. any suggestions for that?
I know I can query that env inside the method instead of constructor but this is a performance reduction and also I would like to think of an ider for re-injecting environment (again, maybe using a setter injection?).
I would keep this task as simple as possible. Instead of conditionally load one implementation of the MyInterface interface at startup and then fire an event that triggers dynamic loading of another implementation of the same interface, I would tackle this problem in a different way, that is much simpler to implement and maintain.
First of all, I'd just load all possible implementations:
#Component
public class MyInterfaceImplementationsHolder {
#Autowired
private Map<String, MyInterface> implementations;
public MyInterface get(String impl) {
return this.implementations.get(impl);
}
}
This bean is just a holder for all implementations of the MyInterface interface. Nothing magic here, just common Spring autowiring behavior.
Now, wherever you need to inject a specific implementation of MyInterface, you could do it with the help of an interface:
public interface MyInterfaceReloader {
void changeImplementation(MyInterface impl);
}
Then, for every class that needs to be notified of a change of the implementation, just make it implement the MyInterfaceReloader interface. For instance:
#Component
public class SomeBean implements MyInterfaceReloader {
// Do not autowire
private MyInterface myInterface;
#Override
public void changeImplementation(MyInterface impl) {
this.myInterface = impl;
}
}
Finally, you need a bean that actually changes the implementation in every bean that has MyInterface as an attribute:
#Component
public class MyInterfaceImplementationUpdater {
#Autowired
private Map<String, MyInterfaceReloader> reloaders;
#Autowired
private MyInterfaceImplementationsHolder holder;
public void updateImplementations(String implBeanName) {
this.reloaders.forEach((k, v) ->
v.changeImplementation(this.holder.get(implBeanName)));
}
}
This simply autowires all beans that implement the MyInterfaceReloader interface and updates each one of them with the new implementation, which is retrieved from the holder and passed as an argument. Again, common Spring autowiring rules.
Whenever you want the implementation to be changed, you should just invoke the updateImplementations method with the name of the bean of the new implementation, which is the lower camel case simple name of the class, i.e. myImplA or myImplB for classes MyImplA and MyImplB.
You should also invoke this method at startup, so that an initial implementation is set on every bean that implements the MyInterfaceReloader interface.
I solved a similar issue by using org.apache.commons.configuration.PropertiesConfiguration and org.springframework.beans.factory.config.ServiceLocatorFactoryBean:
Let VehicleRepairService be an interface:
public interface VehicleRepairService {
void repair();
}
and CarRepairService and TruckRepairService two classes that implements it:
public class CarRepairService implements VehicleRepairService {
#Override
public void repair() {
System.out.println("repair a car");
}
}
public class TruckRepairService implements VehicleRepairService {
#Override
public void repair() {
System.out.println("repair a truck");
}
}
I create an interface for a service factory:
public interface VehicleRepairServiceFactory {
VehicleRepairService getRepairService(String serviceType);
}
Let use Config as configuration class:
#Configuration()
#ComponentScan(basePackages = "config.test")
public class Config {
#Bean
public PropertiesConfiguration configuration(){
try {
PropertiesConfiguration configuration = new PropertiesConfiguration("example.properties");
configuration
.setReloadingStrategy(new FileChangedReloadingStrategy());
return configuration;
} catch (ConfigurationException e) {
throw new IllegalStateException(e);
}
}
#Bean
public ServiceLocatorFactoryBean serviceLocatorFactoryBean() {
ServiceLocatorFactoryBean serviceLocatorFactoryBean = new ServiceLocatorFactoryBean();
serviceLocatorFactoryBean
.setServiceLocatorInterface(VehicleRepairServiceFactory.class);
return serviceLocatorFactoryBean;
}
#Bean
public CarRepairService carRepairService() {
return new CarRepairService();
}
#Bean
public TruckRepairService truckRepairService() {
return new TruckRepairService();
}
#Bean
public SomeService someService(){
return new SomeService();
}
}
By using FileChangedReloadingStrategy your configuration be reload when you change the property file.
service=truckRepairService
#service=carRepairService
Having the configuration and the factory in your service, let you can get the appropriate service from the factory using the current value of the property.
#Service
public class SomeService {
#Autowired
private VehicleRepairServiceFactory factory;
#Autowired
private PropertiesConfiguration configuration;
public void doSomething() {
String service = configuration.getString("service");
VehicleRepairService vehicleRepairService = factory.getRepairService(service);
vehicleRepairService.repair();
}
}
Hope it helps.
If I understand you correctly then the goal is not to replace injected object instances but to use different implementations during interface method call depends on some condition at run time.
If it is so then you can try to look at the Sring TargetSource mechanism in combination with ProxyFactoryBean. The point is that proxy objects will be injected to beans that uses your interface, and all the interface method calls will be sent to TargetSource target.
Let's call this "Polymorphic Proxy".
Have a look at example below:
ConditionalTargetSource.java
#Component
public class ConditionalTargetSource implements TargetSource {
#Autowired
private MyRegistry registry;
#Override
public Class<?> getTargetClass() {
return MyInterface.class;
}
#Override
public boolean isStatic() {
return false;
}
#Override
public Object getTarget() throws Exception {
return registry.getMyInterface();
}
#Override
public void releaseTarget(Object target) throws Exception {
//Do some staff here if you want to release something related to interface instances that was created with MyRegistry.
}
}
applicationContext.xml
<bean id="myInterfaceFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="MyInterface"/>
<property name="targetSource" ref="conditionalTargetSource"/>
</bean>
<bean name="conditionalTargetSource" class="ConditionalTargetSource"/>
SomeService.java
#Service
public class SomeService {
#Autowired
private MyInterface myInterfaceBean;
public void foo(){
//Here we have `myInterfaceBean` proxy that will do `conditionalTargetSource.getTarget().bar()`
myInterfaceBean.bar();
}
}
Also if you want to have both MyInterface implementations to be Spring beans, and the Spring context could not contains both instances at the same time then you can try to use ServiceLocatorFactoryBean with prototype target beans scope and Conditional annotation on target implementation classes. This approach can be used instead of MyRegistry.
P.S.
Probably Application Context refresh operation also can do what you want but it can cause other problems such as performance overheads.
This may be a duplicate question or at least very similar, anyway I answered this sort of question here: Spring bean partial autowire prototype constructor
Pretty much when you want a different beans for a dependency at run-time you need to use a prototype scope. Then you can use a configuration to return different implementations of the prototype bean. You will need to handle the logic on which implementation to return yourself, (they could even be returning 2 different singleton beans it doesn't matter) But say you want new beans, and the logic for returning the implementation is in a bean called SomeBeanWithLogic.isSomeBooleanExpression(), then you can make a configuration:
#Configuration
public class SpringConfiguration
{
#Bean
#Autowired
#Scope("prototype")
public MyInterface createBean(SomeBeanWithLogic someBeanWithLogic )
{
if (someBeanWithLogic .isSomeBooleanExpression())
{
return new ImplA(); // I could be a singleton bean
}
else
{
return new ImplB(); // I could also be a singleton bean
}
}
}
There should never be a need to reload the context. If for instance, you want the implementation of a bean to change at run-time, use the above. If you really need to reload your application, because this bean was used in constructors of a singleton bean or something weird, then you need to re-think your design, and if these beans are really singleton beans. You shouldn't be reloading the context to re-create singleton beans to achieve different run-time behavior, that is not needed.
Edit The first part of this answer answered the question about dynamically injecting beans. As asked, but I think the question is more of one: 'how can I change the implementation of a singleton bean at run-time'. This could be done with a proxy design pattern.
interface MyInterface
{
public String doStuff();
}
#Component
public class Bean implements MyInterface
{
boolean todo = false; // change me as needed
// autowire implementations or create instances within this class as needed
#Qualifier("implA")
#Autowired
MyInterface implA;
#Qualifier("implB")
#Autowired
MyInterface implB;
public String doStuff()
{
if (todo)
{
return implA.doStuff();
}
else
{
return implB.doStuff();
}
}
}
You can use #Resource annotation for injection as originally answered here
e.g.
#Component("implA")
public class ImplA implements MyInterface {
...
}
#Component("implB")
public class ImplB implements MyInterface {
...
}
#Component
public class DependentClass {
#Resource(name = "\${myinterface.type}")
private MyInterface impl;
}
and then set the implementation type in properties file as -
myinterface.type=implA
Be aware that - if interesting to know about - FileChangedReloadingStrategy makes your project highly dependent on the deployment conditions: the WAR/EAR should be exploded by container and your should have direct access to the file system, conditions that are not always met in all situations and environments.
You can use Spring #Conditional on a property value. Give both Beans the same name and it should work as only one Instance will be created.
Have a look here on how to use #Conditional on Services and Components:
http://blog.codeleak.pl/2015/11/how-to-register-components-using.html
public abstract class SystemService {
}
public class FooSystemService extends FileSystemService {
}
public class GoSystemService extends FileSystemService {
}
#Configuration
public class SystemServiceConf {
#Bean
#Conditional(SystemServiceCondition.class)
public SystemService systemService(#Value("${value.key}") value) {
switch (value) {
case A:
return new FooSystemService();
case B:
return new GoSystemService();
default:
throw new RuntimeException("unknown value ");
}
}
}
public class SystemServiceCondition implements Condition {
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return true;
}
}
with Java based configuration, i am trying to convert a map that maps enums to bean references to be in pure java config (currently in XML & works) but can't seem to find anything in the documentations;
Currently, my XML like so;
<util:map id="colourHanders" key-type="com.example.ColourEnum"
value-type="com.example.ColourHandler">
<entry key="white" value-ref="whiteColourHandler"/>
<entry key="blue" value-ref="blueColourHandler"/>
<entry key="red" value-ref="redColourHandler"/>
</util:map>
I'm sure it is easy but again, can't find anything on the subject of how to represent this in Pure Java (so I don't have any XML configuration files)..
Note; the ColourHandler beans are created using the #Component annotation, e.g..
#Component
public class RedColourHandler implements ColourHander{
.....
}
and the map of colourHandlers is referenced as so;
#Resource(name="colourHandlers")
private Map<ColourHandlerEnum, ColourHandler> colourHandlers;
Thanks,
Ian.
You probably want something like this:
#Configuration
public class MyConfiguration {
#Bean public Map<ColourEnum, ColourHandler> colourHandlers() {
Map<ColourEnum, ColourHandler> map = new EnumMap<>();
map.put(WHITE, whiteHandler());
// etc
return map;
}
#Bean public ColourHandler whiteHandler() {
return new WhiteHandler();
}
}
If you need to keep your handlers as #Components, then you can autowire them into the configuration class:
#Configuration
public class MyConfiguration {
#Autowired private WhiteColourHandler whiteColourHandler;
#Bean public Map<ColourEnum, ColourHandler> colourHandlers() {
Map<ColourEnum, ColourHandler> map = new EnumMap<>();
map.put(WHITE, whiteColourHandler);
return map;
}
}
Since you already have a unique class/#Component for each ColorHandler, I would just let Spring figure out what to use (no need for #Autowire injection nor any additional creation methods):
#Configuration
public class MyConfiguration {
#Bean public Map<ColourEnum, ColourHandler> colourHandlers(
WhiteColourHandler whiteHandler,
BlueColourHandler blueHandler,
RedColourHandler redHandler) {
Map<ColourEnum, ColourHandler> map = new EnumMap<>();
map.put(WHITE, whiteHandler);
map.put(BLUE, blueHandler);
map.put(RED, redHandler);
return map;
}
}
Similar to the accepted answer except that, instead of autowiring components, you can declare the beans in the configuration class as usual and pass them as arguments to the Map bean method:
#Configuration
public class MyConfiguration {
#Bean public Map<ColourEnum, ColourHandler> colourHandlers(ColourHandler whiteHandler) {
Map<ColourEnum, ColourHandler> map = new EnumMap<>();
map.put(WHITE, whiteHandler);
return map;
}
#Bean public ColourHandler whiteHandler() {
return new WhiteHandler();
}
}
Also note that the injection of the map as a #Resource doesn't need the annotation's "name" parameter if the field name follows the same naming convention as the bean definition.
i.e. This would work without the name parameter:
#Resource
private Map<ColourHandlerEnum, ColourHandler> colourHandlers;
but this would require it:
#Resource(name="colourHandlers")
private Map<ColourHandlerEnum, ColourHandler> handlers;
This is actually pretty simple but you need to know how:
#Autowired private ColourHandler whiteColourHandler;
...
public Map<ColourEnum, ColourHandler> getColourHander() {
Map<ColourEnum, ColourHandler> result = ...;
map.put( ColourEnum.white, whiteColourHandler );
...
return map;
}
The trick is that you can inject beans into a config.
You can have the ColourHandler class itself define its own type, this way you will not have to keep changing your config class when you get new types:
public interface ColourHander {
ColourEnum getType();
}
#Component
public class RedColourHandler implements ColourHander{
#Override
public ColourEnum getType() {
return ColourEnum.RED;
}
}
And in your config class you get all ColourHandlers and map them to their type.
#Configuration
public class MyConfiguration {
#Bean
public Map<ColourEnum, ColourHandler> colourHandlers(List<ColourHandler> colourHandlers) {
return colourHandlers.stream().collect(toMap(ColourHandler::getType, x -> x));
}
}
Note that you will get an IllegalStateException in case you have more than one handler per color, which I guess is the expected behaviour, you can catch it and throw your own.