SpringBoot3 GraalVM 原生镜像打包 搭建云原生环境

java发布到如今,已经过去几十年,如今微服务、云原生逐渐成为了主流,java原本的很多优势不再重要,而启动慢,耗内存等的缺点也越来越被放大.

java在新发布的很多相关技术中也做出了很多改变

其中SpringBoot3结合GraalVM,可以直接将java项目打包成原生可执行文件,提升运行速度并大大节省服务资源,

但是GraalVM通过静态分析提前编译来为Java应用程序构建高度优化的本机可执行文件,这就需要在编译时就知道所有的程序类型,而java中的反射、动态代理等功能,在编译时不确定具体的类型,甚至很多代码是在运行时才生成的,所以在使用GraalVm构建native image前需要通过配置列出反射可见的所有类型。反射的配置是一个json格式的文件。为了简化这种反射的配置,GraalVm提供agentlib工具,来辅助生成这个配置文件

而这也就意味着,有大量使用反射的库,在GraalVM编译后的文件使用时,将会异常,本人在后面的服务中,为了解决这个问题,写了一个工具类,利用GraalVM提供的工具类,将项目中的java代码通通反射了一遍,向GraalVM配置列出项目中可能需要反射的所有类型,虽然感觉这样不太合适,但是本就是新技术,先把服务跑起来

环境准备:

GraalVM 是 Oracle 发布的虚拟机,可以像正常的运行java程序,也可以让js,python,C等不同语言互相调用,集成在一起

后续的代码是基于 graalvm-jdk-20_windows-x64_bin.zip 运行的

下载GrralVM

Download GraalVM

按照以前JDK的方式一样,配置好java环境变量

用GRAALVM_HOME替换掉JAVA_HOME,或者两个一起配置,避免一些其奇奇怪怪的错误

windows下需要安装visualstudio:

下载 Visual Studio Tools - 免费安装 Windows、Mac、Linux

运行,选择C++桌面开发安装

语言包需要修改为英语,否则打包会异常

Linux Ubuntu下,需要安装

gcc

 apt install gcc

后续将java项目编译成可执行文件时,可能会报一些异常,例如

/usr/bin/ld: cannot find -lxxx

再补充安装对应的文件

apt-get install libxxx-dev

例如

异常 /usr/bin/ld: cannot find -lz

安装 apt-get install libz-dev

下面按照最基本到相对复杂完整的三个服务,对技术进行演示


代码演示:

编译后的文件太大,没有和服务一起上传到代码库,

GitHub - cjs199/springboot3-demo2

一个最简单的web服务

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.1.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	
	<groupId>com.example</groupId>
	<artifactId>demo2</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>demo2</name>
	<description>Demo project for Spring Boot</description>
	
	<properties>
		<java.version>20</java.version>
	</properties>
	
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.graalvm.buildtools</groupId>
				<artifactId>native-maven-plugin</artifactId>
			</plugin>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(proxyBeanMethods = false)
public class Demo2Application {

	public static void main(String[] args) {
		SpringApplication.run(Demo2Application.class, args);
	}

}
package com.example.demo.control;

import java.util.Random;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

	@GetMapping("test1")
	public void test1(int num) {
		String str = "";
		long begin = System.currentTimeMillis();
		Random random = new Random();
		for (int i = 0; i < num; i++) {
			str += random.nextInt();
		}
		long end = System.currentTimeMillis();
		System.err.println("总共耗时" + (end - begin) + "ms");
		System.err.println(str.length());
	}


}

在这里我们可以看到使用了注解,那么Spring是如何解决GraalVM 反射的问题呢?

spring是通过spring-aot将代码在编译前生成的

在springboot3下,服务依旧可以直接运行main方法启动

当然最重要的是如何打包成指定平台的运行程序呢?运行下面的maven打包命令

mvn -Pnative -DskipTests clean native:compile

在eclipse下,运行打包

最终程序生成了如下一个demo2.exe文件

访问测试

http://localhost:8080/test1?num=10000

