Closed itineric closed 1 year ago
Hi @itineric The project has not verified with native compilation yet, any contribution on this field is welcome.
I got something that works based on your servlet/resteasy-servlet-spring-boot-sample-app.
Hope it helps you to add your lib to the graalvm supported ones: https://www.graalvm.org/native-image/libraries-and-frameworks/
Bean double registration
First you need to declare a BeanRegistrationExcludeFilter because of this code in your library: https://github.com/resteasy/resteasy-spring-boot/blob/main/servlet/resteasy-servlet-spring-boot-starter/src/main/java/org/jboss/resteasy/springboot/ResteasyBeanProcessorTomcat.java#L57
Why ? the bean registered there is added once during Spring AOT process, then again during the runtime. The error is then The bean 'com.sample.app.configuration.JaxrsApplication' could not be registered. A bean with that name has already been defined and overriding is disabled.
The implementation of the BeanRegistrationExcludeFilter looks like this:
class ServletRegistrationBeanExcludeFilter implements BeanRegistrationExcludeFilter
{
@Override
public boolean isExcludedFromAotProcessing(final RegisteredBean registeredBean)
{
return registeredBean.getBeanClass().equals(ServletRegistrationBean.class);
}
}
Maybe in more complex applications filtering needs to be more specific (not excluding every ServletRegistrationBean from AOT process). But for applications built like the sample, it works.
Another solution may be to keep AOT for that bean registration (thats the point of AOT after all) but then the registration code in https://github.com/resteasy/resteasy-spring-boot/blob/main/servlet/resteasy-servlet-spring-boot-starter/src/main/java/org/jboss/resteasy/springboot/ResteasyBeanProcessorTomcat.java must be excluded from native deliverable (since already done during AOT).
In order for the BeanRegistrationExcludeFilter to be triggered it must be declared in a file named META-INF/spring/aot.factories using the following key: org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter=com.sample.app.configuration.ServletRegistrationBeanExcludeFilter
Reflection hints
When thats done, you then get reflection errors. Such errors can to be addressed using hints or reflect-config.json files. I have done it using hints (which spring native will use to generation reflect-config.json).
The class with comments:
import static org.springframework.aot.hint.ExecutableMode.INVOKE;
import java.io.OutputStream;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportRuntimeHints;
@Configuration
@ImportRuntimeHints(NativeImageHints.class)
public class NativeImageHints implements RuntimeHintsRegistrar
{
@Override
public void registerHints(final RuntimeHints hints, final ClassLoader classLoader)
{
try
{
hints.reflection()
// resteasy hints (yous should add them to your lib for now since resteasy is not (yet) a supported graalvm lib)
.registerConstructor(org.jboss.resteasy.resteasy_jaxrs.i18n.LogMessages_$logger.class.getConstructor(org.jboss.logging.Logger.class),
INVOKE)
.registerConstructor(org.jboss.resteasy.plugins.validation.i18n.LogMessages_$logger.class.getConstructor(org.jboss.logging.Logger.class),
INVOKE)
.registerField(org.jboss.resteasy.resteasy_jaxrs.i18n.Messages_$bundle.class.getField("INSTANCE"))
.registerField(org.jboss.resteasy.plugins.validation.i18n.Messages_$bundle.class.getField("INSTANCE"))
.registerConstructor(classLoader.loadClass("org.jboss.resteasy.core.ContextServletOutputStream").getDeclaredConstructor(classLoader.loadClass("org.jboss.resteasy.core.ContextParameterInjector"),
OutputStream.class),
ExecutableMode.INVOKE)
// this lib hint
.registerType(classLoader.loadClass("org.jboss.resteasy.springboot.ResteasyApplicationBuilder"),
MemberCategory.INTROSPECT_PUBLIC_METHODS,
MemberCategory.INVOKE_PUBLIC_METHODS)
// app hints (thats only here to make the sample work, must not be included in your lib)
.registerType(JaxrsApplication.class,
MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)
.registerType(org.jboss.resteasy.springboot.common.sample.resources.Echo.class,
MemberCategory.INVOKE_PUBLIC_METHODS)
.registerType(org.jboss.resteasy.springboot.common.sample.resources.Foo.class,
MemberCategory.INVOKE_PUBLIC_METHODS)
.registerType(org.jboss.resteasy.springboot.common.sample.resources.EchoMessage.class,
MemberCategory.INTROSPECT_PUBLIC_METHODS,
MemberCategory.INVOKE_PUBLIC_METHODS);
}
catch (final ClassNotFoundException | NoSuchMethodException | NoSuchFieldException | SecurityException exception)
{
throw new RuntimeHintsException(exception);
}
}
private static class RuntimeHintsException extends RuntimeException
{
private static final long serialVersionUID = 1L;
RuntimeHintsException(final Throwable cause)
{
super(cause);
}
}
}
The build
To build all that natively, thats the profile I added to the pom.xml
<build>
...
...
...
<pluginManagement>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.9.25</version>
<extensions>true</extensions>
</plugin>
</plugins>
</pluginManagement>
</build>
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder:tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
</env>
</image>
</configuration>
<executions>
<execution>
<id>process-aot</id>
<goals>
<goal>process-aot</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<configuration>
<classesDirectory>${project.build.outputDirectory}</classesDirectory>
<metadataRepository>
<enabled>true</enabled>
</metadataRepository>
<requiredVersion>22.3</requiredVersion>
</configuration>
<executions>
<execution>
<id>add-reachability-metadata</id>
<goals>
<goal>add-reachability-metadata</goal>
</goals>
</execution>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
<configuration>
<fallback>false</fallback>
<buildArgs>
<arg>--install-exit-handlers</arg>
</buildArgs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
And the command line: mvn -f servlet/resteasy-servlet-spring-boot-sample-app -Pnative package
that will produce some ./servlet/resteasy-servlet-spring-boot-sample-app/target/resteasy-servlet-spring-boot-sample-app
executable
Resources: Setup graalvm: https://www.graalvm.org/jdk17/docs/getting-started/ The graalvm manual to understand what I am talking about: https://www.graalvm.org/latest/reference-manual/native-image/metadata/
@itineric Thanks for sharing the information! I've talked with @jamezp on this topic, who is the leader of the RESTEasy project, and the conclusion is that currently the RESTEasy project hasn't supported native build formally.(In Quarkus it uses resteasy-reactive which officially supports native build). So the native build of RESTEasy projects are not formally supported yet (except the resteasy-reactive
for Quarkus). But any contribution in this field is welcome :D
Close as answered.
I was trying to run the sample app compiled with spring native, compilation works fine but I am getting an error on app startup: ` The bean 'com.sample.app.configuration.JaxrsApplication' could not be registered. A bean with that name has already been defined and overriding is disabled.
` AOT seems fine on the app, I cannot understand how it finds the same bean many times. Any clue ?