I have this Spring bean (this RestController
for the sake of the example) that, depending on the country (let’s say a param that comes in), I want to inject the right implementation of the TaxpayerNameService
.
So, I have that TaxpayerNameService
interface and two (more in the future) implementations of such interface which needs to be injected in the current call of the controller; I say current call because that same controller will be serving many countries and depending on the iso2 constant that I’m sending somewhere in (right now it comes from documentType.getCountry()
, I have to retrieve in runtime the right TaxpayerNameService
implementation and call that method getTaxpayerName
.
Each country has a different set of services, so each implementation of the interface does the right call to the right service.
@RestController @RequestMapping("/taxpayers") public class TaxpayerController { @Autowired @Qualifier("TaxpayerNameServiceImplHN") private TaxpayerNameService taxpayerNameServHN; @Autowired @Qualifier("TaxpayerNameServiceImplCR") private TaxpayerNameService taxpayerNameServCR; @GetMapping(path = "/{documentType}-{documentNumber}/name", produces = MediaType.TEXT_PLAIN_VALUE) public ResponseEntity<String> getName( final @PathVariable("documentType") TaxpayerDocumentType documentType, final @PathVariable("documentNumber") String documentNumber) throws NoSuchMethodException { try { final TaxpayerNameService taxpayerNameService = getTaxpayerNameServiceImpl(documentType.getCountry()); return ResponseEntity.of(taxpayerNameService.getTaxpayerName(documentType, documentNumber)); } catch (IOException ex) { log.error(String.format("Error querying [%s][%s]", documentType, documentNumber), ex); return ResponseEntity.internalServerError().build(); } } private TaxpayerNameService getTaxpayerNameServiceImpl(final String country) { switch(country) { case "CR": return taxpayerNameServCR; case "HN": return taxpayerNameServHN; default: throw new IllegalArgumentException("Invalid country"); } } }
What I want to do is a more elegant/spring way to do it, other than this ugly method getTaxpayerNameServiceImpl
.
Advertisement
Answer
Use BeanFactory
to create beans programatically:
import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.stereotype.Component; @Component public class TaxpayerNameServiceFactory implements BeanFactoryAware { private static final String BEAN_NAME_FORMAT = "TaxpayerNameServiceImpl%s"; private BeanFactory beanFactory; public TaxpayerNameService getTaxpayerNameServiceImpl(String countryName) { try { return (TaxpayerNameService) beanFactory.getBean(String.format(BEAN_NAME_FORMAT, countryName)); } catch(Exception e) { throw new TaxpayerNameServiceException(e.getMessage(), e); } } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } }
TaxpayerNameServiceImplCR class:
import org.springframework.stereotype.Component; @Component("TaxpayerNameServiceImplCR") public class TaxpayerNameServiceImplCR implements TaxpayerNameService { //All methods }
Rest controller class:
@RestController @RequestMapping("/taxpayers") public class TaxpayerController { @Autowired TaxpayerNameServiceFactory factory; @GetMapping(path = "/{documentType}-{documentNumber}/name", produces = MediaType.TEXT_PLAIN_VALUE) public ResponseEntity<String> getName( final @PathVariable("documentType") TaxpayerDocumentType documentType, final @PathVariable("documentNumber") String documentNumber) throws NoSuchMethodException { try { final TaxpayerNameService taxpayerNameService = factory.getTaxpayerNameServiceImpl(documentType.getCountry()); return ResponseEntity.of(taxpayerNameService.getTaxpayerName(documentType, documentNumber)); } catch (IOException ex) { log.error(String.format("Error querying [%s][%s]", documentType, documentNumber), ex); return ResponseEntity.internalServerError().build(); } } }