spring-native
spring-native copied to clipboard
Add support for record DTOs
Hi, I have an issue acessing dtos/records in a thymeleaf-template when using a docker image generated via spring-native. It works perfectly fine if BP_NATIVE_IMAGE = false
Controller
# Data classes
public class SomeDto {
private final String name;
# getter / setter
}
public record SomeRecord(String name) {
}
# IndexController
@Controller
public class IndexController {
@GetMapping
public String index(Model model) {
model.addAttribute("someRecord", new SomeRecord("My Name"));
model.addAttribute("someDto", new SomeDto("My Name"));
return "index";
}
}
# index.html
...
<h2 th:text="${someDto.name}">Selected</h2>
<h2 th:text="${someRecord.name()}">Selected</h2>
...
When opening the page, this results in following exception:
2022-07-01 19:15:31.765 ERROR 1 --- [nio-8080-exec-1] org.thymeleaf.TemplateEngine : [THYMELEAF][http-nio-8080-exec-1] Exception processing template "index": Exception evaluating SpringEL expression: "someDto.getName()" (template: "index" - line 13, col 9)
org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "someDto.getName()" (template: "index" - line 13, col 9)
at org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator.evaluate(SPELVariableExpressionEvaluator.java:292) ~[na:na]
at org.thymeleaf.standard.expression.VariableExpression.executeVariableExpression(VariableExpression.java:166) ~[na:na]
at org.thymeleaf.standard.expression.SimpleExpression.executeSimple(SimpleExpression.java:66) ~[com.example.demo.DemoApplication:3.0.15.RELEASE]
at org.thymeleaf.standard.expression.Expression.execute(Expression.java:109) ~[com.example.demo.DemoApplication:3.0.15.RELEASE]
at org.thymeleaf.standard.expression.Expression.execute(Expression.java:138) ~[com.example.demo.DemoApplication:3.0.15.RELEASE]
at org.thymeleaf.standard.processor.AbstractStandardExpressionAttributeTagProcessor.doProcess(AbstractStandardExpressionAttributeTagProcessor.java:144) ~[com.example.demo.DemoApplication:3.0.15.RELEASE]
at org.thymeleaf.processor.element.AbstractAttributeTagProcessor.doProcess(AbstractAttributeTagProcessor.java:74) ~[com.example.demo.DemoApplication:3.0.15.RELEASE]
at org.thymeleaf.processor.element.AbstractElementTagProcessor.process(AbstractElementTagProcessor.java:95) ~[com.example.demo.DemoApplication:3.0.15.RELEASE]
at org.thymeleaf.util.ProcessorConfigurationUtils$ElementTagProcessorWrapper.process(ProcessorConfigurationUtils.java:633) ~[na:na]
at org.thymeleaf.engine.ProcessorTemplateHandler.handleOpenElement(ProcessorTemplateHandler.java:1314) ~[na:na]
at org.thymeleaf.engine.OpenElementTag.beHandled(OpenElementTag.java:205) ~[na:na]
at org.thymeleaf.engine.TemplateModel.process(TemplateModel.java:136) ~[com.example.demo.DemoApplication:3.0.15.RELEASE]
at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:661) ~[na:na]
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1098) ~[com.example.demo.DemoApplication:3.0.15.RELEASE]
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1072) ~[com.example.demo.DemoApplication:3.0.15.RELEASE]
at org.thymeleaf.spring5.view.ThymeleafView.renderFragment(ThymeleafView.java:366) ~[com.example.demo.DemoApplication:3.0.15.RELEASE]
at org.thymeleaf.spring5.view.ThymeleafView.render(ThymeleafView.java:190) ~[com.example.demo.DemoApplication:3.0.15.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1401) ~[com.example.demo.DemoApplication:5.3.21]
at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1145) ~[com.example.demo.DemoApplication:5.3.21]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1084) ~[com.example.demo.DemoApplication:5.3.21]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963) ~[com.example.demo.DemoApplication:5.3.21]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[com.example.demo.DemoApplication:5.3.21]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[com.example.demo.DemoApplication:5.3.21]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:655) ~[com.example.demo.DemoApplication:4.0.FR]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[com.example.demo.DemoApplication:5.3.21]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:764) ~[com.example.demo.DemoApplication:4.0.FR]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[na:na]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[com.example.demo.DemoApplication:9.0.64]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[na:na]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[com.example.demo.DemoApplication:5.3.21]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[com.example.demo.DemoApplication:5.3.21]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[na:na]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[com.example.demo.DemoApplication:5.3.21]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[com.example.demo.DemoApplication:5.3.21]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[na:na]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[com.example.demo.DemoApplication:5.3.21]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[com.example.demo.DemoApplication:5.3.21]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[na:na]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[na:na]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[na:na]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) ~[com.example.demo.DemoApplication:9.0.64]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) ~[na:na]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[com.example.demo.DemoApplication:9.0.64]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[na:na]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360) ~[na:na]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399) ~[na:na]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[com.example.demo.DemoApplication:9.0.64]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:890) ~[na:na]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1787) ~[na:na]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[com.example.demo.DemoApplication:9.0.64]
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 java.lang.Thread.run(Thread.java:833) ~[com.example.demo.DemoApplication:na]
at com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:704) ~[com.example.demo.DemoApplication:na]
at com.oracle.svm.core.posix.thread.PosixPlatformThreads.pthreadStartRoutine(PosixPlatformThreads.java:202) ~[na:na]
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1004E: Method call: Method getName() cannot be found on type com.example.demo.SomeDto
at org.springframework.expression.spel.ast.MethodReference.findAccessorForMethod(MethodReference.java:225) ~[na:na]
at org.springframework.expression.spel.ast.MethodReference.getValueInternal(MethodReference.java:135) ~[na:na]
at org.springframework.expression.spel.ast.MethodReference.access$000(MethodReference.java:55) ~[na:na]
at org.springframework.expression.spel.ast.MethodReference$MethodValueRef.getValue(MethodReference.java:386) ~[na:na]
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:92) ~[na:na]
at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:112) ~[com.example.demo.DemoApplication:5.3.21]
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:338) ~[na:na]
at org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator.evaluate(SPELVariableExpressionEvaluator.java:265) ~[na:na]
... 60 common frames omitted
Minimum viable demo: demo.zip
OS: Kernel: 5.10.124-1-MANJARO x86_64 'org.springframework.boot' version '2.7.0' 'org.springframework.experimental.aot' version '0.12.0' java: 17.0.3-tem gradle: 7.4.1
Could you please provide some support here, on what I'm might missing here?
Thanks!
The main problem here is using records as a DTO.
Method getName() cannot be found on type com.example.demo.SomeDto
You have to add some reflection hints to include all public methods from your DTO into the native image. Then Thmyeleaf with records should work.
I'll reword the issue and add it to the backlog.
For this kind of use case, we don't have explicit trigger from the annotation based programming model, so 2 potential solutions that I am mentioning here more for discussion related to our upcoming Spring Boot 3 support:
- Add support for
model.addAttribute
via #1152 - Support an annotation on the said DTO/record
Spring Native is now superseded by Spring Boot 3 official native support, see the related reference documentation for more details.
As a consequence, I am closing this issue, and recommend trying your use case with latest Spring Boot 3 version. If you still experience the issue reported here, please open an issue directly on the related Spring project (Spring Framework, Data, Security, Boot, Cloud, etc.) with a reproducer.
Thanks for your contribution on the experimental Spring Native project, we hope you will enjoy the official native support introduced by Spring Boot 3.