Skip to content

AOT/native compilation not working with FeignClient and AOP #860

@CC007

Description

@CC007

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 java.base@19.0.2/java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:47) ~[boxedhello-app.exe:na]
        at java.base@19.0.2/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?

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions