spring-attic / spring-native

Spring Native is now superseded by Spring Boot 3 official native support
https://docs.spring.io/spring-boot/docs/current/reference/html/native-image.html
Apache License 2.0
2.74k stars 356 forks source link

Spring Cloud Function - Failed to locate function when using scanBasePackages #1295

Closed aks-wp closed 2 years ago

aks-wp commented 2 years ago

Context: Migrating Spring Cloud Function AWS apps to use Spring Native

I've been able to run dsyer's spring cloud function aws demo successfully, but when migrating my existing cloud function apps to spring native, I get the error "Failed to locate function". Running in debug mode, I can see it does try to look for my function

c.f.c.c.BeanFactoryAwareFunctionRegistry : Function '****' is not available in FunctionCatalog or BeanFactory _Caused by: java.lang.IllegalArgumentException: Failed to locate function. Tried locating default function, function by 'DEFAULT_HANDLER', 'HANDLER' env variable as well as'spring.cloud.function.definition'. Functions available in catalog are: [functionRouter]

The main difference between the demo app and the real app is scanning base packages @SpringBootApplication(scanBasePackages = "com.xyz.package.*")

Removing that ensures the function is invoked.

I've used spring native version 0.10.4 and 0.10.5 (Release versions), 0.11.0-SNAPSHOT and RC1 with Spring Cloud functions 3.1.5 and 3.2.0-M3 respectively.

The scrubbed version in a nutshell is

@SpringBootApplication(scanBasePackages = "com.xyz.package.*")
public class Application  {
  public static void main(String[] args) { SpringApplication.run(Application.class, args); }
}
@Component
public class XYZFunction implements Function<Foo, Foo> {

  @Autowired Bar bar;

  @Override
  public fn apply(final Foo foo) {

  //business logic
  }

}

cc: @olegz (Not sure if this issue belongs here or in the Cloud Function repo)

sdeleuze commented 2 years ago

Could you please double check that your application is working as expected without AOT? I am asking that because:

aks-wp commented 2 years ago
  • Shouldn't @SpringBootApplication(scanBasePackages = "com.xyz.package.*") be @SpringBootApplication(scanBasePackages = "com.xyz.package")?

Yes, the original app actually doesn't have the wildcard at the end, but without it, the native image would complain about missing dependencies...

aks-wp commented 2 years ago

With regards to running the app without AOT... I get the following error when starting the native image

ERROR org.springframework.boot.SpringApplication - Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor': 
Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException:
Failed to instantiate [org.springframework.context.annotation.ConfigurationClassPostProcessor]: 
No default constructor found; nested exception is java.lang.NoSuchMethodException: org.springframework.context.annotation.ConfigurationClassPostProcessor.<init>()

(If you meant, as a regular cloud function app - yes, those are running live today - this is just a migration effort to move them to Native)

aks-wp commented 2 years ago

@sdeleuze Thank you for the pointer about the wildcard used at the end of scanBasePackages - that seems to have caused the issue about being unable to locate the function. Interestingly, when I used the functional style earlier, I didn't get the same issue with the wildcard, so I didn't think too much of it.

Now I'm working through a different issue with a NPE by adding hints. (Since these are from other modules). Will keep you posted.

Caused by: java.lang.NullPointerException: null at org.springframework.cloud.function.adapter.aws.AWSLambdaUtils.generateMessage(AWSLambdaUtils.java:134) ~[na:na] at org.springframework.cloud.function.adapter.aws.AWSLambdaUtils.generateMessage(AWSLambdaUtils.java:68) ~[na:na] at org.springframework.cloud.function.adapter.aws.CustomRuntimeEventLoop.eventLoop(CustomRuntimeEventLoop.java:89) ~[scrubbed] at org.springframework.cloud.function.adapter.aws.CustomRuntimeInitializer.lambda$null$0(CustomRuntimeInitializer.java:47) ~[na:na] at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:791)

aks-wp commented 2 years ago