包含AOP的服务演示

服务代码库

GitHub - cjs199/springboot3-demo3

在上述代码的基础上增加一个AOP注解

package com.example.demo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {

}
package com.example.demo.annotation.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import com.example.demo.annotation.TestAnnotation;

/**
 * 处理RedisLock注解的逻辑</br>
 * 
 * @author Robert 2020-8-17 11:34:10
 */
@Aspect
@Component
public class RedisLockAspect {

	/**
	 * 环绕加redis锁
	 * 
	 * @param pjp
	 * @param redisLock 切点
	 * @return
	 * @throws Throwable
	 */
	@Around(value = "@annotation(testAnnotation)")
	public Object around(ProceedingJoinPoint pjp, TestAnnotation testAnnotation) throws Throwable {
		System.err.println("执行TestAnnotation注解");
		return pjp.proceed();
	}

}
	@TestAnnotation
	@GetMapping("test1")
	public void test1(int num) {
		String str = "";
		long begin = System.currentTimeMillis();
		Random random = new Random();
		for (int i = 0; i < num; i++) {
			str += random.nextInt();
		}
		long end = System.currentTimeMillis();
		System.err.println("总共耗时" + (end - begin) + "ms");
		System.err.println(str.length());
	}

直接IDE运行 main服务访问,控制台会执行AOP的代码,在执行test1的代码 

http://localhost:8080/test1?num=1

在不处理反射相关问题时,直接打包,执行GraalVm的原生代码,再访问,就会报如下异常

http://localhost:8080/test1?num=1

PS D:\eclipse_file\new_ws\springboot3-demo3\target> .\springboot3-demo3.exe

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.1)


2023-07-19T18:03:31.975+08:00 ERROR 16212 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed: org.graalvm.nativeimage.MissingReflectionRegistrationError: The program tried to reflectively invoke method public java.lang.Object com.example.demo.annotation.aspect.TestAnnotationAspect.around(org.aspectj.lang.ProceedingJoinPoint,com.example.demo.annotation.TestAnnotation) throws java.lang.Throwable without it being registered for runtime reflection. Add it to the reflection metadata to solve this problem. See https://www.graalvm.org/latest/reference-manual/native-image/metadata/#reflection for help.] with root cause
....
org.graalvm.nativeimage.MissingReflectionRegistrationError: The program tried to reflectively invoke method public java.lang.Object com.example.demo.annotation.aspect.TestAnnotationAspect.around(org.aspectj.lang.ProceedingJoinPoint,com.example.demo.annotation.TestAnnotation) throws java.lang.Throwable without it being registered for runtime reflection. Add it to the reflection metadata to solve this problem. See https://www.graalvm.org/latest/reference-manual/native-image/metadata/#reflection for help.
....
        at com.example.demo.control.TestController$$SpringCGLIB$$0.test1(<generated>) ~[springboot3-demo3.exe:na]
        at java.base@20.0.1/java.lang.reflect.Method.invoke(Method.java:578) ~[springboot3-demo3.exe:na]
      

怎么解决这个问题?需要利用GraalVm提供的工具,将服务中使用到的反射配置好,执行如下的命令,绿色的部分,执行时需要替换为自己服务真实的名称和路径

java -agentlib:native-image-agent=config-output-dir=D:\eclipse_file\new_ws\springboot3-demo3\src\main\resources\META-INF\native-image  -jar  .\springboot3-demo3-0.0.1-SNAPSHOT.jar

但是上述命令还有一个坑,他能够生成的反射,是要代码被调用执行以后才能够生成使用了的类的反射配置,而其他没有被执行的代码对应的配置类,就不会执行,为了解决这个问题,我写了另一个类,扫描自己服务包下所有的类,将所有类通通反射一遍

package com.example.demo.config;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import cn.hutool.core.util.ClassUtil;
import jakarta.annotation.PostConstruct;

/**
 * 反射将所有项目类扫描加入到服务, 大力出奇迹的操作,感觉不太合适,不过先让服务跑起来
 * 
 * @author PC
 *
 */
