Environment

Using Spring Boot 3.0.2 with the following:

java version "17.0.5" 2022-10-18 LTS
Java(TM) SE Runtime Environment GraalVM EE 22.3.0 (build 17.0.5+9-LTS-jvmci-22.3-b07)
Java HotSpot(TM) 64-Bit Server VM GraalVM EE 22.3.0 (build 17.0.5+9-LTS-jvmci-22.3-b07, mixed mode, sharing)

------------------------------------------------------------
Gradle 7.5
------------------------------------------------------------

Build time:   2022-07-14 12:48:15 UTC
Revision:     c7db7b958189ad2b0c1472b6fe663e6d654a5103

Kotlin:       1.6.21
Groovy:       3.0.10
Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM:          17.0.5 (Oracle Corporation 17.0.5+9-LTS-jvmci-22.3-b07)
OS:           Mac OS X 13.1 aarch64

Problem

./build/native/nativeCompile/demo

 :: Spring Boot ::                (v3.0.2)

2023-01-20T15:37:34.264+04:00  WARN 29315 --- [           main] w.s.c.ServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myEventListener': Instantiation of supplied bean failed
2023-01-20T15:37:34.264+04:00  INFO 29315 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2023-01-20T15:37:34.265+04:00 ERROR 29315 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myEventListener': Instantiation of supplied bean failed
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainInstanceFromSupplier(AbstractAutowireCapableBeanFactory.java:1236) ~[demo:6.0.4]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1210) ~[demo:6.0.4]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1157) ~[demo:6.0.4]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:561) ~[demo:6.0.4]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[demo:6.0.4]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[demo:6.0.4]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[demo:6.0.4]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[demo:6.0.4]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[demo:6.0.4]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:961) ~[demo:6.0.4]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:915) ~[demo:6.0.4]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:584) ~[demo:6.0.4]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[demo:3.0.2]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:730) ~[demo:3.0.2]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:432) ~[demo:3.0.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) ~[demo:3.0.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1302) ~[demo:3.0.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1291) ~[demo:3.0.2]
    at com.example.demo.DemoApplication.main(DemoApplication.java:38) ~[demo:na]
Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface com.example.demo.MyListener] 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.5/java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:47) ~[demo:na]
    at java.base@17.0.5/java.lang.reflect.Proxy.newProxyInstance(Proxy.java:1037) ~[demo:na]
    at com.example.demo.MyConfiguration$MyInnerConfiguration.getProxy(MyConfiguration.java:34) ~[demo:na]
    at com.example.demo.MyConfiguration$MyInnerConfiguration.myEventListener(MyConfiguration.java:30) ~[demo:na]
    at com.example.demo.MyConfiguration__BeanDefinitions$MyInnerConfiguration__BeanDefinitions.lambda$getMyEventListenerInstanceSupplier$0(MyConfiguration__BeanDefinitions.java:41) ~[na:na]
    at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:63) ~[demo:6.0.4]
    at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:51) ~[demo:6.0.4]
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$withGenerator$0(BeanInstanceSupplier.java:173) ~[na:na]
    at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:68) ~[demo:6.0.4]
    at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:54) ~[demo:6.0.4]
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$get$2(BeanInstanceSupplier.java:208) ~[na:na]
    at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:59) ~[demo:6.0.4]
    at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:47) ~[demo:6.0.4]
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.invokeBeanSupplier(BeanInstanceSupplier.java:220) ~[na:na]
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:208) ~[na:na]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainInstanceFromSupplier(AbstractAutowireCapableBeanFactory.java:1225) ~[demo:6.0.4]

The following proxy config is generated:

[
  {
    "interfaces": [
      "com.example.demo.MyListener",
      "org.springframework.aop.SpringProxy",
      "org.springframework.aop.framework.Advised",
      "org.springframework.core.DecoratingProxy"
    ]
  }
]

Steps to reproduce

  • Here is a reproducer: demo.zip

  • Unzip, and run ./gradlew clean build and then ./gradlew nativeBuild.

  • Run the generated native application

