spring-projects / spring-hateoas

Spring HATEOAS - Library to support implementing representations for hyper-text driven REST web services.
https://spring.io/projects/spring-hateoas
Apache License 2.0
1.04k stars 476 forks source link

Links are not deserialized in Spring Boot Native applications #2210

Closed klopfdreh closed 2 months ago

klopfdreh commented 2 months ago

Currently we have the issue that _links are not able to be deserialized in one of our Spring Boot Native application at client side (Spring Boot 3.3.2)

We are using the Spring Cloud Data Flow rest client (SCDF 2.11.2) and manually enabled the resources (e.g. RootResource https://github.com/spring-cloud/spring-cloud-dataflow/blob/v2.11.2/spring-cloud-dataflow-rest-resource/src/main/java/org/springframework/cloud/dataflow/rest/resource/RootResource.java and all other resources via RuntimeHintsRegistrar) for reflections.

The aot-process however shows that the HATEOAS types are registered for reflections:

{"timestamp":"2024-08-29T08:09:28.265+0200","level":"INFO","thread":"main","logger":"org.springframework.hateoas.aot.AotUtils","message":"Registering Spring HATEOAS types in org.springframework.hateoas for reflection.","context":"default"}

The error occurs when receiving the ABOUT_REL in the DataFlowTemplate see: https://github.com/spring-cloud/spring-cloud-dataflow/blob/v2.11.2/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/DataFlowTemplate.java#L170

We just checked that all required dependencies are included so that the HypermediaAutoConfiguration can be loaded.

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'launcher': Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'dataFlowOperations': Instantiation of supplied bean failed
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveAutowiredArgument(BeanInstanceSupplier.java:345)
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveArguments(BeanInstanceSupplier.java:265)
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:204)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:949)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1237)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1180)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:971)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:625)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:335)
    at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:149)
    at batch.basic.tasks.serial.launcher.SerialLauncherApplication.main(SerialLauncherApplication.java:29)
    at java.base@22.0.1/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataFlowOperations': Instantiation of supplied bean failed
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1243)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1180)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1443)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353)
    at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:904)
    at org.springframework.beans.factory.support.RegisteredBean.resolveAutowiredArgument(RegisteredBean.java:253)
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveAutowiredArgument(BeanInstanceSupplier.java:342)
    ... 20 more
Caused by: java.util.NoSuchElementException: No value present
    at java.base@22.0.1/java.util.Optional.get(Optional.java:143)
    at org.springframework.cloud.dataflow.rest.client.DataFlowTemplate.<init>(DataFlowTemplate.java:170)
    at batch.rest.starter.dataflow.DataFlowClientConnectionUtils.getClientCredentialsAuthorizedDataFlowOperations(DataFlowClientConnectionUtils.java:64)
    at batch.rest.starter.dataflow.properties.DataFlowClientAutoConfiguration.dataFlowOperations(DataFlowClientAutoConfiguration.java:32)
    at batch.rest.starter.dataflow.properties.DataFlowClientAutoConfiguration__BeanDefinitions.lambda$getDataFlowOperationsInstanceSupplier$0(DataFlowClientAutoConfiguration__BeanDefinitions.java:31)
    at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:68)
    at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:54)
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$get$2(BeanInstanceSupplier.java:206)
    at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58)
    at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46)
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.invokeBeanSupplier(BeanInstanceSupplier.java:219)
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:206)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:949)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1237)
    ... 33 more

I also tried to add the following annotations to my application without any success.

@EnableHypermediaSupport(type = {HAL, HAL_FORMS}, stacks = {WEBFLUX, WEBMVC})
@EnableWebMvc

With a normal Java JVM application the code is running without any issues.

odrotbohm commented 2 months ago

I think we need someone from the DataFlow team to look into this, as calling Optional.get() without a guard is clearly problematic. I know too little about DataFlow to judge what could generally cause this. They seem to use a lot of custom RepresentationModel extenstion that they would of course have to make available for reflection, too.

corneil commented 2 months ago

SCDF 2.x is based on Boot 2.x So there may be elements that hasn't gone through the preparation for AOT and Native support.

Creating a 3.x client will require a separate module with modified copies of related classes that have been updated and tested with Boot 3.x

klopfdreh commented 2 months ago

Beside this there are a lot of frameworks which need to be adjusted to be able to be used with Spring Boot Native.

If you follow my recent changes I contributed a lot to AWS SDK / AWS CRT and also to Spring Cloud Config (https://github.com/spring-cloud/spring-cloud-config/issues/2439) require changes.

What we did is to add all classes in the packages (and sub packages):

https://github.com/spring-cloud/spring-cloud-dataflow/tree/main/spring-cloud-dataflow-rest-resource/src/main/java/org/springframework/cloud/dataflow/rest/resource

and

https://github.com/spring-cloud/spring-cloud-dataflow/tree/main/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client

for reflections, similar like explained here:

https://github.com/hub4j/github-api/issues/1908#issuecomment-2296488149

So there are no issues anymore during the build time but only during the runtime that the links are null like explained in this issue.

So are there any other classes required to be included for reflections? Maybe I am able to make the REST API of SCDF 2.11.x be usable in Spring Boot Native.

odrotbohm commented 2 months ago

Does it make sense to continue the work on this ticket in the SCDF issue tracker?

corneil commented 2 months ago

Does it make sense to continue the work on this ticket in the SCDF issue tracker?

Yes.

klopfdreh commented 2 months ago

Followup ticket is created - I am going to close this one here.

klopfdreh commented 2 months ago

@odrotbohm - I finally solved the issue in the follow up ticket and I think it is a Spring Hateoas issue. I explained it here: https://github.com/spring-cloud/spring-cloud-dataflow/issues/5924#issuecomment-2337411193 - let me know if I should reopened this issue and instead closing the one of the SCDF issue track