@Component
public class ClassReflectConfig {

	static boolean begin = true;

	@Value("${scanclass}")
	private Boolean scanclass;

	@Autowired
	private ThreadPoolTaskExecutor executorService;

	@PostConstruct
	public void init() {

		if (scanclass) {
			System.err.println("配置文件下 scanclass 开启了生成反射类");
		} else {
			System.err.println("配置文件下 scanclass 关闭了生成反射类");
		}

		synchronized (ClassReflectConfig.class) {
			if (begin && scanclass) {
				begin = false;
				executorService.submit(() -> {

//					{
//						// 先抓取上一次的文件,生成
//						try {
//							BufferedReader utf8Reader = ResourceUtil
//									.getUtf8Reader("classpath:/META-INF/native-image/reflect-config.json");
//							String res = utf8Reader.lines().collect(Collectors.joining());
//							List object = ProJsonUtil.toObject(res, List.class);
//							for (Object object2 : object) {
//								try {
//									Map object22 = (Map) object2;
//									handlerClass(Class.forName(ProMapUtil.getStr(object22, "name")));
//								} catch (Exception e) {
//								}
//							}
//						} catch (Exception e) {
//							log.error("生成文件异常", e);
//						}
//					}

					{
						// 扫描系统第二级开始的包
						String packageName = ClassReflectConfig.class.getPackageName();
						String proPackageName = packageName.substring(0,
								packageName.indexOf(".", packageName.indexOf(".") + 1));

						// 可以在这个地方,添加除了服务以外其他的包,将会加入反射,以供graalvm生成配置
						List<String> asList = Arrays.asList(proPackageName);

						for (String spn : asList) {
							try {
								Set<Class<?>> doScan = ClassUtil.scanPackage(spn);
								for (Class clazz : doScan) {
									handlerClass(clazz);
								}
							} catch (Throwable e) {
								e.printStackTrace();
							}
						}
					}
					
					// handlerClass(RedisMessageListenerContainer.class);

				});
			}
		}


	}

	private void handlerClass(Class clazz) {
		if (clazz.equals(ClassReflectConfig.class)) {
			// 跳过自己,避免形成循环
			return;
		}

		executorService.submit(() -> {
			try {
				System.err.println("反射注入:" + clazz.getName());
				// 生成所有的构造器
				Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
				// 找到无参构造器然后实例化
				Constructor declaredConstructor = clazz.getDeclaredConstructor();
				declaredConstructor.setAccessible(true);
				Object newInstance = declaredConstructor.newInstance();
				Method[] methods = clazz.getDeclaredMethods();
				for (Method method : methods) {
					try {
						// 实例化成功,那么调用一下
						method.setAccessible(true);
						// graalvm必须需要声明方法
						method.invoke(newInstance);
					} catch (Throwable e) {
					}
				}
				Field[] fields = clazz.getDeclaredFields();
				for (Field field : fields) {
					try {
						field.setAccessible(true);
						field.getType();
						String name = field.getName();
						field.get(newInstance);

					} catch (Throwable e) {
					}
				}
				System.err.println("反射注入完成:" + clazz.getName());
			} catch (Throwable e) {
			}
		});
	}

}

添加这个类完成以后再执行

PS D:\eclipse_file\new_ws\springboot3-demo3\target> java -agentlib:native-image-agent=config-output-dir=D:\eclipse_file\new_ws\springboot3-demo3\src\main\resources\META-INF\native-image  -jar  .\springboot3-demo3-0.0.1-SNAPSHOT.jar

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.1)

