0

I have a generic rest controller (with @RestController and empty mapping) with a method that accepts a generic @RequestBody. I then register this controller multiple times using ImportBeanDefinitionRegistrar and assign dynamic mappings inside my own customized RequestMappingHandlerMapping. The problem I'm having is that when the request comes to the controller it's not of the resoved non-generic type, but rather a LinkedHashMap.

I don't have the exact code with me, but it goes something as follows

@RestController
@RequiredArgsConstructor
class DynamicGenericController<T> {
  
  private final Service<T> service;

  @PostMapping
  void execute(@RequestBody T request) {
    //request is always LinkedHashMap and not the actual generic type
    service.execute(request);
  }
}

class DynamicRegistrar implements ImportBeanDefinitionRegistrar {

  void registerBeanDefinitions(...) {
    
    //this is actually a variable sequence of registrations based on the app configuration
    //here are well known classes just for the sake of simplicity
    registerController(registry, new ServiceA(), A.class);
    registerController(registry, new ServiceB(), B.class);
  }

  <T> void registerController(BeanRegistry registry, Service<T> service, Class<T> clazz) {
    var bean = (RootBeanDefinition)BeanDefinitionBuilder.rootBeanDefinition()
      .beanClass(DynamicGenericController.class)
      .addConstrutorArgValue(service)
      .build();

    bean.setTargetType(ResolvableType.forClassWithGenerics(DynamicGenericController.class, clazz));
    registry.register("controller" + clazz.getSimpleName(), bean);
  }
}

@Bean
RequestMappingHandlerMapping requestMappingHandlerMapping(BeanFactory beanFactory) {

  return new RequestMappingHandlerMapping() {

    @Override
    void registerMapping(...) {
      ...
      var bean = beanFactory.getBean(beanName);
      if(bean instanceof DynamicGenericController) {
        super.registerMapping(..., mapping.mutate().paths(...).build());
      } else {
        super.registerMapping(...);
      }
 
    }

  }
}

I more or less understand why this is the case and I'm looking for a simple and elegant way of keeping this as generic as possible without modyfing the request classes or introducing my own ArgumentResolver or ObjectMapper for Jackson - I would like to utilize the beans that are already registered there and not overwrite what application has set itself. Potentially I will have more controllers like this in the future with more methods, so I wouldn't like to create a boiler plate code for each of them.

1 Answer 1

0

Try

bean.setTargetType(ResolvableType.forClassWithGenerics(DynamicGenericController.class, clazz));

instead of

bean.setTargetType(ResolvableType.forClassWithGenerics(Service.class, clazz));

By setting bean.setTargetType(ResolvableType.forClassWithGenerics(DynamicGenericController.class, clazz)), you are explicitly telling the Spring container, "This bean definition for 'controllerA' isn't just DynamicGenericController; it is specifically DynamicGenericController."

Sign up to request clarification or add additional context in comments.

1 Comment

It was a mistake when typing in the code, my original code has the controller being set as the targetType together with its generic parameters. I corrected the original code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.