javaspring-bootnativegraalvm

spring boot 3 native graalvm - HandlerMethodArgumentResolver not working


I configured a custom HandlerMethodArgumentResolver for getting some parameters from the request header and wrapping them in a custom object to be injected as an argument into my controllers.

The custom HandlerMethodArgumentResolver is not in the main project, but in a library imported by the main application. Pom and component scan are both ok, because other beans and configurations are being processed with no issues.

All is working fine running it on JVM, but compiling it as a native application with GraalVM is causing me troubles, becase when I run the application, the custom argument resolver won't be taken into consideration, and the custom object injected is null at runtime.

Here it is the code.

public class HeaderMethodArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().equals(MyHeader.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        MyHeader header = MyHeaderExtractor.generateFrom(webRequest);

        // Makes Validated annotation working on my custom argument
        if (parameter.hasParameterAnnotation(Validated.class) || parameter.hasParameterAnnotation(Valid.class)) {
            // Validate header
            WebDataBinder binder = binderFactory.createBinder(webRequest, header, parameter.getParameterName());
            validateIfApplicable(binder, parameter);
            if (binder.getBindingResult().hasErrors()) {
                throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
            }
        }
        return header;
    }

    /**
     * Validate the model attribute if applicable.
     * <p>The default implementation checks for {@code @jakarta.validation.Valid},
     * Spring's {@link org.springframework.validation.annotation.Validated},
     * and custom annotations whose name starts with "Valid".
     * @param binder the DataBinder to be used
     * @param parameter the method parameter declaration
     * @see WebDataBinder#validate(Object...)
     * @see SmartValidator#validate(Object, Errors, Object...)
     */
    protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
        for (Annotation ann : parameter.getParameterAnnotations()) {
            Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
            if (validationHints != null) {
                binder.validate(validationHints);
                break;
            }
        }
    }
}

Then the configuration class for letting Spring MVC know about my argument resolver.

@EnableWebMvc // Tried with and without EnableWebMvc
@Configuration(proxyBeanMethods = false)
public class HeaderMethodArgumentResolverConfiguration implements WebMvcConfigurer {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        // tried this
        resolvers.add(new HeaderMethodArgumentResolver());
        // and also this when created as a Bean
        resolvers.add(headerMethodArgumentResolver());
    }

    @Bean   // Tried with and without this method
    public HeaderMethodArgumentResolver headerMethodArgumentResolver() {
        return new HeaderMethodArgumentResolver();
    }

}

And an example controller.

@RestController
@RequestMapping("/api/my-resource")
@Slf4j
public class MyController {

    @PostMapping("/example")
    public MyResponseObject doSomething(
            @Validated({HeaderAsIWant.class}) MyHeader myHeader,
            @Validated @RequestBody MyRequestObject request) {
    }

}

Am I doing something wrong, missing some configuration, or is it a bug/incompatibility with the native build?

Many thanks to everyone that will reply.

NOTE 1: I checked from the /actuator/beans invocation, and I'm sure the bean got instantiated correctly. The issue seems to be that Spring won't call the method resolver when the request comes in.

NOTE 2: Moving the custom argument resolver from the library to the main project solves the issue. Though, that's not a solution for me because I need to use this argument resolver in multiple microservices, possibly without copy-pasting it.


Solution

  • I finally figured out how to make it work, just adding the following on the configuration bean:

    @RegisterReflectionForBinding({HeaderMethodArgumentResolverConfiguration.class})
    

    Here it is the full class:

    @Configuration(proxyBeanMethods = false)
    @RegisterReflectionForBinding({HeaderMethodArgumentResolverConfiguration.class})
    public class HeaderMethodArgumentResolverConfiguration implements WebMvcConfigurer {
    
        @Override
        public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
            resolvers.add(new HeaderMethodArgumentResolver());
        }
    
    }
    

    Probably, given that the configuration bean was in a library, it needs to register reflection information for discovering the addArgumentResolvers method or that it implements WebMvcConfigurer (can't be sure now, but it works!)