2023-07-19T18:06:22.193+08:00  INFO 14292 --- [           main] c.e.demo.Springboot3Demo3Application     : Starting Springboot3Demo3Application v0.0.1-SNAPSHOT using Java 20.0.1 with PID 14292 (D:\eclipse_file\new_ws\springboot3-demo3\target\springboot3-demo3-0.0.1-SNAPSHOT.jar started by PC in D:\eclipse_file\new_ws\springboot3-demo3\target)
2023-07-19T18:06:22.198+08:00  INFO 14292 --- [           main] c.e.demo.Springboot3Demo3Application     : No active profile set, falling back to 1 default profile: "default"
2023-07-19T18:06:23.262+08:00  INFO 14292 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode
2023-07-19T18:06:23.264+08:00  INFO 14292 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2023-07-19T18:06:23.310+08:00  INFO 14292 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 22 ms. Found 0 Redis repository interfaces.
2023-07-19T18:06:24.451+08:00  INFO 14292 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-07-19T18:06:24.463+08:00  INFO 14292 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-07-19T18:06:24.463+08:00  INFO 14292 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.10]
2023-07-19T18:06:24.541+08:00  INFO 14292 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-07-19T18:06:24.544+08:00  INFO 14292 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2252 ms
配置文件下 scanclass 开启了生成反射类
反射注入:com.example.demo.control.TestController$$SpringCGLIB$$0
反射注入:com.example.demo.annotation.aspect.TestAnnotationAspect
反射注入:com.example.demo.control.TestController
反射注入:com.example.demo.Springboot3Demo3Application$$SpringCGLIB$$0
反射注入:com.example.demo.annotation.TestAnnotation
反射注入:com.example.demo.Springboot3Demo3Application__BeanFactoryRegistrations
反射注入:com.example.demo.Springboot3Demo3Application__BeanDefinitions
反射注入:com.example.demo.annotation.aspect.TestAnnotationAspect__BeanDefinitions
反射注入:com.example.demo.control.TestController__BeanDefinitions
反射注入完成:com.example.demo.annotation.aspect.TestAnnotationAspect__BeanDefinitions
反射注入完成:com.example.demo.control.TestController
反射注入完成:com.example.demo.control.TestController__BeanDefinitions
反射注入完成:com.example.demo.annotation.aspect.TestAnnotationAspect
反射注入完成:com.example.demo.Springboot3Demo3Application__BeanFactoryRegistrations
反射注入完成:com.example.demo.Springboot3Demo3Application__BeanDefinitions
反射注入完成:com.example.demo.Springboot3Demo3Application$$SpringCGLIB$$0
反射注入完成:com.example.demo.control.TestController$$SpringCGLIB$$0
反射注入:com.example.demo.Springboot3Demo3Application__ApplicationContextInitializer
反射注入:com.example.demo.Springboot3Demo3Application
反射注入完成:com.example.demo.Springboot3Demo3Application__ApplicationContextInitializer
反射注入完成:com.example.demo.Springboot3Demo3Application
2023-07-19T18:06:27.476+08:00  INFO 14292 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-07-19T18:06:27.486+08:00  INFO 14292 --- [           main] c.e.demo.Springboot3Demo3Application     : Started Springboot3Demo3Application in 5.764 seconds (process running for 6.33)
PS D:\eclipse_file\new_ws\springboot3-demo3\target>

当配置文件执行完毕,停止服务,查看resources目录下,已经生成了对应的配置文件

 此时再次执行编译,运行生成后的文件

此时最好将配置文件和编译后的文件放在一起,关闭执行的反射配置类,此时这个类已经没有作用了

访问 http://localhost:8080/test1?num=1

PS D:\eclipse_file\new_ws\springboot3-demo3\target> .\springboot3-demo3.exe

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.1)

....
2023-07-19T18:12:19.231+08:00  INFO 4812 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 47 ms
配置文件下 scanclass 关闭了生成反射类
...
2023-07-19T18:12:21.648+08:00  INFO 4812 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
执行TestAnnotation注解
总共耗时0ms
9

另外可以看一下,编译后的java服务,内存占用只有37.9m

 集成访问mysql和redis的服务

代码库:

GitHub - cjs199/springboot3-demo4

在测试中,spring-boot-starter-data-redis自带的lettuce-core在广播订阅消息时,会引发一些奇怪的错误,能够查询到的资料很少,不知道原因,于是只能将maven下的依赖从 lettuce 修改成 jedis

