spring-guice icon indicating copy to clipboard operation
spring-guice copied to clipboard

Support for SpringBoot 3 Aot and GraalVM native images

Open eahau opened this issue 2 years ago • 7 comments

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);
	}

eahau avatar Jan 18 '23 05:01 eahau

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?

dsyer avatar Jan 30 '23 12:01 dsyer

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

eahau avatar Feb 01 '23 06:02 eahau

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)?

dsyer avatar Feb 01 '23 06:02 dsyer

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

eahau avatar Feb 01 '23 10:02 eahau

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 avatar Feb 03 '23 09:02 dsyer

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.

dsyer avatar Feb 07 '23 11:02 dsyer

Thanks! I will try this demo.

eahau avatar Feb 07 '23 13:02 eahau