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
按照以前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>
服务资源占用还是很低的