spring-cloud-openfeign
spring-cloud-openfeign copied to clipboard
AOT/native compilation not working with FeignClient and AOP
Describe the bug I read that starting spring-cloud 2022.0.0, you can use native compilation. I'm using 2022.0.2 (spring-cloud-starter-openfeign:4.0.2), but when I use an OpenFeign client with native compilation, I get the following error:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'boxedHelloController': Unsatisfied dependency expressed through field 'helloWorldClient': Error creating bean with name 'nl.cqit.function.poc.java.helloworld.controller.HelloWorldClient': Post-processing of FactoryBean's singleton object failed
at org.springframework.beans.factory.aot.AutowiredFieldValueResolver.resolveValue(AutowiredFieldValueResolver.java:195) ~[na:na]
at org.springframework.beans.factory.aot.AutowiredFieldValueResolver.resolveAndSet(AutowiredFieldValueResolver.java:167) ~[na:na]
at nl.cqit.function.poc.java.boxedhello.controller.BoxedHelloController__Autowiring.apply(BoxedHelloController__Autowiring.java:15) ~[na:na]
at org.springframework.beans.factory.support.InstanceSupplier$1.get(InstanceSupplier.java:83) ~[na:na]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:947) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1214) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1158) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:973) ~[boxedhello-app.exe:6.0.7]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:917) ~[boxedhello-app.exe:6.0.7]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:584) ~[boxedhello-app.exe:6.0.7]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[boxedhello-app.exe:3.0.5]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:732) ~[boxedhello-app.exe:3.0.5]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) ~[boxedhello-app.exe:3.0.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:310) ~[boxedhello-app.exe:3.0.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1304) ~[boxedhello-app.exe:3.0.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1293) ~[boxedhello-app.exe:3.0.5]
at nl.cqit.function.poc.java.boxedhello.Main.main(Main.java:12) ~[boxedhello-app.exe:na]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'nl.cqit.function.poc.java.helloworld.controller.HelloWorldClient': Post-processing of FactoryBean's singleton object failed
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:108) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1823) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getObjectForBeanInstance(AbstractAutowireCapableBeanFactory.java:1273) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:259) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.addCandidateEntry(DefaultListableBeanFactory.java:1640) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1597) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1380) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.aot.AutowiredFieldValueResolver.resolveValue(AutowiredFieldValueResolver.java:189) ~[na:na]
... 22 common frames omitted
Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface nl.cqit.function.poc.java.helloworld.controller.HelloWorldClient, interface org.springframework.aop.SpringProxy, interface org.springframework.aop.framework.Advised, interface org.springframework.core.DecoratingProxy] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles=<comma-separated-config-files> and -H:DynamicProxyConfigurationResources=<comma-separated-config-resources> options.
at org.graalvm.nativeimage.builder/com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:89) ~[na:na]
at org.graalvm.nativeimage.builder/com.oracle.svm.core.reflect.proxy.DynamicProxySupport.getProxyClass(DynamicProxySupport.java:171) ~[na:na]
at [email protected]/java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:47) ~[boxedhello-app.exe:na]
at [email protected]/java.lang.reflect.Proxy.newProxyInstance(Proxy.java:1037) ~[boxedhello-app.exe:na]
at org.springframework.aop.framework.JdkDynamicAopProxy.getProxy(JdkDynamicAopProxy.java:123) ~[na:na]
at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110) ~[na:na]
at org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor.postProcessAfterInitialization(AbstractAdvisingBeanPostProcessor.java:127) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:434) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.postProcessObjectFromFactoryBean(AbstractAutowireCapableBeanFactory.java:1885) ~[boxedhello-app.exe:6.0.7]
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:105) ~[boxedhello-app.exe:6.0.7]
... 32 common frames omitted
Sample Here is my API:
package nl.cqit.function.poc.java.helloworld.api;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import nl.cqit.function.poc.java.helloworld.api.model.Person;
import nl.cqit.function.poc.java.helloworld.api.model.Problem;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
@Validated
@Tag(
name = "HelloWorld",
description = "the HelloWorld API"
)
public interface HelloWorldApi {
@Operation(
operationId = "sayHello",
summary = "The main function",
description = "This endpoint will call sayHello",
tags = {"HelloWorld"},
responses = {@ApiResponse(
responseCode = "200",
description = "OK",
content = {@Content(
mediaType = "application/json",
schema = @Schema(
implementation = String.class
)
), @Content(
mediaType = "application/problem+json",
schema = @Schema(
implementation = String.class
)
)}
), @ApiResponse(
responseCode = "400",
description = "input validation failed",
content = {@Content(
mediaType = "application/json",
schema = @Schema(
implementation = Problem.class
)
), @Content(
mediaType = "application/problem+json",
schema = @Schema(
implementation = Problem.class
)
)}
)}
)
@RequestMapping(
method = {RequestMethod.POST},
value = {"/helloWorld"},
produces = {"application/json", "application/problem+json"},
consumes = {"application/json"}
)
@ResponseStatus(HttpStatus.OK)
String sayHello(@Parameter(name = "Person",description = "",required = true) @RequestBody @Valid Person var1);
}
And here is my Feign client:
package nl.cqit.function.poc.java.helloworld.controller;
import nl.cqit.function.poc.java.helloworld.api.HelloWorldApi;
import org.springframework.cloud.openfeign.FeignClient;
@FeignClient(name = "helloWorldClient", url = "http://localhost:8080")
public interface HelloWorldClient extends HelloWorldApi {
}
I'm using it like this:
package nl.cqit.function.poc.java.boxedhello.impl;
import nl.cqit.function.poc.java.boxedhello.api.BoxedHelloApi;
import nl.cqit.function.poc.java.boxedhello.api.model.Person;
import nl.cqit.function.poc.java.helloworld.controller.HelloWorldClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class BoxedHelloImpl implements BoxedHelloApi {
@Autowired
private HelloWorldClient helloWorldClient;
@Override
public String sayHello(Person person) {
String greeting = helloWorldClient.sayHello(map(person));
String horizontalEdge = "+" + "-".repeat(greeting.length() + 2) + "+";
return horizontalEdge + "\n| " + greeting + " |\n" + horizontalEdge;
}
private nl.cqit.function.poc.java.helloworld.api.model.Person map(Person person) {
return new nl.cqit.function.poc.java.helloworld.api.model
.Person(person.getFirstName())
.lastName(person.getLastName());
}
}
With this as my Main class:
package nl.cqit.function.poc.java.boxedhello;
import nl.cqit.function.poc.java.helloworld.controller.HelloWorldClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients(basePackageClasses = {HelloWorldClient.class})
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
Using this profile for native compilation in my pom.xml:
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder:tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
</env>
</image>
</configuration>
<executions>
<execution>
<id>process-aot</id>
<goals>
<goal>process-aot</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<configuration>
<classesDirectory>${project.build.outputDirectory}</classesDirectory>
<metadataRepository>
<enabled>true</enabled>
</metadataRepository>
<requiredVersion>22.3</requiredVersion>
</configuration>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
<execution>
<id>add-reachability-metadata</id>
<goals>
<goal>add-reachability-metadata</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
Is this a bug or am I missing something?
This issue started occurring after upgrading to Spring Boot 3.0.6 from 3.0.5 so it seems to be some regression bug. Unfortunately I have not been able to track down what causes it.
I was able to get somewhat further when manually adding the aot hint for a proxy for the client interface. It now allows the application to start, albeit slowly, due to a missing resource (not at my pc atm. I'll post the specific logs later), but it can't find the HttpMessageConverter when trying to use the client. I'll see if I can make a public git repo available demonstrating the issues, when I have time.
@Rocker93 I am actually using 3.0.5
Hello, @CC007 @Rocker93 , if you could provide a link to a sample, that would be helpful. We do have a working sample here. I will look into the code you've provided anyway, but will need some time as we have various issues queued up at this point.
@OlgaMaciaszek I made my repo public for now. You can find it here: https://github.com/CodeQualIT/CQITFunctions
Thanks a lot, @CC007. Should be able to get to it around 8th of May.
No problem. If you have any questions, feel free to reply here or message me on Discord: cc007#2753
Also, one thing I noticed is that when org.springframework.cloud.client.HostInfoEnvironmentPostProcessor is trying to get the config for spring.cloud.inetutils, used in the instantiation of org.springframework.cloud.commons.util.InetUtils on line 60 of the post processor, that the config isn't loaded correctly.
InetUtils in org.springframework.cloud.commons.util.UtilAutoConfiguration doesn't seem to have the same issue, because that one seems to get fully initialized before use.
This leads to a significant startup delay, because I'm using a vEthernet (External switch) adapter from hyper-v to route my internet (this was needed to get LAN IP addresses for my hyper-v VMs). Adding...
spring:
cloud:
inetutils:
preferred-networks:
- 192.168
...should have fixed this, but due to the config not getting picked up in the post processor, the startup delay is not prevented.
This happened even without native compilation.
I think the issue in my previous comment is out of scope, so I created an issue in the appropriate issue tracker: https://github.com/spring-cloud/spring-cloud-commons/issues/1233
FYI: I manually added https://github.com/CodeQualIT/CQITFunctions/blob/master/pocs/boxedhello-project/boxedhello-generated/boxedhello-clients/boxedhello-helloworld-client/src/main/java/nl/cqit/function/poc/java/boxedhello/services/helloworld/HelloWorldServiceRuntimeHints.java as a partial workaround. This makes it so that the application starts, but it still causes issues when calling the client. Also, I'd expect these runtime hints for feign clients to be automatically added by openfeign, without user intervention.
Hello @CC007 , I've tried running the project, but the HelloWorldApi class is not available, so it does not compile. Please provide a minimal, executable sample that we can run and debug.
Probably caused by Spring Boot issue: https://github.com/spring-projects/spring-boot/issues/35397.
Hello @CC007 , I've tried running the project, but the
HelloWorldApiclass is not available, so it does not compile. Please provide a minimal, executable sample that we can run and debug.
There are 2 projects in the poc folder: helloworld and boxedhello. Boxedhello uses the interface of helloworld to create a feign client.
I now created a 3rd project in the poc folder with the amount of modules reduced and with the helloworld interface added as submodule, so that it can be built independently.
Thanks, @CC007.
It seems 2 different issues have been discussed here. @Rocker93 if you have a project not working with Boot 3.0.6, but working with Boot 3.0.5, it's probably the regression I've linked above, and your project should work again on the newest Boot snapshots and Boot 3.0.7.
The second issue is the one present in the project @CC007 has linked. This issue seems specifically linked to the use of AOP alongside OpenFeign. I was able to reproduce it. Looks like a bug.
The question is: is this one bug or 2 bugs?
The fact that I manually have to add the runtime hint is one bug, but I don't know if the errors I get related to HttpMessageConverter after manually adding the hints is the same bug or a second bug.
Is that one also caused by a missing runtime hints?
Any progress regarding this issue?
@CC007 I will work on it this week or at the beginning of the next week.
Any progress on this? We are having the same issue on one of our apps with boot 3.0.7, spring-cloud 2022.0.3 and native-maven-plugin 0.9.23.
Hi @korkutkose, it's high on the priority list. Should be able to handle it before we next release.
@OlgaMaciaszek which release will that be? Are you referring to the next spring-cloud-openfeign release? Or the spring-cloud project in general?
They usually go hand in hand, unless there's a security patch. So it would be this one: https://github.com/spring-cloud/spring-cloud-release/milestone/137. If we get to the bug before it, we can release an OF only patch, but the Spring Cloud Train milestone is where it's scheduled.
Hello @CC007, as I've started working on this issue, I have created a proper minimal sample (with only Spring Web, Spring Cloud and Spring AOP), without any code generation or unnecessary non Spring-dependencies. I have set a Spring Boot parent, which is necessary for our integrations to work, have disabled spring.cloud.refresh, which required for GraalVM support and have upgraded to the most recent Boot 3.0.x and Cloud 2022.0.x dependencies. There don't seem to be any issues after fixing the Spring Boot setup and upgrading the dependencies. To test, follow instructions in the README file.
@korkutkose if you're still having issues after upgrading to Boot 3.0.8 and Cloud 2022.0.3, please provide a minimal, complete, verifiable example that reproduces the issue.
@OlgaMaciaszek thanks for taking a look at it. I'm going to see if I can get it working now too.
Since using dependency management rather than a parent is a valid use case when using Spring Boot, what specifically is defined in the parent that is needed to get native compilation to work?
It's expected to use both the parent and dependency management in a Spring Boot app.
You can also use Spring boot without the starter parent: https://docs.spring.io/spring-boot/docs/current/maven-plugin/reference/htmlsingle/#using.import
I tried your minimal example (slightly modified, but in essence the same). When using Spring Boot 3.0.5 and Spring Cloud 2022.0.2, the example does now work. There are 2 things though:
- I can't get it to work with Spring Boot
3.0.8and Spring Cloud2022.0.3. It gives the following error
No code is changed compared to the spring boot[2/7] Performing analysis... [*] (9,8s @ 1,30GB) 7.357 (80,71%) of 9.115 classes reachable 8.741 (53,83%) of 16.237 fields reachable 28.915 (46,47%) of 62.222 methods reachable 583 classes, 305 fields, and 4.493 methods registered for reflection 4 native libraries: crypt32, ncrypt, psapi, winhttp Error: Virtual threads are used in code, but are not currently available or active. Use JDK 19 with preview features enabled (--enable-preview).3.0.5test. I tested it withjava.versionbeing 17 and 19. Both give the same error. - The runtime hints in
HelloWorldServiceRuntimeHintsseem to still be required. Without them, I get an error on runtime:
I would have expected OpenFeign to add those dynamically at compile time.org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'boxedHelloController': Unsatisfied dependency expressed through field 'helloWorldService': Error creating bean with name 'nl.cqit.function.poc.java.boxedhello.services.helloworld.HelloWorldService': Post-processing of FactoryBean's singleton object failed at org.springframework.beans.factory.aot.AutowiredFieldValueResolver.resolveValue(AutowiredFieldValueResolver.java:195) ~[na:na] ... Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'nl.cqit.function.poc.java.boxedhello.services.helloworld.HelloWorldService': Post-processing of FactoryBean's singleton object failed at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:108) ~[boxedhello-minimal-app.exe:6.0.7] ... Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface nl.cqit.function.poc.java.boxedhello.services.helloworld.HelloWorldService, interface org.springframework.aop.SpringProxy, interface org.springframework.aop.framework.Advised, interface org.springframework.core.DecoratingProxy] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles=<comma-separated-config-files> and -H:DynamicProxyConfigurationResources=<comma-separated-config-resources> options. at org.graalvm.nativeimage.builder/com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:89) ~[na:na]
Hmm, after applying your suggestions, when I run with the project with my branch, I do still get the following error:
Could not write request: no suitable HttpMessageConverter found for request type [nl.cqit.function.poc.java.helloworld.api.model.Person] and content type [application/json]
It seems that yours was unfortunately too minimal to reproduce the issue. I'll see if I can find a middle ground where the code base is minimalized, but the issue still occurs.
Hi @CC007. Thanks for the update. You're right. I thought you might have misconfigured Graal plugin (you don't need to use the Boot parent, but then you need to make sure any build plugins, including Graal are configured correctly and updated if necessary when switching to a newer Boot version).
The problem seems to be that while we register any @FeignClient-annotated interfaces for reflection an proxying, we don't do it with any supertypes. I'll work on fixing it.
In the meantime, as a workaround, you can add @RegisterReflectionForBinding(BoxedHelloApi.class) over nl.cqit.function.poc.java.boxedhello.Main to make it execute correctly.
For my usecase I eventually want to support the user being able to provide their own parent, but for now for the MVP of my software using the spring boot parent should be enough. Once that MVP is finished I'll see if I can find the issue regarding my plugin configuration.
Also, thanks for diagnosing the problem with the @FeignClient annotation. I'll see if the workaround of using that annotation drops the need for the runtime hints class whenever I have time.
And regarding the issues with the newer versions of spring boot and cloud, that is a separate bug, so it deserves its own issue. Or is that issue already created?