|
1 | | -## Micronaut 4.9.4 Documentation |
| 1 | +# Microanut Layered Native Image Demo |
2 | 2 |
|
3 | | -- [User Guide](https://docs.micronaut.io/4.9.4/guide/index.html) |
4 | | -- [API Reference](https://docs.micronaut.io/4.9.4/api/index.html) |
5 | | -- [Configuration Reference](https://docs.micronaut.io/4.9.4/guide/configurationreference.html) |
6 | | -- [Micronaut Guides](https://guides.micronaut.io/index.html) |
7 | | ---- |
| 3 | +This example shows how to build a simple [Micronaut](https://micronaut.io/) REST application using the [GraalVM Native Image Layers](https://github.com/oracle/graal/blob/master/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/NativeImageLayers.md) feature. |
8 | 4 |
|
9 | | -- [Micronaut Maven Plugin documentation](https://micronaut-projects.github.io/micronaut-maven-plugin/latest/) |
10 | | -## Feature maven-enforcer-plugin documentation |
| 5 | +## Environment Setup |
| 6 | +Point your `JAVA_HOME` to a GraalVM distribution. |
| 7 | +Native Image Layers is an experimental feature, for best experience use the latest [GraalVM Early Access Build](https://github.com/graalvm/oracle-graalvm-ea-builds/releases). |
| 8 | +```bash |
| 9 | +export JAVA_HOME=/path/to/graalvm/ea/build |
| 10 | +``` |
11 | 11 |
|
12 | | -- [https://maven.apache.org/enforcer/maven-enforcer-plugin/](https://maven.apache.org/enforcer/maven-enforcer-plugin/) |
| 12 | +## Create The Micronaut Application |
13 | 13 |
|
| 14 | +We'll start by generating a basic application using the Micronaut CLI. |
| 15 | +For more details see the [Micronaut guide](https://guides.micronaut.io/latest/creating-your-first-micronaut-app-maven-java.html). |
14 | 16 |
|
15 | | -## Feature serialization-jackson documentation |
| 17 | +First we need to install the `mn` tool: |
| 18 | +```bash |
| 19 | +sdk install micronaut 4.9.4 |
| 20 | +sdk use micronaut 4.9.4 |
| 21 | +``` |
16 | 22 |
|
17 | | -- [Micronaut Serialization Jackson Core documentation](https://micronaut-projects.github.io/micronaut-serialization/latest/guide/) |
| 23 | +Now we ca generate the basic app: |
| 24 | +```bash |
| 25 | +mn create-app example.micronaut.micronaut-hello-rest-maven-layered --build=maven --lang=java --features=graalvm |
| 26 | +``` |
18 | 27 |
|
| 28 | +### Add A Custom Controller |
19 | 29 |
|
20 | | -## Feature micronaut-aot documentation |
| 30 | +We'll add a custom controller to `src/main/java/example/micronaut/HelloController.java`: |
| 31 | +```java |
| 32 | +package example.micronaut; |
21 | 33 |
|
22 | | -- [Micronaut AOT documentation](https://micronaut-projects.github.io/micronaut-aot/latest/guide/) |
| 34 | +import io.micronaut.http.MediaType; |
| 35 | +import io.micronaut.http.annotation.Controller; |
| 36 | +import io.micronaut.http.annotation.Get; |
| 37 | +import io.micronaut.http.annotation.Produces; |
23 | 38 |
|
| 39 | +@Controller("/hello") |
| 40 | +public class HelloController { |
| 41 | + @Get |
| 42 | + @Produces(MediaType.TEXT_PLAIN) |
| 43 | + public String hello() { |
| 44 | + return "Hello from GraalVM Native Image!"; |
| 45 | + } |
| 46 | +} |
| 47 | +``` |
24 | 48 |
|
| 49 | +### Standalone Application |
| 50 | + |
| 51 | +We'll first demonstrate how to build a standalone executable for this simple app. |
| 52 | +For this we'll extend the `pom.xml` with a custom profile and configure the native build using the [GraalVM Native Image Maven plugin](https://graalvm.github.io/native-build-tools/latest/maven-plugin.html): |
| 53 | +```xml |
| 54 | +<profile> |
| 55 | + <id>standalone</id> |
| 56 | + <build> |
| 57 | + <plugins> |
| 58 | + <plugin> |
| 59 | + <groupId>org.graalvm.buildtools</groupId> |
| 60 | + <artifactId>native-maven-plugin</artifactId> |
| 61 | + <version>0.10.3</version> |
| 62 | + <configuration> |
| 63 | + <imageName>standalone-app</imageName> |
| 64 | + <mainClass>example.micronaut.Application</mainClass> |
| 65 | + </configuration> |
| 66 | + </plugin> |
| 67 | + </plugins> |
| 68 | + </build> |
| 69 | +</profile> |
| 70 | +``` |
| 71 | + |
| 72 | +Using this profile we can now generate the executable: |
| 73 | +```bash |
| 74 | +./mvnw clean package -Dpackaging=native-image -Pstandalone |
| 75 | +``` |
| 76 | + |
| 77 | +This will generate an executable file that we can run |
| 78 | +```bash |
| 79 | +./target/standalone-app |
| 80 | + __ __ _ _ |
| 81 | +| \/ (_) ___ _ __ ___ _ __ __ _ _ _| |_ |
| 82 | +| |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __| |
| 83 | +| | | | | (__| | | (_) | | | | (_| | |_| | |_ |
| 84 | +|_| |_|_|\___|_| \___/|_| |_|\__,_|\__,_|\__| |
| 85 | +12:20:53.437 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 6ms. Server Running: http://localhost:8080 |
| 86 | +``` |
| 87 | +and test our custom endpoint |
| 88 | +```bash |
| 89 | +curl localhost:8080/hello |
| 90 | +Hello from GraalVM Native Image! |
| 91 | +``` |
| 92 | +
|
| 93 | +### Layered Application |
| 94 | +
|
| 95 | +#### Configure The Base Layer |
| 96 | +
|
| 97 | +We will create a base layer that contains both `java.base` and the Micronaut framework. |
| 98 | +For this we'll add a second custom profile: |
| 99 | +```xml |
| 100 | +<profile> |
| 101 | + <id>base-layer</id> |
| 102 | + <build> |
| 103 | + <directory>${project.basedir}/base-layer-target</directory> |
| 104 | + <plugins> |
| 105 | + <plugin> |
| 106 | + <groupId>org.graalvm.buildtools</groupId> |
| 107 | + <artifactId>native-maven-plugin</artifactId> |
| 108 | + <version>0.10.3</version> |
| 109 | + <configuration> |
| 110 | + <imageName>libmicronautbaselayer</imageName> |
| 111 | + <mainClass>.</mainClass> |
| 112 | + <buildArgs> |
| 113 | + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> |
| 114 | + <buildArg>-H:LayerCreate=base-layer.nil,module=java.base,package=io.micronaut.*,package=io.netty.*,package=jakarta.*,package=com.fasterxml.jackson.*,package=org.slf4j.*,package=reactor.*,package=org.reactivestreams.*</buildArg> |
| 115 | + <buildArg>-H:ApplicationLayerOnlySingletons=io.micronaut.core.io.service.ServiceScanner$StaticServiceDefinitions</buildArg> |
| 116 | + <buildArg>-H:ApplicationLayerInitializedClasses=io.micronaut.inject.annotation.AnnotationMetadataSupport</buildArg> |
| 117 | + <buildArg>-H:ApplicationLayerInitializedClasses=io.micronaut.core.io.service.MicronautMetaServiceLoaderUtils</buildArg> |
| 118 | + <buildArg>-H:-UnlockExperimentalVMOptions</buildArg> |
| 119 | + </buildArgs> |
| 120 | + </configuration> |
| 121 | + </plugin> |
| 122 | + </plugins> |
| 123 | + </build> |
| 124 | +</profile> |
| 125 | +``` |
| 126 | +
|
| 127 | +We use `-H:LayerCreate=` to specify what should be included in the base layer: `java.base`, `io.micronaut`, `io.netty` and a few more other packages that a Micronaut application usually depends on. |
| 128 | +For more details consult the [Native Image Layers documentation](https://github.com/oracle/graal/blob/master/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/NativeImageLayers.md). |
| 129 | +
|
| 130 | +Additionally we use two options that are specific for layered builds: `-H:ApplicationLayerOnlySingletons=`, which specifies that a singleton object should be installed in the application layer only, and `-H:ApplicationLayerInitializedClasses=`, which registers a class as being initialized in the app layer. |
| 131 | +These are necessary for correctly building the Micronaut framework in a layered set-up. |
| 132 | +
|
| 133 | +Now we can build the base layer: |
| 134 | +```bash |
| 135 | +./mvnw clean install -Dpackaging=native-image -Pbase-layer |
| 136 | +``` |
| 137 | +This will create the `base-layer.nil` which is a build time dependency for the application build. |
| 138 | +It will also create the `libmicronautbaselayer.so` shared library which is a run time dependency for the application layer. |
| 139 | +Note also that we use `install` instead of `package` to ensure that the base layer jar is installed in the `.m2` cache as it will be needed by the application build later. |
| 140 | +
|
| 141 | +
|
| 142 | +### Configure The Application Layer |
| 143 | +
|
| 144 | +To configure the app layer we'll add an additional profile: |
| 145 | +```xml |
| 146 | +<profile> |
| 147 | + <id>app-layer</id> |
| 148 | + <build> |
| 149 | + <directory>${project.basedir}/app-layer-target</directory> |
| 150 | + <plugins> |
| 151 | + <plugin> |
| 152 | + <groupId>org.graalvm.buildtools</groupId> |
| 153 | + <artifactId>native-maven-plugin</artifactId> |
| 154 | + <version>0.10.3</version> |
| 155 | + <configuration> |
| 156 | + <imageName>layered-app</imageName> |
| 157 | + <mainClass>example.micronaut.Application</mainClass> |
| 158 | + <buildArgs> |
| 159 | + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> |
| 160 | + <buildArg>-H:LayerUse=base-layer-target/base-layer.nil</buildArg> |
| 161 | + <buildArg>-H:LinkerRPath=$ORIGIN</buildArg> |
| 162 | + <buildArg>-H:-UnlockExperimentalVMOptions</buildArg> |
| 163 | + </buildArgs> |
| 164 | + </configuration> |
| 165 | + </plugin> |
| 166 | + </plugins> |
| 167 | + </build> |
| 168 | +</profile> |
| 169 | +``` |
| 170 | +
|
| 171 | +Now we can build a layered Native Image which depends on the base layer that we created earlier: |
| 172 | +```bash |
| 173 | +./mvnw clean package -Dpackaging=native-image -Papp-layer |
| 174 | +``` |
| 175 | +
|
| 176 | +This will generate the layered executable file in `./app-layer-target/layered-app` and will copy `libmicronautbaselayer.so` next to it. |
| 177 | +
|
| 178 | +Then we can execute the layered application: |
| 179 | +```bash |
| 180 | +./app-layer-target/layered-app |
| 181 | + __ __ _ _ |
| 182 | +| \/ (_) ___ _ __ ___ _ __ __ _ _ _| |_ |
| 183 | +| |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __| |
| 184 | +| | | | | (__| | | (_) | | | | (_| | |_| | |_ |
| 185 | +|_| |_|_|\___|_| \___/|_| |_|\__,_|\__,_|\__| |
| 186 | +12:24:21.341 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 6ms. Server Running: http://localhost:8080 |
| 187 | +``` |
| 188 | +and test it with: |
| 189 | +``` |
| 190 | +curl localhost:8080/hello |
| 191 | +Hello from GraalVM Native Image! |
| 192 | +``` |
0 commit comments