Home > Software design >  How to inject the right bean implementation according to a RequestParam in the current call
How to inject the right bean implementation according to a RequestParam in the current call

Time:12-20

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));

    }

}
  • Related