Spring
Spring Native provides experimental support of building native images for Spring projects.
This page is based on Spring Native 0.12.0.
Spring Native is still an experimental project. Use with caution.
Getting Started
When creating a new Spring Boot project, the easiest way to enable Spring Native is using Spring initializr. When adding dependencies, make sure Spring Native is selected.
For an existing project, you need to add Spring Native configurations manually. This can be done by comparing a newly created Spring Native project with your existing project. You may only need to update Maven or Gradle build configurations to get it working.
A simple Maven project is used as an example. Full source of this project can be found at GitHub.
Spring Native provides two approaches to build native images, using Buildpacks or Native Build Tools. The difference is that Buildpacks requires Docker to build container images, but Native Build Tools requires local GraalVM to build native executables.
Buildpacks
When using Buildpacks, the following command can be used to build a container image.
./mvnw spring-boot:build-image
The native image buildpack is used to build a container image with native images. The actual builder used is paketobuildpacks/builder:tiny
. The BP_NATIVE_IMAGE
environment variable enables this buildpack. Extra options can be passed to native-image
using the BP_NATIVE_IMAGE_BUILD_ARGUMENTS
environment variable.
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>${repackage.classifier}</classifier>
<image>
<builder>paketobuildpacks/builder:tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
</env>
</image>
</configuration>
</plugin>
The build result is a container image with the native image of the current Spring project.
docker run --rm -p 8080:8080 grpc:1.0.0-SNAPSHOT
Native Build Tools
When using Native Build Tools, the following command can be used to build a native image.
./mvnw -Pnative package
Maven or Gradle plugin provided by GraalVM is used to invoke native-image
tool to build a native image. For Maven projects, the native
profile contains the configurations of Native Build Tools Maven plugin.
<profile>
<id>native</id>
<properties>
<repackage.classifier>exec</repackage.classifier>
<native-buildtools.version>0.9.9</native-buildtools.version>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${native-buildtools.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<id>test-native</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
</execution>
<execution>
<id>build-native</id>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
The build result is a native executable in the target
directory.
Native Hints
If an application requires specific configurations related to native image generation, it can add hints for Spring Native to update the configurations used by native image builder.
The easiest way to provide native hints is using annotations defined in the org.springframework.nativex.hint
package. These annotations are usually added to the Spring Boot application class.
The @NativeHint
annotation provides hints. The table below lists attributed related to Native Image configurations. These attributes are used to generate build configurations for native-image
. Values of these attributes are other annotation types to provide configuration details.
Attribute | Type | Description |
---|---|---|
options | String[] | Options passed to native-image |
types | TypeHint[] | Types that should be accessible |
resources | ResourceHint[] | Resources to be included |
initialization | InitializationHint[] | Class initialization configurations |
jdkProxies | JdkProxyHint[] | JDK dynamic proxies |
serializables | SerializationHint[] | Serialization configurations |
aotProxies | AotProxyHint[] | Class based proxies that should be created at image build time |
Options
Options are passed to the native-image
command. These options will be added to the native-image.properties
file.
@NativeHint(
options = {
"-H:+ReportExceptionStackTraces"
}
)
Types
@TypeHint
annotation configures reflectively accessed elements for Native Image.
The table below shows the attributes.
Attribute | Type | Description |
---|---|---|
types | Class<?>[] | Class references |
typeNames | String[] | Type names of classes |
methods | MethodHint[] | Method information for reflective invocation and metadata query |
queriedMethods | MethodHint[] | Method information for reflective metadata query |
fields | FieldHint[] | Fields information |
access | TypeAccess[] | Scope of the reflection entries or resource access to be configured |
TypeAccess
is an enum type which describes various types of access that can be requested.
@NativeHint(
types = {
@TypeHint(
types = {io.netty.buffer.AbstractByteBufAllocator.class},
access = TypeAccess.QUERY_DECLARED_METHODS),
@TypeHint(
typeNames = {"io.netty.buffer.AdvancedLeakAwareByteBuf"},
access = TypeAccess.QUERY_DECLARED_METHODS
)
}
)
Resources
@ResourceHint
annotation configures the resource patterns to be included.
The table below shows the attributes.
Attribute | Type | Description |
---|---|---|
patterns | String[] | Resource patterns |
isBundle | boolean | Use resource bundles |
When providing configurations for resource bundles, the patterns
attribute specifies the name of a resource bundle.
@NativeHint(
resources = {
@ResourceHint(patterns = "input.txt"),
@ResourceHint(patterns = "AppResources", isBundle = true)
}
)
Initialization
@InitializationHint
annotation configures the initialization time.
The table below shows the attributes.
Attribute | Type | Description |
---|---|---|
initTime | InitializationTime | Initialization time of a type |
types | Class<?>[] | Class references |
typeNames | String[] | Type names of classes |
packageNames | String[] | Names of packages |
InitializationTime
enum type has two values, BUILD
and RUN
.
@NativeHint(
initialization = {
@InitializationHint(
initTime = InitializationTime.BUILD,
packageNames = {"io.netty.util"}
)
}
)
JDK Proxy
@JdkProxyHint
annotation configures interfaces of dynamic proxies.
The table below shows the attributes.
Attribute | Type | Description |
---|---|---|
types | Class<?>[] | Interfaces of a dynamic proxy |
typeNames | String[] | Interface names of a dynamic proxy |
@NativeHint(
jdkProxies = {
@JdkProxyHint(
types = {java.util.concurrent.Callable.class}
)
}
)
Serialization
@SerializationHint
configures the serializable types.
The table below shows the attributes.
Attribute | Type | Description |
---|---|---|
types | Class<?>[] | Class references of serializable types |
typeNames | String[] | Class names of serializable types |
@NativeHint(
serializables = {
@SerializationHint(
typeNames = {"com.example.MyClass"}
)
}
)
AOT Proxy
@AotProxyHint
configures class based proxies.
The table below shows the attributes.
Attribute | Type | Description |
---|---|---|
interfaces | Class<?>[] | Interfaces of a proxy |
interfaceNames | String[] | Interface names of a proxy |
targetClass | Class<?> | Target class |
targetClassName | String | Name of target class |
proxyFeatures | int | Proxy features |
@NativeHint(
aotProxies = {
@AotProxyHint(targetClass = org.lognet.springboot.grpc.FailureHandlingSupport.class)
}
)
Verify
To verify configurations generated by native hints, we can run the spring-aot:generate
goal to generate configurations. Configurations can be found in the target/classes/META-INF/native-image/org.springframework.aot/spring-aot
directory.
./mvnw spring-aot:generate
Use Tracing Agent
Instead of providing native hints, GraalVM tracing agent can also be used to generate native image configurations.
Run the Application
We can run the application with tracing agent enabled, then interact with the running application. Tracing agent will generate configuration files.
- Configure the Spring AOT plugin to use the
native-agent
mode.
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>${spring-native.version}</version>
<configuration>
<mode>native-agent</mode>
</configuration>
</plugin>
- Build the application with the AOT plugin enabled.
- Run the application with the tracing agent to generate configuration files.
As shown in the command below, the output directory of tracing agent is set to src/main/resources/META-INF/native-image/io.vividcode.spring-native/grpc
, which is the recommended directory for native image build configurations.
java -DspringAot=true \
-agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/io.vividcode.spring-native/grpc \
-jar target/spring-grpc-0.0.1-SNAPSHOT.jar
Test the application with typical scenarios to make sure that as many code paths are executed as possible.
- Configure the Spring AOT plugin to use the
native
mode. - Build the native image using the
native
profile.
./mvnw package -Pnative
- Verify the generated native image.
By using this approach, we actually combine configurations generated by the tracing agent and those created by Spring AOT plugin. This is necessary when AOT proxies are used, because they are only supported by Spring AOT plugin.
Work with Tests
Running the application with tracing agent is not a good choice, because manual interaction with the application is required to ensure code coverage. A better choice is to integrate with existing tests, including unit tests and integration tests.
Use an Access Filter
When running tests with tracing agent, the agent may intercept invocations related to test classes. These invocations won't happen when running the actual application. It's better to filter out these invocations using filters.
The code below shows a simple access filter.
{
"rules": [
{
"excludeClasses": "org.apache.maven.surefire.**"
},
{
"excludeClasses": "net.bytebuddy.**"
},
{
"excludeClasses": "org.apiguardian.**"
},
{
"excludeClasses": "org.junit.**"
},
{
"excludeClasses": "org.mockito.**"
},
{
"excludeClasses": "org.springframework.test.**"
},
{
"excludeClasses": "org.springframework.boot.test.**"
}
]
}
Update Maven Surefire Plugin
Maven Surefire plugin needs to be updated to run tests with tracing agent. In the argument of native-image-agent
,
- Value of the
access-filter-file
option is set to the access filter JSON file shown above. - Value of the
config-merge-dir
option is set to thetarget/classes/META-INF/native-image/io.vividcode.spring-native/grpc
directory.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
-agentlib:native-image-agent=access-filter-file=../test-access-filter.json,config-merge-dir=target/classes/META-INF/native-image/io.vividcode.spring-native/grpc
</argLine>
</configuration>
</plugin>
The config-merge-dir
option is used instead of config-output-dir
, because there are many tests to run. Configurations from different tests will be merged.
Now we can run tests with mvn test
. After all tests are executed, configuration files will be generated into the target/classes
directory. These configurations can be combined with those generated by Spring AOT plugin.
Samples
See alexcheng1982/spring-native-samples for Spring Native samples.