Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document explicit @ModelAttribute is required for reflection hints inference #31765

Closed
xuyixun opened this issue Dec 6, 2023 · 10 comments
Closed
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: backported An issue that has been backported to maintenance branches theme: aot An issue related to Ahead-of-time processing type: documentation A documentation task
Milestone

Comments

@xuyixun
Copy link

xuyixun commented Dec 6, 2023

code https://github.com/xuyixun/demo-graalvm-java
spring boot: 3.2.0
gradle: 8.5

public class TestDto{
  private String name;
  //get and set
}
@RequestMapping("/demo")
public String demo(TestDto dto){
   System.out.println(dto.getName());
   return "success";
 }

Hi Spring Team,I run Demo with gradle bootRun is fine. Then build Demo with gradle bootBuildImage is fine. But when run docker image and request 127.0.0.1:8080/demo or http://127.0.0.1:8080/demo?name=test with error causes

java.lang.IllegalStateException: No primary or single unique constructor found for class com.example.demo.dto.TestDto
        at org.springframework.beans.BeanUtils.getResolvableConstructor(BeanUtils.java:267) ~[na:na]
        at org.springframework.validation.DataBinder.createObject(DataBinder.java:924) ~[com.example.demo.DemoApplication:6.1.1]
        at org.springframework.validation.DataBinder.construct(DataBinder.java:903) ~[com.example.demo.DemoApplication:6.1.1]
        at org.springframework.web.bind.ServletRequestDataBinder.construct(ServletRequestDataBinder.java:112) ~[com.example.demo.DemoApplication:6.1.1]
        at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.constructAttribute(ServletModelAttributeMethodProcessor.java:156) ~[na:na]
        at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:148) ~[com.example.demo.DemoApplication:6.1.1]
        at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122) ~[na:na]
        at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:218) ~[com.example.demo.DemoApplication:6.1.1]
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:171) ~[com.example.demo.DemoApplication:6.1.1]
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[com.example.demo.DemoApplication:6.1.1]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:917) ~[com.example.demo.DemoApplication:6.1.1]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:829) ~[com.example.demo.DemoApplication:6.1.1]
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[com.example.demo.DemoApplication:6.1.1]
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) ~[com.example.demo.DemoApplication:6.1.1]
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) ~[com.example.demo.DemoApplication:6.1.1]
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[com.example.demo.DemoApplication:6.1.1]
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[com.example.demo.DemoApplication:6.1.1]
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) ~[com.example.demo.DemoApplication:6.0]
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[com.example.demo.DemoApplication:6.1.1]
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[com.example.demo.DemoApplication:6.0]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[na:na]
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[com.example.demo.DemoApplication:10.1.16]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[na:na]
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[com.example.demo.DemoApplication:6.1.1]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[com.example.demo.DemoApplication:6.1.1]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[na:na]
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[na:na]
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[na:na]
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) ~[com.example.demo.DemoApplication:10.1.16]
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[na:na]
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[com.example.demo.DemoApplication:10.1.16]
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[na:na]
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:340) ~[na:na]
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391) ~[na:na]
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[com.example.demo.DemoApplication:10.1.16]
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896) ~[na:na]
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744) ~[na:na]
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[com.example.demo.DemoApplication:10.1.16]
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[na:na]
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[na:na]
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[na:na]
        at [email protected]/java.lang.Thread.runWith(Thread.java:1596) ~[com.example.demo.DemoApplication:na]
        at [email protected]/java.lang.Thread.run(Thread.java:1583) ~[com.example.demo.DemoApplication:na]
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:837) ~[com.example.demo.DemoApplication:na]
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.posix.thread.PosixPlatformThreads.pthreadStartRoutine(PosixPlatformThreads.java:211) ~[na:na]

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Dec 6, 2023
@quaff
Copy link
Contributor

quaff commented Dec 6, 2023

You can reproduce it by gradle nativeRun, nothing to do with docker.

@mhalbritter
Copy link
Contributor

mhalbritter commented Dec 6, 2023