Comment From: mhalbritter

There's this code in your configuration:

        public <T> T getProxy(Class<T> clazz) {
            return (T) Proxy.newProxyInstance(MyListener.class.getClassLoader(),
                new Class[]{clazz},
                (proxy, method, args) -> {
                    return new ArrayList<>();
                });
        }

As you're creating this proxy in your own code, you'll need to add a new RuntimeHintsRegistrar and register the proxy for the class com.example.demo.MyListener yourself.

Comment From: mmoayyed

I don't follow. Why should I add an entry for this, when one is found and generated for it?

Even adding it, causes a crash later:

Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface com.example.demo.MyListener, interface java.io.Serializable, 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@17.0.5/java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:47) ~[demo:na]
    at java.base@17.0.5/java.lang.reflect.Proxy.newProxyInstance(Proxy.java:1037) ~[demo: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:121) ~[demo:6.0.4]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:435) ~[demo:6.0.4]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1754) ~[demo:6.0.4]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599) ~[demo:6.0.4]
    ... 15 common frames omitted

Comment From: mmoayyed

This is the setup that ultimately seems to work correctly:

[
  {
    "interfaces": [
      "com.example.demo.MyListener",
      "org.springframework.aop.SpringProxy",
      "org.springframework.aop.framework.Advised",
      "org.springframework.core.DecoratingProxy"
    ]
  },
  {
    "interfaces": [
      "com.example.demo.MyListener"
    ]
  },
  {
    "interfaces": [
      "com.example.demo.MyListener",
      "java.io.Serializable",
      "org.springframework.aop.SpringProxy",
      "org.springframework.aop.framework.Advised",
      "org.springframework.core.DecoratingProxy"
    ]
  }
]

...generated by:

    @Override
    public void registerHints(final RuntimeHints hints, final ClassLoader classLoader) {
        hints.proxies().registerJdkProxy(MyListener.class);
        hints.proxies().registerJdkProxy(MyListener.class, Serializable.class,
            SpringProxy.class, Advised.class, DecoratingProxy.class);
    }

If I may, is this really not something that could/should be handled by the plugin?

Comment From: mhalbritter

If you remove this line:

hints.proxies().registerJdkProxy(MyListener.class, Serializable.class,
            SpringProxy.class, Advised.class, DecoratingProxy.class);

then it doesn't work and fails with the:

Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface com.example.demo.MyListener, interface java.io.Serializable, 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@17.0.5/java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:47) ~[demo:na]
    at java.base@17.0.5/java.lang.reflect.Proxy.newProxyInstance(Proxy.java:1037) ~[demo: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:121) ~[demo:6.0.4]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:435) ~[demo:6.0.4]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1754) ~[demo:6.0.4]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599) ~[demo:6.0.4]
    ... 15 common frames omitted

error?

If that's the case, then yes, this should have been detected. Your own creation of the proxy can't be detected, as this happens inside a method body.

Maybe that's too many layers of proxies here :D

Comment From: mmoayyed

That is correct, yes. Ultimately, the only setup that works (so far) is one that generates the two extra blocks in the proxy config, in addition to what the plugin generates (which I don't know if it's actually doing anything to help this case).

Is this not somehow specific to the fact that we are dealing with event listeners here? I am doing very similar things with proxies inside method bodies elsewhere that are not event listeners. Maybe related? (If it helps, I can demonstrate how that works with a demo).

Comment From: mhalbritter

Event listeners work out of the box with no configuration needed:

    @Configuration(value = "MyInnerConfiguration", proxyBeanMethods = false)
    public static class MyInnerConfiguration {

        @Bean
        @Lazy(false)
        public MyListener myEventListener() {
            return new MyListenerImpl();
        }

    }

    static class MyListenerImpl implements MyListener {

        @Override
        public void handleMyEvent(MyApplicationEvent event) {
            System.out.println(event);
        }
    }        

works. The failure in your project is caused by the

        public <T> T getProxy(Class<T> clazz) {
            return (T) Proxy.newProxyInstance(MyListener.class.getClassLoader(),
                new Class[]{clazz},
                (proxy, method, args) -> {
                    return new ArrayList<>();
                });
        }