ly configure spring.jpa.open-in-view to disable this warning
2023-07-21T12:25:42.063+08:00  INFO 16408 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
Exception in thread "task-1" 2023-07-21T12:25:42.064+08:00  WARN 16408 --- [           main] w.s.c.ServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'RedisMessageListenerContainer'
java.lang.UnsatisfiedLinkError: jdk.jfr.internal.JVM.isExcluded(Ljava/lang/Class;)Z [symbol: Java_jdk_jfr_internal_JVM_isExcluded or Java_jdk_jfr_internal_JVM_isExcluded__Ljava_lang_Class_2]
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.jni.access.JNINativeLinkage.getOrFindEntryPoint(JNINativeLinkage.java:152)
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.jni.JNIGeneratedMethodSupport.nativeCallAddress(JNIGeneratedMethodSupport.java:53)
        at jdk.jfr@20.0.1/jdk.jfr.internal.JVM.isExcluded(Native Method)
....
        at java.base@20.0.1/java.lang.Thread.runWith(Thread.java:1636)
        at java.base@20.0.1/java.lang.Thread.run(Thread.java:1623)
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:807)
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.windows.WindowsPlatformThreads.osThreadStartRoutine(WindowsPlatformThreads.java:179)
2023-07-21T12:25:46.217+08:00  INFO 16408 --- [           main] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2023-07-21T12:25:46.217+08:00  INFO 16408 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2023-07-21T12:25:46.242+08:00  INFO 16408 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
2023-07-21T12:25:46.246+08:00  INFO 16408 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2023-07-21T12:25:46.247+08:00 ERROR 16408 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.context.ApplicationContextException: Failed to start bean 'RedisMessageListenerContainer'
        at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181) ~[demo-1.exe:6.0.10]
        at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356) ~[demo-1.exe:6.0.10]
...
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) ~[demo-1.exe:3.1.1]
        at com.example.demo.Demo1Application.main(Demo1Application.java:11) ~[demo-1.exe:na]
Caused by: java.lang.NoClassDefFoundError: Could not initialize class io.lettuce.core.event.connection.JfrConnectionCreatedEvent
        at java.base@20.0.1/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500) ~[demo-1.exe:na]
....
        at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:178) ~[demo-1.exe:6.0.10]
        ... 13 common frames omitted

PS D:\eclipse_file\ws3\demo-1\target>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
			<exclusions>
				<exclusion>
					<groupId>io.lettuce</groupId>
					<artifactId>lettuce-core</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
		</dependency>

然后,mysql测试,数据库连接一个最简单的表

CREATE TABLE `sys_user` (
  `id` varchar(255) NOT NULL,
  `password` varchar(255) DEFAULT NULL,
  `username` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  KEY `username_index` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC;

测试内容如下,这些功能足够满足大部分呢开发中使用场景了

package com.example.demo.control;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.jpa.SysUser;
import com.example.demo.jpa.SysUserRepository;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;

@RestController
public class TestControl {

	@Autowired
	private SysUserRepository sysUserRepository;

	@Autowired
	private StringRedisTemplate stringRedisTemplate;

	// 数据库保存
	@GetMapping("/test_save")
	public void test_save() throws Exception {
		System.err.println("save");
		SysUser sysUser = new SysUser();
		sysUser.setUsername(RandomUtil.randomString(3));
		sysUser.setPassword(RandomUtil.randomString(3));
		sysUserRepository.save(sysUser);
	}

	// 数据库所有查找
	@GetMapping("/test_find_all")
	public String test_find_all() throws Exception {
		List<SysUser> findAll = sysUserRepository.findAll();
		System.err.println(JSONUtil.toJsonPrettyStr(findAll));
		return JSONUtil.toJsonPrettyStr(findAll);
	}

	// 数据库id查找
	@GetMapping("/test_find_by_id")
	public String test_find_by_id(String id) throws Exception {
		SysUser sysUser = sysUserRepository.getOne(id);
		System.err.println(JSONUtil.toJsonPrettyStr(sysUser));
		return JSONUtil.toJsonPrettyStr(sysUser);
	}

	// redis 键值对数据设置
	@GetMapping("/test_redis_kv_set")
	public String test_redis_kv_set() throws Exception {
		stringRedisTemplate.opsForValue().set("123", "456");
		return "OK";
	}

	// redis 键值对数据获取
	@GetMapping("/test_redis_kv_get")
	public String test_redis_kv_get() throws Exception {
		System.err.println(stringRedisTemplate.opsForValue().get("123"));
		return "OK";
	}

	// redis广播消息测试
	@GetMapping("/test_redis_pubsub")
	public String test_redis_pubsub() throws Exception {
		stringRedisTemplate.convertAndSend("test_redis_pubsub", "a msg");
		return "OK";
	}

	// redis队列测试
	@GetMapping("/test_redis_queue")
	public String test_redis_queue() throws Exception {
		stringRedisTemplate.opsForList().rightPush("MsgQueue", "a msg");
		return "OK";
	}


}

测试,所有功能都是正常的

PS D:\eclipse_file\ws3\demo-1\target> .\demo-1.exe

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.1)


