spring-projects / spring-boot

Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss.
https://spring.io/projects/spring-boot
Apache License 2.0
75.4k stars 40.75k forks source link

When `@Aspect` and `@FeignClient` are used simultaneously in one application, an exception will be thrown in `native-image` #34388

Closed wangliang181230 closed 1 year ago

wangliang181230 commented 1 year ago

This exception only throw in the native-image. When running spring-aot-mode without native-image, this exception will not be thrown.

Through adding logs and debugging, found that the root cause is that the JDK proxy object cannot perform the 'getMethod' operation in the native-image, it will threw the NoSuchMethodException, resulting in the method obtained by the 'ClassUtils. getMostSpecificMethod (...)' method is not as expected.

For example: jdkProxy.getClass().getMethod(methodName); will throw NoSuchMethodException in native-image, even if the method exists in the proxied interface.

I also report another issue: https://github.com/oracle/graal/issues/6079

The simplest example:

https://github.com/wangliang181230/example__spring-projects_spring-boot_issue-34388


Classes:

My FeignClient

@FeignClient(name = "openfeign-example", url = "https://httpbin.org", contextId = "openfeign-example")
public interface HttpbinClient {

    @GetMapping("/delay/3")
    String delay();

    @GetMapping("/status/500")
    String status500();

    @GetMapping("/get")
    String get();

}

My Aspect

@Aspect
@Component
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {

    private static final Logger LOGGER = LoggerFactory.getLogger(SentinelResourceAspect.class);

    @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
    public void sentinelResourceAnnotationPointcut() {
    }

    @Around("sentinelResourceAnnotationPointcut()")
    public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
        LOGGER.info("Start aspect");

        // do something......
    }
}

Modified AspectJExpressionPointcut and print error log:

I modified the AspectJExpressionPointcut to catch the exception and print exception log. Otherwise, the application will not start.

The modified AspectJExpressionPointcut:

public class AspectJExpressionPointcut extends AbstractExpressionPointcut
        implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {

    ......

    private ShadowMatch getTargetShadowMatch(Method method, Class<?> targetClass) {
        Method targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);

        if (targetClass.getName().startsWith("jdk.")) {
            // In `native image`,    the value of `targetMethod.getDeclaringClass()` is the FeignClient interface: `HttpbinClient.class`,
            // In 'spring-aot-mode', the value of `targetMethod.getDeclaringClass()` is the proxy type: `jdk.proxy4.$Proxy46`.
            logger.info("\r\n    getTargetShadowMatch:" + " targetClass: " + targetClass.getName() + ", targetMethod: " + targetMethod.toGenericString()
                    + ", targetMethod.getDeclaringClass(): " + targetMethod.getDeclaringClass().getName()
            );
        }

        if (targetMethod.getDeclaringClass().isInterface()) {
            // Try to build the most specific interface possible for inherited methods to be
            // considered for sub-interface matches as well, in particular for proxy classes.
            // Note: AspectJ is only going to take Method.getDeclaringClass() into account.
            Set<Class<?>> ifcs = ClassUtils.getAllInterfacesForClassAsSet(targetClass);
            if (ifcs.size() > 1) {
                Class<?>[] interfaces = ClassUtils.toClassArray(ifcs);
                try {
                    Class<?> compositeInterface = ClassUtils.createCompositeInterface(
                            interfaces, targetClass.getClassLoader());
                    targetMethod = ClassUtils.getMostSpecificMethod(targetMethod, compositeInterface);
                }
                catch (IllegalArgumentException ex) {
                    // Implemented interfaces probably expose conflicting method signatures...
                    // Proceed with original target method.
                }
                catch (Throwable t) {
                    // Catch the exception, and print the log
                    // Otherwise, the application will not start
                    logger.error(Arrays.toString(interfaces) + " for class '"+targetClass.getName()+"' is createCompositeInterface failed", t);
                }
            }
        }
        return getShadowMatch(targetMethod, method);
    }

    ......

}

The error log in native-image:

// Reason for the BUG
2023-02-27T15:22:59.474+08:00  WARN 9980 --- [main] org.springframework.util.ClassUtils      : No such method 'status500' by the target class 'jdk.proxy4.$Proxy46'

// For observation
2023-02-27T15:22:59.474+08:00  INFO 9980 --- [main] o.s.a.aspectj.AspectJExpressionPointcut  : 
    getTargetShadowMatch: targetClass: jdk.proxy4.$Proxy46, targetMethod: public abstract java.lang.String cn.wangliang181230.spring_projects__spring_boot__issue_34388.openfeign.HttpbinClient.status500(), targetMethod.getDeclaringClass(): cn.wangliang181230.spring_projects__spring_boot__issue_34388.openfeign.HttpbinClient