which is used for "implementing" the listener class. In that case, the hint inference fails.

In my example, these hints are generated:

  {
    "interfaces": [
      "com.example.demo.MyListener",
      "org.springframework.aop.SpringProxy",
      "org.springframework.aop.framework.Advised",
      "org.springframework.core.DecoratingProxy"
    ]
  }

Your example needs a proxy for these classes:

[interface com.example.demo.MyListener, interface java.io.Serializable, interface org.springframework.aop.SpringProxy, interface org.springframework.aop.framework.Advised, interface org.springframework.core.DecoratingProxy]

which is almost the same, but with java.io.Serializable added. I assume this is because of the proxy which "implements" the listener.

Comment From: sbrannen

If I may, is this really not something that could/should be handled by the plugin?

If you invoke Proxy.newProxyInstance(...) within your own code, Spring is not aware of that, and you are responsible for registering the necessary runtime hints to support the proxy you are requesting.

Though I think the more important issue is figuring out why you are creating proxies of your listeners and why Spring is also creating proxies of your listeners.

Based on the reflection configuration you supplied, it appears that there are three (3) proxies being created for your MyListener instance.

  {
    "interfaces": [
      "com.example.demo.MyListener"
    ]
  },

The above looks like it comes from a manual JDK proxy created based solely on the MyListener interface.

  {
    "interfaces": [
      "com.example.demo.MyListener",
      "org.springframework.aop.SpringProxy",
      "org.springframework.aop.framework.Advised",
      "org.springframework.core.DecoratingProxy"
    ]
  },

The above looks like it comes from Spring AOP creating a JDK proxy based on the MyListener interface plus the three additional Spring interfaces used by Spring AOP.

  {
    "interfaces": [
      "com.example.demo.MyListener",
      "java.io.Serializable",
      "org.springframework.aop.SpringProxy",
      "org.springframework.aop.framework.Advised",
      "org.springframework.core.DecoratingProxy"
    ]
  }

The above looks like it comes from Spring AOP creating a JDK proxy created based on the MyListener interface plus the three additional Spring interfaces used by Spring AOP, but this time an instance of the proxy created for one of the previous two scenarios was supplied -- since the proxy now implements Serializable, and IIRC every JDK proxy instance implements Serializable.

In other words -- without having debugged the code in a native image -- it appears that you have three levels of proxies: some created by you and some created by Spring.

Comment From: mmoayyed

If you invoke Proxy.newProxyInstance(...) within your own code, Spring is not aware of that, and you are responsible for registering the necessary runtime hints to support the proxy you are requesting.

Allow me to clarify: I am creating many other proxies in the code using the same exact technique, and so far none have any issues. It's possible I have not run into issues yet, (given I am not able to proceed with the full native build just yet due to other non-related issues) but they (those proxies) seem to be OK. This is why I was suggesting that this might be isolated to event listeners, and not everything else that is a proxy. For the proxies that I do create, and that are NOT event listeners, I certainly register them in the native image metadata as I should and as you indicated. The same technique is challenged here with AOP perhaps and in that it likely wraps a proxy around my own proxy :) So as someone suggested above, yes this might be too many proxies!

As for why: This is just an experiment and it's a very long answer. It has to do with how @ConditionalOnProperty definitions work with Spring Cloud's @RefreshScope. For background: https://github.com/spring-cloud/spring-cloud-config/issues/1645

For a bean to be refreshable, it must exist in the first place. @ConditionalOnProperty might negate that, so instead and as an experiment, I am creating a proxy in the negative case that can later on be refreshed and possibly re-created. This is also required due to the fact that object references held by a listener are also refreshable and whose reference might have changed as part of a refresh event.

Comment From: sdeleuze

Re-reading the discussion here, I don't see an actionable item here. If the user project code is creating a proxy, it should registers related hints. If Spring Cloud is registering additional proxies for @RefreshScope, it should be its responsibility to register related hints.