spring-projects / spring-guice

Tools for using Spring in Guice and Guice in Spring
Apache License 2.0
171 stars 64 forks source link

Support for SpringBoot 3 Aot and GraalVM native images #108

Open eahau opened 1 year ago

eahau commented 1 year ago

Version Info

java:17 spring-boot:3.0.1 spring-framework:6.0.3 spring-guice:2.0.2 native-maven-plugin:0.9.19

Description

I followed the requirements of the SpringBoot3 native-image document and made relevant configuration. like this:

  <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>${spring-boot.version}</version>
        <configuration>
          <mainClass>${mainClass}</mainClass>
        </configuration>
        <executions>
          <execution>
            <configuration>
              <jvmArguments>
<!--                -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005-->
              </jvmArguments>

            </configuration>
            <id>process-aot</id>
            <goals>
              <goal>process-aot</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

      <plugin>
        <groupId>org.graalvm.buildtools</groupId>
        <artifactId>native-maven-plugin</artifactId>
       <version>${native-maven-plugin.version}</version>
      </plugin>

But encountered java.lang.IllegalArgumentException: Code generation does not support com.google.inject.Key<?> when execute spring-boot-maven-plugin#process-aot

image

BeanDefinitionPropertyValueCodeGenerator#generateCode

private CodeBlock generateCode(@Nullable Object value, ResolvableType type) {
        if (value == null) {
            return NULL_VALUE_CODE_BLOCK;
        }
        for (Delegate delegate : this.delegates) {
            CodeBlock code = delegate.generateCode(value, type);
            if (code != null) {
                return code;
            }
        }
        throw new IllegalArgumentException("Code generation does not support " + type);
    }
dsyer commented 1 year ago

This seems entirely expected, but I didn't see the same error when I tried it with an empty project from start.spring.io. Whether it can be fixed or not I don't know. Maybe you could provide a complete, minimal sample?

eahau commented 1 year ago

This seems entirely expected, but I didn't see the same error when I tried it with an empty project from start.spring.io. Whether it can be fixed or not I don't know. Maybe you could provide a complete, minimal sample?

You can download and unzip spring-native-guice-demo.zip And mvn -X -Dmaven.test.skip=true package

dsyer commented 1 year ago

Thanks, that helps. Using @EnableGuiceModules is enough, in fact, to make a project fail on AOT processing.

I don't know if this is fixable. Spring Guice does some really extremely dynamic things to an ApplicationContext and one of the assumptions of AOT is that the ApplicationContext is fixed at build time. Actually I'm surprised it didn't break in other places, or possibly it did and this is just the first of a long series of errors. If we do fix it there will likely be compromises to do with the "fixed world" assumptions that have to be made in AOT - behaviour at runtime for some apps might be different. Hopefully that wouldn't affect too many people, and it's consistent with the limitations of AOT generally, not just with Spring Guice.

BTW why did you exclude spring-context from the dependencies (shouldn't be necessary)?

eahau commented 1 year ago

BTW why did you exclude spring-context from the dependencies (shouldn't be necessary)?

spring-boot 3.0.1 need spring-context 6.0.3, but spring-guice 2.0.2 need spring-context 5.3.16 This should have no impact.

image
dsyer commented 1 year ago

With some changes to spring-guice (replacing constructor-based bean definitions with reflection-free variants) I was able to make your sample compile with AOT. But it will never run in GraalVM native, unless Guice supports it, and I don't see any appetite for that. If AOT on its own is interesting for anyone I can show you how to do it.

dsyer commented 1 year ago

I think I got something working. Try 2.0.3-SNAPSHOT and make sure you add reflection hints like this:

class DemoRuntimeHints implements RuntimeHintsRegistrar {

    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        // Guice needs reflection access to at least these...
        hints.reflection().registerType(Integer.class, MemberCategory.INVOKE_DECLARED_METHODS);
        hints.reflection().registerType(Long.class, MemberCategory.INVOKE_DECLARED_METHODS);
        hints.reflection().registerType(Double.class, MemberCategory.INVOKE_DECLARED_METHODS);
        hints.reflection().registerType(Float.class, MemberCategory.INVOKE_DECLARED_METHODS);
        hints.reflection().registerType(Boolean.class, MemberCategory.INVOKE_DECLARED_METHODS);
        hints.reflection().registerType(Byte.class, MemberCategory.INVOKE_DECLARED_METHODS);
        hints.reflection().registerType(Short.class, MemberCategory.INVOKE_DECLARED_METHODS);
        hints.reflection().registerType(Named.class, MemberCategory.INTROSPECT_DECLARED_METHODS);
        // add more here  - all the Guice modules declared as @Bean and all classes bound in those, e.g.
        hints.reflection().registerType(MyModule.class, MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
        hints.reflection().registerType(MyService.class, MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
        ...
    }
}

and add a filter like this in META-INF/spring.aot.factories:

org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter=\
com.example.demo.ExcludeFilter

where

public class ExcludeFilter implements BeanRegistrationExcludeFilter {

    static final String IGNORE_ME = "spring-guice";

    @Override
    public boolean isExcludedFromAotProcessing(RegisteredBean registeredBean) {
        if (registeredBean.getMergedBeanDefinition().hasAttribute(IGNORE_ME)) {
            return true;
        }
        return false;
    }

}

Complete sample: https://github.com/scratches/guice-demo.

eahau commented 1 year ago

Thanks! I will try this demo.