Spring Cloud 2022.0.2中OpenFeign原生编译报错求助
问题描述
Spring Cloud 2022.0.0开始支持原生编译,当前使用2022.0.2版本(对应spring-cloud-starter-openfeign:4.0.2),但将OpenFeign客户端配合原生编译使用时,出现如下错误:
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] ...(省略中间栈信息) 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] ...(省略中间栈信息)
相关代码
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); }
Feign客户端
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 { }
客户端使用代码
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()); } }
主类
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); } }
pom.xml原生编译Profile
<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>
提问:这是一个bug还是我遗漏了某些配置步骤?
解决方案
这不是bug,是GraalVM原生镜像的特性限制:原生镜像不支持在运行时动态生成代理类,必须在构建镜像时提前声明需要生成的代理接口组合。具体解决步骤如下:
创建动态代理配置文件:在项目的
src/main/resources目录下创建proxy-config.json文件,内容如下:[ { "interfaces": [ "nl.cqit.function.poc.java.helloworld.controller.HelloWorldClient", "org.springframework.aop.SpringProxy", "org.springframework.aop.framework.Advised", "org.springframework.core.DecoratingProxy" ] } ]这个文件告诉GraalVM在构建原生镜像时,为指定的这组接口生成动态代理类。
修改native-maven-plugin配置:在
native-maven-plugin的<configuration>节点中添加动态代理配置资源的引用:<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> <!-- 添加以下配置 --> <buildArgs> <buildArg>-H:DynamicProxyConfigurationResources=proxy-config.json</buildArg> </buildArgs> </configuration> <!-- 原有executions保持不变 --> </plugin>额外检查:确保Spring AOT处理生效:Spring Cloud OpenFeign在原生编译时需要Spring AOT的支持,你的pom.xml已经配置了
spring-boot-maven-plugin的process-aot目标,这部分没问题。如果后续还有其他问题,可以尝试运行mvn spring-boot:aot-generate提前生成AOT代码,再构建原生镜像。
完成以上配置后,重新执行原生编译命令,Feign客户端的代理类会在镜像构建阶段生成,运行时就不会再出现这个错误了。
内容的提问来源于stack exchange,提问作者Rik Schaaf
相关产品推荐
相关产品推荐

