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
.
CodePudding user response:
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();
}
}
}
CodePudding user response:
or maybe this?
public enum Country {
HR("HR", TaxpayerNameServiceImplHR.class),
CR("CR", TaxpayerNameServiceImplCR.class);
private String code;
private Class<? extends TaxpayerNameService> serviceClass;
Country(String code, Class<? extends TaxpayerNameService> svcClass) {
this.code = code;
this.serviceClass = svcClass;
}
public String getCode() {
return code;
}
public Class<? extends TaxpayerNameService> getServiceClass() {
return serviceClass;
}
public static Optional<Country> findCountry(String code) {
return java.util.stream.Stream.of(Country.values())
.filter(country -> code.equals(country.getCode()))
.findFirst();
}
}
public class TaxPayerController {
@Autowired
private ApplicationContext context;
public ResponseEntity<String> getName(final @PathVariable("documentType") TaxpayerDocumentType documentType,
final @PathVariable("documentNumber") String documentNumber) throws NoSuchMethodException {
Optional<Country> optionalCountry = Country.findCountry(documentType.getCountry());
if (optionalCountry.isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
}
final TaxpayerNameService taxpayerNameService = context.getBean(optionalCountry.get().getServiceClass());
return ResponseEntity.ok(taxpayerNameService.getTaxpayerName(documentType, documentNumber));
}
}