There's a @RequestBody annotation missing on your controller method:

@RequestMapping("/demo")
public String demo(@RequestBody TestDto dto){
// ...

@mhalbritter mhalbritter added status: invalid An issue that we don't feel is valid and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Dec 6, 2023
@mhalbritter mhalbritter closed this as not planned Won't fix, can't repro, duplicate, stale Dec 6, 2023
@quaff
Copy link
Contributor

quaff commented Dec 6, 2023

There's a @RequestBody annotation missing on your controller method:

@RequestMapping("/demo")
public String demo(@RequestBody TestDto dto){
// ...

It's intentional, then parameters from request will be used instead of request body.

@xuyixun
Copy link
Author

xuyixun commented Dec 6, 2023

@mhalbritter I want use get request and url parameters like http://127.0.0.1:8080/demo?name=test not post request. This
code run in jvm is fine, But in graalvm is fail

@quaff
Copy link
Contributor

quaff commented Dec 6, 2023

If you change return type from String to TestDto like this:

  public TestDto demo(TestDto dto){
    return dto;
  }

It works both with bootRun and nativeRun.

@mhalbritter mhalbritter reopened this Dec 6, 2023
@mhalbritter
Copy link
Contributor

Sorry, my bad. Then this looks like a bug to me.

@mhalbritter mhalbritter changed the title gradle bootBuildImage mvc DataBinder error No primary or single unique constructor Failed to bind MVC request parameters to object when running in native-image Dec 6, 2023
@wilkinsona
Copy link
Member

I suspect it's a bug/limitation of Framework's ControllerMappingReflectiveProcessor.

@mhalbritter
Copy link
Contributor

I agree, this belongs to Spring Framework.

Workaround: Annotate your main class with @RegisterReflectionForBinding(TestDto.class).

@bclozel bclozel removed the status: invalid An issue that we don't feel is valid label Dec 6, 2023
@bclozel bclozel transferred this issue from spring-projects/spring-boot Dec 6, 2023
@bclozel bclozel added status: waiting-for-triage An issue we've not yet triaged or decided on in: web Issues in web modules (web, webmvc, webflux, websocket) theme: aot An issue related to Ahead-of-time processing labels Dec 6, 2023
@sdeleuze sdeleuze self-assigned this Dec 6, 2023
@sdeleuze sdeleuze added type: documentation A documentation task and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Dec 6, 2023
@sdeleuze sdeleuze added this to the 6.0.15 milestone Dec 6, 2023
@sdeleuze
Copy link
Contributor

sdeleuze commented Dec 6, 2023

This is a known and almost "by design" limitation of ControllerMappingReflectiveProcessor (see #28623 related implementation) since the programming model induced by the catch-all instantiation of ServletModelAttributeMethodProcessor and ModelAttributeMethodArgumentResolver with useDefaultResolution set to true is not something, as far as I can tell, that we can support in a maintainable way. And our past tries to add reflection binding hints to all web handler method parameters has always been a failure due to a very significant increase of the footprint due to much methods reachable.

As a consequence, I suggest that we recommend and document for GraalVM use case explicit annotation with @ModelAttribute in order to allow automatic reflection hint generation.

As discussed with @mhalbritter, I suggest we add in the web section of Spring Boot GraalVM wiki:

Optional usage of @ModelAttribute as supported by Spring MVC and Spring WebFlux does not allow ahead-of-time inference of related data binding reflection hints. As a consequence, it is recommended to annotate explicitly with @ModelAttribute method parameters where binding is required when compiling to GraalVM native images.

I will also update those Spring Framework reference pages to document that limitation.

@sdeleuze sdeleuze changed the title Failed to bind MVC request parameters to object when running in native-image Document explicit @ModelAttribute is required for reflection hints inference Dec 6, 2023
@mhalbritter
Copy link
Contributor

I've updated the wiki page on Spring Boot side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: backported An issue that has been backported to maintenance branches theme: aot An issue related to Ahead-of-time processing type: documentation A documentation task
Projects
None yet
Development

No branches or pull requests

7 participants