Okay, so @sdeleuze I found a work around from an existing issue (https://github.com/spring-projects-experimental/spring-native/issues/241) . The trick seemed to be to generate spring.components in the dependencies and use that jar instead.

However, Autowiring dependencies still seems to be an issue and I get NoSuchBeanDefinitionException. Since I'm using 0.10.4, I'd be safe to assume (https://github.com/spring-projects-experimental/spring-native/issues/782) is fixed. So I removed spring-context-indexer from the main pom (I only use it now in the dependencies as mentioned above) and I don't get the NoSuchBeanDefinitionException for my dependency. But when deploying it to AWS lambda, the function throws the below error

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.cloud.function.context.FunctionCatalog' available at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351) ~[na:na] at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342) ~[na:na] at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1172) ~[na:na] at org.springframework.cloud.function.adapter.aws.CustomRuntimeEventLoop.eventLoop(CustomRuntimeEventLoop.java:76) ~[] at org.springframework.cloud.function.adapter.aws.CustomRuntimeInitializer.lambda$null$0(CustomRuntimeInitializer.java:47) ~[na:na] at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:791)

Note that if I keep the indexer in the pom and remove the autowiring, everything works as expected. But since these are multiple microservices hooked together, I'm hoping to get all the pieces working together on the cloud function app.

I'll also try to get a reproducer going, but in the meantime, if you have any suggestions that I can try out, do let me know. Basically, from my original post, the @Autowired Bar bar is from the dependency jar.

Thanks much!

sdeleuze commented 2 years ago

@olegz @OlgaMaciaszek Could you please check why spring.components is needed here with Spring Cloud Function (we should not rely on that) and if that's Spring Cloud Function specific or a more global issue?

olegz commented 2 years ago

Is there a sample application that demonstrates the issue? Both https://github.com/spring-projects-experimental/spring-native/tree/main/samples/cloud-function-aws and https://github.com/spring-projects-experimental/spring-native/tree/main/samples/cloud-function-netty have ben tested in AWS without issues. I basically need a way to reproduce it so a sample app with bare minimum in github somewhere would be nice

sdeleuze commented 2 years ago

@aks-wp Please provide a repro project.

aks-wp commented 2 years ago

@olegz Yeah, I've had no problem with running a simple demo app. In the first link you shared, the only difference is, instead of Function<String, String>, I use Function<FooBar, FooBar> and FooBar is a from a dependency (and is a different package so scanBasePackages is used) There's no problem starting the app locally, but it fails on AWS. I'll try to get the repro over the weekend.

olegz commented 2 years ago

Ok, i'll give it a shot shortly based on your description, see if I can reproduce

olegz commented 2 years ago

I seemed to reproduced something, but it is not what you have. Basically i modified the sample to look like

@Component
public class NameUppercaser implements Function<Person, String> {

    @Override
    public String apply(Person input) {
        return "hi " + input.getName().toUpperCase() + "!";
    }
}

with Person looking like

public class Person {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }
}

And i get this

Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.demo.domain.Person` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

. . which is very weird and most definitely leads me to believe that there is some native voodoo going on here since this is the simplest possible example and runs find locally and in AWS without native.

Will keep on digging. . .

olegz commented 2 years ago

@aks-wp I just updated the sample - https://github.com/spring-projects-experimental/spring-native/tree/main/samples/cloud-function-aws to use POJO and successfully tested it in AWS. Please take a look and let me know if I am missing something as I still can not reproduce your error

aks-wp commented 2 years ago

Thanks @olegz - I've forked your sample. For simplicity sake, I've moved Person to a different package (in reality, this would be it's own module) but my main point was using scanBasePackages seems to cause issues for me - https://github.com/aks-wp/spring-native/blob/main/samples/cloud-function-aws/src/main/java/com/example/demo/DemoApplication.java#L10

olegz commented 2 years ago

@aks-wp i see, but scanBasePackages is part of the spring-boot, so I am not sure what relevance does it have for s-c-function. Perhaps I am missing something. May be @sdeleuze @snicoll can chime in?

olegz commented 2 years ago

Actually, just chatted with @sdeleuze , so let me modify the sample and test it again. Will let you know once done

olegz commented 2 years ago

@aks-wp I now think there is no issue as I just tested it with

@SpringBootApplication(scanBasePackages="com.example.demo")

Now, couple of problems that could get you off track:

  1. if you add asterisk (e.g., scanBasePackages = "com.example.demo.*) that would simply be the wrong syntax and would not work. By definition of 'base package' it (the asterisk) is not needed, since it already implies "start scanning from here and recursively scan the rest of the hierarchy"
  2. In your case you have scanBasePackages=com.example.dependency which is a non-existing package in your example, so it will not work since by scanning it it can not find anything.
  3. Keep in mind as boot javadoc for scanBasePackages says "Base packages to scan for annotated components". It is not a general class loading instruction to search for any type of classes. So if you simply have some POJO in some package that is not spring managed component it will not work either.

@sdeleuze I believe we can resolve it as there is nothing to be done. As for the TypeHint and implementation of BeanFactoryNativeConfigurationProcessor to simplify things for the user, that is a whole other issue which I am already working on, so you can create one and assign to me.