The beans that are injected in the rest controller don't vary with the http request content. What you can do is factor your request_type as a path variable and create the desired chains in separate http mappings like so:
@PostMapping(value = "/request_type_A")
public Object handle1(args...){
// Validator1 --> Processor1 --> DAO1
}
@PostMapping(value = "/request_type_B")
public Object handle2(args...){
// Validator2 --> Processor2 --> DAO2
}
If this is not practical for whatever reason and you must specify the type dynamically in the @RequestBody, @PathVariable or @RequestParam, then I would suggest implementing a resolver bean similar to this:
@Component
public class Resolver {
private final RequestTypeAValidator requestTypeAValidator;
private final RequestTypeBValidator requestTypeBValidator;
...
public IValidator getValidator(String requestType) {
switch (requestType) {
case "request_type_A":
return requestTypeAValidator;
case "request_type_B":
return requestTypeBValidator;
default:
throw new IllegalArgumentException("cannot find validator");
}
}
}
The drawback of this approach is that it does not comply with the "Open-Closed" principle in the sense that for any new request type, you will need to edit the resolvers. That can be fixed by using a HashMap in the resolver and letting the beans register themselves to that map on @PostConstruct:
@Component
public class Resolver {
private final Map<String, IValidator> validators = new HashMap<>();
public IValidator getValidator(String requestType) {
IValidator result = validators.get(requestType);
if (Objects.isNull(result)) {
throw new IllegalArgumentException("cannot find validator");
}
return result;
}
public void register(String type, IValidator validator) {
validators.put(type, validator)
}
}
@Component
public class ValidatorA implements IValidator {
private final Resolver resolver;
@PostConstruct
private void register() {
resolver.register("request_type_A", this);
}
...
}
However, in this approach there is a direct dependency from all implementations back to the Resolver.
Lastly, you could inject dynamically like so:
@Component
public class Resolver {
private final ApplicationContext applicationContext;
...
public IValidator getValidator(String requestType) {
switch (requestType) {
case "request_type_A":
try {
return applicationContext.getBean(ValidatorA.class);
} catch (NoSuchBeanDefinitionException e) {
// handle exception
}
case "request_type_B":
try {
return applicationContext.getBean(ValidatorB.class);
} catch (NoSuchBeanDefinitionException e) {
// handle exception
}
default:
throw new IllegalArgumentException("cannot find validator");
}
}
}
Note: Avoid taking the client specified string as the class name or type directly in the applicationContext.getBean() call. That is not safe and may present a great security vulnerability, use a switch or dictionary to resolve the correct bean name or bean type.
If you want to inject multiple instances of the same classes, create a configuration class and declare the beans like this:
@Configuration
public class BeanConfiguration {
@Bean
public IValidator aValidator(){
return new ValidatorImpl(...);
}
@Bean
public IValidator bValidator(){
return new ValidatorImpl(...);
}
}
And then to inject it, you can either use the dynamic resolution by name as above, or use the @Qualifier annotation:
@Service
public class MyService {
private final ApplicationContext applicationContext;
private final IValidator bValidator;
public MyService(ApplicationContext applicationContext, @Qualifier("bValidator") IValidator bValidator) {
this.applicationContext = applicationContext;
this.bValidator = bValidator;
}
public void getDynamically(){
IValidator aValidator = (IValidator)applicationContext.getBean("aValidator");
}
}