// The error log
2023-02-27T15:22:59.475+08:00 ERROR 9980 --- [main] o.s.a.aspectj.AspectJExpressionPointcut  : 
    [interface cn.wangliang181230.spring_projects__spring_boot__issue_34388.openfeign.HttpbinClient, interface java.io.Serializable]
    for class 'jdk.proxy4.$Proxy46' is createCompositeInterface failed

com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface cn.wangliang181230.spring_projects__spring_boot__issue_34388.openfeign.HttpbinClient, interface java.io.Serializable] 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@17.0.6/java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:47) ~[spring-projects_spring-boot_issue-34388.exe:na]
    at java.base@17.0.6/java.lang.reflect.Proxy.getProxyClass(Proxy.java:398) ~[spring-projects_spring-boot_issue-34388.exe:na]
    at org.springframework.util.ClassUtils.createCompositeInterface(ClassUtils.java:783) ~[na:na]
    at org.springframework.aop.aspectj.AspectJExpressionPointcut.getTargetShadowMatch(AspectJExpressionPointcut.java:432) ~[na:na]
    at org.springframework.aop.aspectj.AspectJExpressionPointcut.matches(AspectJExpressionPointcut.java:289) ~[na:na]
    at org.springframework.aop.support.AopUtils.canApply(AopUtils.java:251) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.aop.support.AopUtils.canApply(AopUtils.java:288) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.aop.support.AopUtils.findAdvisorsThatCanApply(AopUtils.java:320) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findAdvisorsThatCanApply(AbstractAdvisorAutoProxyCreator.java:128) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findEligibleAdvisors(AbstractAdvisorAutoProxyCreator.java:97) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean(AbstractAdvisorAutoProxyCreator.java:78) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:366) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:318) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:435) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.postProcessObjectFromFactoryBean(AbstractAutowireCapableBeanFactory.java:1866) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:105) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1823) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getObjectForBeanInstance(AbstractAutowireCapableBeanFactory.java:1265) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:259) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.addCandidateEntry(DefaultListableBeanFactory.java:1628) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1585) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1368) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1325) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.aot.AutowiredFieldValueResolver.resolveValue(AutowiredFieldValueResolver.java:189) ~[na:na]
    at org.springframework.beans.factory.aot.AutowiredFieldValueResolver.resolveAndSet(AutowiredFieldValueResolver.java:167) ~[na:na]
    at cn.wangliang181230.spring_projects__spring_boot__issue_34388.controller.TestController__Autowiring.apply(TestController__Autowiring.java:14) ~[na:na]
    at org.springframework.beans.factory.support.InstanceSupplier$1.get(InstanceSupplier.java:82) ~[na:na]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainInstanceFromSupplier(AbstractAutowireCapableBeanFactory.java:1225) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1210) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1157) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:561) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:961) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:915) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:584) ~[spring-projects_spring-boot_issue-34388.exe:6.0.2]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-projects_spring-boot_issue-34388.exe:3.0.0]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:730) ~[spring-projects_spring-boot_issue-34388.exe:3.0.0]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:432) ~[spring-projects_spring-boot_issue-34388.exe:3.0.0]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) ~[spring-projects_spring-boot_issue-34388.exe:3.0.0]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1302) ~[spring-projects_spring-boot_issue-34388.exe:3.0.0]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1291) ~[spring-projects_spring-boot_issue-34388.exe:3.0.0]
    at cn.wangliang181230.spring_projects__spring_boot__issue_34388.ExampleApplication.main(ExampleApplication.java:16) ~[spring-projects_spring-boot_issue-34388.exe:na]

Other DEBUG information

图片


Recurrence steps:

  1. git clone https://github.com/wangliang181230/spring-projects_spring-boot_issue-34388.git
  2. mvn clean native:compile -Pnative
  3. start ./target/spring-projects_spring-boot_issue-34388.exe, it will print the error log 3 times.
  4. Browse http://localhost:8080/confirm-bug to confirm the BUG.

Environment and versions:

OS: Windows 10 JDK: graalvm-ce-java17-22.3.1 Spring Boot: 3.0.0 Native maven plugin: 0.9.20

wilkinsona commented 1 year ago

I think this should have been fixed by https://github.com/spring-projects/spring-framework/issues/29519. Please test with the latest version of Spring Boot. If the problem still occurs, please open a Spring Framework issue.

wangliang181230 commented 1 year ago

Fixed, Thanks.