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

Cglib proxies created by MvcUriComponentsBuilder should be processed at build-time #29500

Open
eiswind opened this issue Nov 16, 2022 · 6 comments
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) theme: aot An issue related to Ahead-of-time processing type: enhancement A general enhancement
Milestone

Comments

@eiswind
Copy link

eiswind commented Nov 16, 2022

Affects: 6.0.0

I have a simple controller that goes like

    @PostMapping
    public ResponseEntity<?> insert(
            @RequestBody @Valid CustomerApiDTO customerDTO) {

        var customerEntity = customerMapper.map(customerDTO);

        customerRepository.saveAndFlush(customerEntity);

        var uri = MvcUriComponentsBuilder.fromMethodCall(
                MvcUriComponentsBuilder.on(CustomerApiController.class)
                        .findByCustomerNumber(customerDTO.getCustomerNumber())  
        ).build();
        return ResponseEntity.created(uri.toUri()).build(); 
    }

With AOT generating the proxy for the controller this fails with:

java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
        at de.eiswind.training.spring.rest.customer.responseentity.CustomerApiController$$SpringCGLIB$$1.setCallbacks(<generated>)
        at org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder$ControllerMethodInvocationInterceptor.initProxy(MvcUriComponentsBuilder.java:814)
        at org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.controller(MvcUriComponentsBuilder.java:383)

Please let me know if I can provide any further help with this.

Please forgive: I am not sure where to file this issue as it goes with AOT.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Nov 16, 2022
@sbrannen
Copy link
Member

Can you provide the full stack trace?

Also, have you tried this with Spring Framework 6.0.0 (GA)?

@sbrannen sbrannen added status: waiting-for-feedback We need additional information before we can continue theme: aot An issue related to Ahead-of-time processing labels Nov 16, 2022
@eiswind
Copy link
Author

eiswind commented Nov 16, 2022

I updated everything to spring boot 3-SNAPSHOT which pulls in 6.0.0. Still get the same error. Full stacktrace is

 Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1] with root cause

java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
        at de.eiswind.training.spring.rest.customer.responseentity.CustomerApiController$$SpringCGLIB$$1.setCallbacks(<generated>)
        at org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder$ControllerMethodInvocationInterceptor.initProxy(MvcUriComponentsBuilder.java:814)
        at org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.controller(MvcUriComponentsBuilder.java:383)
        at org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.on(MvcUriComponentsBuilder.java:358)
        at de.eiswind.training.spring.rest.customer.responseentity.CustomerApiController.insert(CustomerApiController.java:105)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:699)
        at de.eiswind.training.spring.rest.customer.responseentity.CustomerApiController$$SpringCGLIB$$0.insert(<generated>)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:891)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:804)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1080)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:973)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1003)
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:906)
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:731)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:880)
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
        at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:108)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1739)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.base/java.lang.Thread.run(Thread.java:833)

2022-11-16T15:15:15.722+01:00 ERROR 58498 --- [           main] o.s.t.w.reactive.server.ExchangeResult   : Request details for assertion failure:

> POST http://localhost:43657/api/customer
> accept-encoding: [gzip]
> user-agent: [ReactorNetty/1.1.0]
> host: [localhost:43657]
> accept: [*/*]
> WebTestClient-Request-Id: [1]
> Content-Type: [application/json]
> Content-Length: [64]

{"customerNumber":"C001","firstname":"Test","lastname":"Mensch"}

< 500 INTERNAL_SERVER_ERROR Internal Server Error
< Content-Type: [application/json]
< Transfer-Encoding: [chunked]
< Date: [Wed, 16 Nov 2022 14:15:15 GMT]
< Connection: [close]

{"timestamp":"2022-11-16T14:15:15.695+00:00","status":500,"error":"Internal Server Error","path":"/api/customer"}

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Nov 16, 2022
@eiswind
Copy link
Author

eiswind commented Nov 16, 2022

Please give me a minute, I'll try to set up a reproducible example.

@eiswind
Copy link
Author

eiswind commented Nov 16, 2022

That was a ride. I found that the failing proxy starts to be generated as soon as I have starter-hateoas on the classpath.
removing it ends up with a different error, as the MvcUriComponentsBuilder then tries to generate a class at runtime.

Please have a quick look at

https://github.com/eiswind/mvcuri-native

where both failures are reproducible.

I did not find a way to register a cglib proxy manually yet. Is there one?

@sbrannen sbrannen added this to the Triage Queue milestone Nov 20, 2022
@odrotbohm
Copy link
Member

odrotbohm commented Nov 23, 2022

So, the non-HATEOAS arrangement basically stumbles over the fact that the controller class and the return type of the dummy method invocation need to be proxied, and no CGLib proxy classes have been registered for these types. I guess that's due to Spring MVC not yet providing any facilities to do that.

In the HATEOAS-world we have an AOT extension for WebMvcContollerLinkBuilder to work properly (which predates MUCB, basically does the same as that but produces Link instances eventually). So, I assume that we now register e.g. a CGLib proxy with a certain proxy definition (declaring LastInvocationAware as sole interface, a HATEOAS specific interceptor). This is done through a ProxyFactory instance that, under the covers, uses the same APIs that MUCB uses. When the code path in MUCB is now hit, the HATEOAS-specific CGLib proxy is used, but in a different setup (registering MethodInvocationInfo as interface and another dedicated interceptor), which then ultimately fails as it seems to try to add a second interceptor to the array already sized to 1.

I guess we'll have to consult the AOT team to avoid such conflicting proxy declarations.

@rstoyanchev rstoyanchev removed this from the Triage Queue milestone Jan 20, 2023
@sbrannen sbrannen changed the title MvcUriComponentsBuilder fails with AOT-generated Proxy MvcUriComponentsBuilder fails with AOT-generated proxy Jan 31, 2023
@sbrannen sbrannen added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Jan 31, 2023
@snicoll snicoll changed the title MvcUriComponentsBuilder fails with AOT-generated proxy Cglib proxies created by MvcUriComponentsBuilder should be processed at build-time Oct 23, 2023
@snicoll
Copy link
Member

snicoll commented Oct 23, 2023

@eiswind thanks for the report. The problem is that those proxies should be generated at build-time and the code in the middle of that controller method cannot be introspected. I've tried to trigger the creation of the proxy at build-time but this lead to:

Exception in thread "main" java.lang.IllegalStateException: No current ServletRequestAttributes
	at org.springframework.util.Assert.state(Assert.java:76)
	at org.springframework.web.servlet.support.ServletUriComponentsBuilder.getCurrentRequest(ServletUriComponentsBuilder.java:179)
	at org.springframework.web.servlet.support.ServletUriComponentsBuilder.fromCurrentServletMapping(ServletUriComponentsBuilder.java:155)
	at org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.getBaseUrlToUse(MvcUriComponentsBuilder.java:559)
	at org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.fromMethodInternal(MvcUriComponentsBuilder.java:539)
	at org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.fromMethodCall(MvcUriComponentsBuilder.java:321)
	at com.example.mcvuri.MvcuriApplicationAotContribution$AotContribution.applyTo(MvcuriApplicationAotContribution.java:28)

At this stage, I don't know how this can be improved. We'll have to investigate.

@snicoll snicoll added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged or decided on status: feedback-provided Feedback has been provided labels Oct 23, 2023
@snicoll snicoll added this to the 6.x Backlog milestone Oct 23, 2023
@bclozel bclozel changed the title Cglib proxies created by MvcUriComponentsBuilder should be processed at build-time Cglib proxies created by MvcUriComponentsBuilder should be processed at build-time Feb 14, 2024
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) theme: aot An issue related to Ahead-of-time processing type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

6 participants