I've encountered a use-case in which RuntimeHintsUtils.registerAnnotation isn't enough to work with the annotation in a native setting. I'm using this code:
RuntimeHintsUtils.registerAnnotation(hints, Endpoint.class);
where Endpoint is org.springframework.boot.actuate.endpoint.annotation.Endpoint:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Reflective
public @interface Endpoint {
String id() default "";
boolean enableByDefault() default true;
}
There are no @AliasFor annotations used, so RuntimeHintsUtils.registerAnnotation won't register a proxy for Endpoint and SynthesizedAnnotation.
But if Endpoint is now used in this code:
String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.applicationContext, Endpoint.class);
it fails with
Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface org.springframework.boot.actuate.endpoint.annotation.Endpoint, interface org.springframework.core.annotation.SynthesizedAnnotation] 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 com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:89) ~[na:na]
at com.oracle.svm.reflect.proxy.DynamicProxySupport.getProxyClass(DynamicProxySupport.java:158) ~[na:na]
at java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:48) ~[actuator-aot:na]
at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:1037) ~[actuator-aot:na]
at org.springframework.core.annotation.SynthesizedMergedAnnotationInvocationHandler.createProxy(SynthesizedMergedAnnotationInvocationHandler.java:305) ~[na:na]
at org.springframework.core.annotation.TypeMappedAnnotation.createSynthesizedAnnotation(TypeMappedAnnotation.java:333) ~[na:na]
at org.springframework.core.annotation.AbstractMergedAnnotation.synthesize(AbstractMergedAnnotation.java:210) ~[actuator-aot:6.0.0-SNAPSHOT]
at org.springframework.core.annotation.AbstractMergedAnnotation.synthesize(AbstractMergedAnnotation.java:200) ~[actuator-aot:6.0.0-SNAPSHOT]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAnnotationOnBean(DefaultListableBeanFactory.java:733) ~[actuator-aot:6.0.0-SNAPSHOT]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAnnotationOnBean(DefaultListableBeanFactory.java:723) ~[actuator-aot:6.0.0-SNAPSHOT]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForAnnotation(DefaultListableBeanFactory.java:693) ~[actuator-aot:6.0.0-SNAPSHOT]
at org.springframework.context.support.AbstractApplicationContext.getBeanNamesForAnnotation(AbstractApplicationContext.java:1301) ~[actuator-aot:6.0.0-SNAPSHOT]
at org.springframework.beans.factory.BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(BeanFactoryUtils.java:285) ~[na:na]
at org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer.createEndpointBeans(EndpointDiscoverer.java:130) ~[actuator-aot:3.0.0-SNAPSHOT]
at org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer.discoverEndpoints(EndpointDiscoverer.java:123) ~[actuator-aot:3.0.0-SNAPSHOT]
at org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer.getEndpoints(EndpointDiscoverer.java:117) ~[actuator-aot:3.0.0-SNAPSHOT]
at org.springframework.boot.actuate.autoconfigure.endpoint.web.ServletEndpointManagementContextConfiguration$WebMvcServletEndpointManagementContextConfiguration.servletEndpointRegistrar(ServletEndpointManagementContextConfiguration.java:61) ~[actuator-aot:0.0.1-SNAPSHOT]
at org.springframework.boot.actuate.autoconfigure.endpoint.web.ServletEndpointManagementContextConfiguration_WebMvcServletEndpointManagementContextConfiguration__BeanDefinitions.lambda$getServletEndpointRegistrarInstance$0(ServletEndpointManagementContextConfiguration_WebMvcServletEndpointManagementContextConfiguration__BeanDefinitions.java:45) ~[na:na]
at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:63) ~[actuator-aot:6.0.0-SNAPSHOT]
at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:51) ~[actuator-aot:6.0.0-SNAPSHOT]
at org.springframework.beans.factory.aot.AutowiredInstantiationArgumentsResolver.resolve(AutowiredInstantiationArgumentsResolver.java:156) ~[na:na]
at org.springframework.boot.actuate.autoconfigure.endpoint.web.ServletEndpointManagementContextConfiguration_WebMvcServletEndpointManagementContextConfiguration__BeanDefinitions.getServletEndpointRegistrarInstance(ServletEndpointManagementContextConfiguration_WebMvcServletEndpointManagementContextConfiguration__BeanDefinitions.java:45) ~[na:na]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainInstanceFromSupplier(AbstractAutowireCapableBeanFactory.java:1224) ~[actuator-aot:6.0.0-SNAPSHOT]
... 50 common frames omitted
It seems like @AliasFor isn't the only thing which should trigger proxy creation.
At the moment I'm working around this with:
hints.proxies().registerJdkProxy(Endpoint.class, SynthesizedAnnotation.class);
Comment From: sbrannen
I've encountered a use-case in which
RuntimeHintsUtils.registerAnnotationisn't enough to work with the annotation in a native setting.
That's to be expected. RuntimeHintsUtils.registerAnnotation can only reason about the annotation itself and meta-annotations in the meta-annotation hierarchy above the supplied annotation type.
If @Endpoint is used as a meta-annotation with attribute overrides in a composed annotation, you should actually invoke RuntimeHintsUtils.registerAnnotation with the composed annotation instead of Endpoint.class. Then RuntimeHintsUtils.registerAnnotation should automatically register the correct proxy class configuration for @Endpoint.
Comment From: sbrannen
One concrete example is @WebEndpoint whose attributes are annotated with @AliasFor(annotation = Endpoint.class), which in turn requires that @Endpoint by synthesizable (i.e., will need proxy configuration).
In summary, this effectively "works as designed", but I'll leave it open in order for the team to put more thought into it and potentially to improve the documentation to point out what I explained above.
Comment From: mhalbritter
Ah, I see. Thanks for the explanation.
Comment From: mhalbritter
If
@Endpointis used as a meta-annotation with attribute overrides in a composed annotation, you should actually invoke RuntimeHintsUtils.registerAnnotation with the composed annotation instead of Endpoint.class. Then RuntimeHintsUtils.registerAnnotation should automatically register the correct proxy class configuration for@Endpoint
When I register WebEndpoint, there will be no proxy hints generated for Endpoint, only for WebEndpoint.
Comment From: sbrannen
When I register
WebEndpoint, there will be no proxy hints generated forEndpoint, only forWebEndpoint.
Ahhh... then that's likely a bug.
We'll look into it.