4

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.

4
  • Inject or Autowired the bean, you need use like @Autowired private SomeInterface someInterface; Commented Feb 20, 2018 at 11:18
  • I have thought of using @Autowired in the class where I am implementing the API but I am lost as to how I could call the correct implementation of someInterface Commented Feb 20, 2018 at 11:21
  • perhaps someImpl? :) Commented Feb 20, 2018 at 11:29
  • @BalázsMáriaNémeth sorry didn't get that :( Commented Feb 20, 2018 at 11:37

5 Answers 5

6

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 { } 
Sign up to request clarification or add additional context in comments.

4 Comments

This works for me perfectly! Just one question, Does this method doesn't go against the principles of DI?
What do you mean? @Autowired mechanism is purposed to maintain DI, hence it can't go against DI principles
how is the map populated?
it can even be caught by it's name like "useClass1" , "useClass2" since u don't define a name for it
2

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(...); } }

3 Comments

@Qualifier("student1") is only possible if I know what the qualifier is beforehand but my use case is slightly more complex. I will get the bean name either from the config file or from the user dynamically. For eg. I might get the bean name from the POST request body.
I hope this helps you. May be you go with this approach
Is there any difference between what wrote and what you answered? applicationContext.getBean(fromConfig, SomeInterface.class); context.getBean(useDb ? DbAuthentication.class : LdapAuthentication.class);
2

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); } //... } } 

3 Comments

I see that in the configuration file I need to hard code the aValueForUseClass1, so if I increase the number of classes implementing the interface, I would need to modify this configuration. Is this in line with the principles of DI?
Well, I understand. If you increase the number of classes, you have to use a @Qualifier for each of them, then You've got your aValueForUseClassX. And yes if you have a logical to get a specific implementation, you will have to modifiy the Bean Producer class of course and you should. If you want to deport the generation of bean to not use applicationContext.getBean it's a valid solution IMO, but that's one possible and non profile specific.
Will the above code work as is? Do you need to autowire the SomeInterfaceProducer somewhere in the controller?
2

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 

1 Comment

Isn't profiles used as run profiles? By bean name wouldn't change from dev to test to production. Rather it would change for each call of the API class that I have shown.
1

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)

2 Comments

No, the purpose is very much in production. There are multiple implementations if an interface because there is a chance of expanding on that interface. For eg. Each class might represent a table structure and I might have a generic API endpoint for calling the correct implementation based on the API data.
It's not too much information but based on that I think I would design this differently. But I would need much more information how to do so.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.