为了避免因为编译平台不一致导致的部署文件不一样,可以使用Docker进行编译,以保证编译环境的一致性。 以下是一些使用Docker进行编译的一些方法,和编译成native镜像的问题和解决方案。
开发测试
Tips:启动镜像并进入容器内部的方法
docker run --rm -it --name temp-container --entrypoint sh <镜像名称>
本地JVM编译测试
# 编译环境
export GRAALVM_HOME="/Library/Java/LibericaNativeImageKit/liberica-vm-full-24.0.2-openjdk22/Contents/Home"
export JAVA_HOME=$GRAALVM_HOME
export PATH="$JAVA_HOME/bin:$PATH"
# 编译
./mvnw clean package -DskipTests
Docker JVM编译测试
docker run -it --rm --name temp-container \
-v $PWD:/home/build \
-v $HOME/.m2:/root/.m2 \
bellsoft/liberica-native-image-kit-container:jdk-22-nik-24.0.2-glibc \
/bin/sh -c "cd /home/build && ./mvnw clean package -DskipTests"
本地Native编译测试
# 编译环境
export GRAALVM_HOME="/Library/Java/LibericaNativeImageKit/liberica-vm-full-24.0.2-openjdk22/Contents/Home"
export JAVA_HOME=$GRAALVM_HOME
export PATH="$JAVA_HOME/bin:$PATH"
# 编译
./mvnw -Pnative clean -DskipTests native:compile
Docker Native编译测试
# bellsoft的NIK镜像
docker pull bellsoft/liberica-native-image-kit-container:jdk-22-nik-24.0.2-glibc
# 映射本地文件和maven仓库进行编译
docker run -it --rm --name temp-container \
-v $PWD:/home/build \
-v $HOME/.m2:/root/.m2 \
bellsoft/liberica-native-image-kit-container:jdk-22-nik-24.0.2-glibc \
/bin/sh -c "cd /home/build && ./mvnw -Pnative clean -DskipTests native:compile"
# 运行测试
docker run -it --rm --name temp-container \
-v $PWD/target/flutter-flex-backend:/home/myapp/flutter-flex-backend \
-p 8080:8080 \
bellsoft/alpaquita-linux-base:stream-glibc-240821 \
/bin/sh -c "cd /home/myapp && ./flutter-flex-backend"
部署
JVM Docker 镜像
docker build -f Dockerfile.jvm -t mytool-backend-jvm:last .
docker run --name mytool-backend-jvm -d -p 8080:8080 --restart unless-stopped mytool-backend-jvm:last
Native Docker 镜像
docker build -f Dockerfile.native -t mytool-backend-native:last .
docker run --name mytool-backend-native -d -p 8080:8080 --restart unless-stopped mytool-backend-native:last
编译Spring项目Native问题汇总
org.mybatis.spring.mapper.ClassPathMapperScanner. Cause: java.lang.NullPointerException
No qualifying bean of type ‘java.lang.Class<?>’ available
修改 MyBatisNativeConfiguration 的 resolveMapperFactoryBeanTypeIfNecessary 方法解决下面的问题:
2024-08-25 08:49:27.837 mytool-backend-backend MateBook-Pro-2019.local [main] WARN o.s.c.s.AbstractApplicationContext -633-
Exception encountered during context initialization - cancelling refresh attempt:
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'memberController':
Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'userService':
Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'sessionRecordService':
Unsatisfied dependency expressed through field 'mapper': Error creating bean with name 'sessionRecordMapper':
Unsatisfied dependency expressed through constructor parameter 0:
No qualifying bean of type 'java.lang.Class<?>' available:
expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
问题参考:
Caused by: java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 24
将 liberica-vm-full-23.1.4-openjdk21 升级为 liberica-vm-full-24.0.2-openjdk22,报错消失。
Ensure that the compiler uses the ‘-parameters’ flag
2024-08-28 10:42:49.361 [http-nio-8899-exec-1] ERROR c.b.s.global.exception.GlobalExceptionHandler - Name for argument of type [java.lang.String] not specified, and parameter name information not available via reflection. Ensure that the compiler uses the '-parameters' flag.
java.lang.IllegalArgumentException: Name for argument of type [java.lang.String] not specified, and parameter name information not available via reflection. Ensure that the compiler uses the '-parameters' flag.
at org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.updateNamedValueInfo(AbstractNamedValueMethodArgumentResolver.java:187)
解决方法,添加 <parameters>true</parameters> 配置:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<parameters>true</parameters>
</configuration>
</plugin>
修改Java代码:
public ResponseEntity<CommandResponse> update(
@PathVariable long param1,
@RequestParam String param2) {
// ...
}
改为:
public ResponseEntity<CommandResponse> update(
@PathVariable("param1") long param1,
@RequestParam("param2") String param2) {
// ...
}
此配置用于启用 Java 编译器的 -parameters 标志。这个标志会在编译时保留方法参数的名称信息, 使得在运行时可以通过反射 API 获取参数名称。这在某些框架(如 Spring)中非常有用, 因为它们可以利用这些参数名称来进行依赖注入或参数绑定。
MapStruct
从静态方法调用改为实例方法调用,生成的代码包含@Component注解,解决native编译后找不到实现类的问题:
@Mapper
public interface UserConvertor {
UserConvertor INSTANCE = Mappers.getMapper(UserConvertor.class);
User regParamToPo(RegisterParam regParam);
}
改为:
import static org.mapstruct.MappingConstants.ComponentModel.SPRING;
@Mapper(componentModel = SPRING)
public interface UserConvertor {
User regParamToPo(RegisterParam regParam);
}
MapScan
org.mybatis.spring.annotation.MapperScan 不要扫描 MapStruct 的 Mapper注解,否则会出错。
参考资料
- 编译native镜像的两种方法:本地 & docker
- Containerize a Native Executable and Run in a Container
- bellsoft/liberica-native-image-kit-container
- how containerize native images
文档信息
- 本文作者:Bob.Zhu
- 本文链接:https://home.mytool.group/2024/08/26/01-two-ways-to-compile-native-image/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