2023-07-21T12:39:14.776+08:00  INFO 5156 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
save
Hibernate: insert into sys_user (password,username,id) values (?,?,?)
Hibernate: select s1_0.id,s1_0.password,s1_0.username from sys_user s1_0
[
    {
        "id": "4028dc158976be5a018976bead320000",
        "password": "r58",
        "username": "rix"
    },
....
]
Hibernate: select s1_0.id,s1_0.password,s1_0.username from sys_user s1_0 where s1_0.id=?
{
    "id": "4028dc378973329b0189733358ce0002",
    "password": "swv",
    "username": "w1m"
}
MsgPubSub收到消息:a msg
MsgPubSub2收到消息:a msg
收到队列消息:a msg
2023-07-21T12:40:01.024+08:00  INFO 5156 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2023-07-21T12:40:01.024+08:00  INFO 5156 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2023-07-21T12:40:01.025+08:00  INFO 5156 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
PS D:\eclipse_file\ws3\demo-1\target>

服务资源占用还是很低的

Spring BootGraalVM 结合可以帮助你将基于 JavaSpring 应用程序打包成高效的、无需 JVM 的原生镜像。这样,应用可以在没有 Java 运行时的情况下直接执行,提高启动速度和性能。 以下是构建 Spring Boot 应用 GraalVM 镜像的基本步骤: 1. **添加 GraalVM 和 Native Image 相关依赖**: 在 `pom.xml` 文件中添加 GraalVM Native Image 插件和对应的版本依赖,如: ```xml <build> <plugins> <plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> <version>...</version> </plugin> </plugins> </build> ``` 2. **配置 Native Image 入口点**: 配置 `src/main/native-image/application.properties` 文件,指定 Spring Boot 主应用类作为入口点: ``` name=my-app mainClass=com.example.MyApp ``` 3. **启用 Native Image 编译**: 在 `application.properties` 中加入以下内容开启 Native Image 编译,或者在 `application.yml` 或 `application.json` 中设置: ``` native.image.include-main-class=true ``` 4. **执行 Native Image 构建**: 在命令行里,通过 Maven 执行 `mvn package -Pnative-image` 来构建镜像。这一步会生成一个 `.jar` 文件和一个包含 Native Image 的 `.tar.gz` 或 `.zip` 影像文件。 5. **运行 Native Image**: 如果你想测试镜像,可以使用 `./my-app`(假设镜像名字为`my-app`)命令直接运行。对于生产部署,则需要将其作为一个单独的服务或容器运行。 **注意事项**: - 检查你的 Spring Boot 应用是否有任何 Java 运行时特有的依赖,因为 Native Image 可能无法处理它们。 - 对于复杂的应用,可能需要调整 Native Image 参数,如 `-H:ServiceConfigurationFiles=...` 来处理服务发现或数据库连接。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值