当前位置: 首页 > news >正文

springboot使用线程池的实际应用(一)

在实际Spring Boot项目中,我们可以使用Java的原生多线程或者使用Spring自带的线程池进行多线程编程。多线程的好处在于能够提高应用程序的运行效率,特别是在某些计算密集型场景下。以下是一些使用多线程的典型场景:

  • 并发处理请求:例如Web应用程序有大量请求需要处理,将请求分配到多个工作线程中可以提高响应时间和系统吞吐量。
  • 数据库操作:在大型数据库操作时,将多个操作在不同的线程里处理。这样可以减少数据库操作的时间,从而提高数据库的并发处理能力。
  • 定时任务:使用多线程来执行定时任务,将耗时的操作在单独的线程中处理,避免定时任务占用系统资源,从而提高系统的稳定性和可靠性。

如果你要使用原生的Java多线程,一般可以使用Java多线程提供的Thread类或Runnable接口,实现多线程编程。如果要使用Spring多线程支持,可以使用Spring Boot的注解@Async来实现异步处理任务,或使用Spring Boot自带的线程池ThreadPoolTaskExecutor实现多线程编程。

# 程序员

## 来自你的消息:
在实际springboot项目中,如何使用多线程,一般用在什么场景下

## 来自 ChatGPT 的消息:
在实际Spring Boot项目中,我们可以使用Java的原生多线程或者使用Spring自带的线程池进行多线程编程。多线程的好处在于能够提高应用程序的运行效率,特别是在某些计算密集型场景下。以下是一些使用多线程的典型场景:

- 并发处理请求:例如Web应用程序有大量请求需要处理,将请求分配到多个工作线程中可以提高响应时间和系统吞吐量。
- 数据库操作:在大型数据库操作时,将多个操作在不同的线程里处理。这样可以减少数据库操作的时间,从而提高数据库的并发处理能力。
- 定时任务:使用多线程来执行定时任务,将耗时的操作在单独的线程中处理,避免定时任务占用系统资源,从而提高系统的稳定性和可靠性。

如果你要使用原生的Java多线程,一般可以使用Java多线程提供的Thread类或Runnable接口,实现多线程编程。如果要使用Spring多线程支持,可以使用Spring Boot的注解@Async来实现异步处理任务,或使用Spring Boot自带的线程池ThreadPoolTaskExecutor实现多线程编程。

## 来自你的消息:
就拿数据库操作举例吧

## 来自 ChatGPT 的消息:
好的,举个例子,比如我们在数据库中需要进行并行的查询操作,或者进行批量的更新操作,这些操作都是非常耗时的。为了减少这些操作对系统资源的占用,我们可以使用多线程来并行处理这些操作。

在Java中,我们可以使用多线程池来实现多线程,Spring Boot中提供了一种方便的方式来管理线程池,那就是使用ThreadPoolTaskExecutor。我们可以在配置类中自定义线程池,并注入到需要执行的方法中,例如:

```

@Configuration
@EnableAsync
public class AppConfig implements AsyncConfigurer {@Bean(name = "taskExecutor")public Executor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(10);executor.setMaxPoolSize(20);executor.setQueueCapacity(30);return executor;}@Overridepublic Executor getAsyncExecutor() {return taskExecutor();}@Overridepublic AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return new SimpleAsyncUncaughtExceptionHandler();}
}


```

在上述代码中,我们通过自定义线程池的方式来实现数据库操作的异步执行,其中setCorePoolSize、setMaxPoolSize、setQueueCapacity分别表示线程池的最小线程数量、最大线程数量以及工作队列的容量大小。

接下来,我们只需要在需要异步执行的方法上面添加@Async注解即可,例如:

```

public class UserRepositoryImpl implements UserRepositoryCustom {@Autowiredprivate UserRepository userRepository;@Autowiredprivate ThreadPoolTaskExecutor taskExecutor;@Async("taskExecutor")@Overridepublic CompletableFuture<List<User>> findByAgeGreaterThan(int age) {return CompletableFuture.completedFuture(userRepository.findByAgeGreaterThan(age));}
}


```

在上述代码中,我们使用了findByAgeGreaterThan方法进行查询操作,并将该方法标记为异步执行,Spring Boot会自动使用我们自定义的线程池进行异步执行。

当然,具体实现还需要根据具体情况进行调整,这里只是一个简单的示例。

## 来自你的消息:
那么你调用下这个findByAgeGreaterThan的话,会启动几个线程处理?

## 来自 ChatGPT 的消息:
答案依据我不同的设置而异,但是我可以讲解一下可能的情况。在上述的配置中,我们使用了线程池的方式来实现异步操作,我们设置了线程池的核心线程数是10,最大线程数为20,工作队列的容量为30。

假设我们调用findByAgeGreaterThan方法时,有30个用户年龄大于某个值,这个时候每个查询操作会被放入工作队列中。在这个过程中,如果线程池中的线程数量还没有达到10个,那么这个30个查询操作中的前10个会被分配到10个线程中执行,剩下的20个查询操作会被存储在工作队列中。

当有新的查询操作进入工作队列时,如果线程池中的线程数量仍然没有达到最大值20,那么会有新的线程被创建,并执行这些查询操作。

如果线程池中的线程数量已经达到了最大值20,那么新的查询操作会被放入工作队列中,等待线程池中的线程空闲出来之后再执行。

综上所述,具体线程数量取决于线程池中的策略设置、当前线程池中的线程数、工作队列中待执行任务数量等因素。

## 来自你的消息:
这些线程会自动分片数据?

## 来自 ChatGPT 的消息:
不会,线程池只是用来管理线程的,它并不会自动分片数据。在上面的示例中,如果查询结果中有30个用户年龄大于某个值,30个查询操作会被放在工作队列中等待执行。线程池中创建的线程会依次取出工作队列中的任务执行。每个线程只会处理一个查询操作,也就是说,一个线程处理一个查询操作,这个查询操作的结果仍然是原来的全部数据。

如果要对数据进行分片处理,可以在查询时手动将数据切分成多个片段,将每个片段作为一个任务,由线程池中的线程分配执行,这样每个线程就只需要处理片段中的一部分数据。可以使用像MyBatis Plus这样的框架,在写SQL时使用分页查询对数据进行分片处理,或者使用像Elasticsearch这样的搜索引擎,使用分片和副本来处理数据。

## 来自你的消息:
有30个用户年龄大于某个值,这个时候每个查询操作会被放入工作队列中      。  这句话我不懂

## 来自 ChatGPT 的消息:
抱歉,可能我表述不够清晰导致你的理解有误。我再解释一下:

假设我们有一个User表,其中有age字段代表用户年龄。我们打算执行一个查询操作来查找所有年龄大于某个值的用户。比如我们要找到所有年龄大于30岁的用户。

那么我们可以写出这样的SQL语句:

SELECT * FROM User WHERE age > 30

当我们执行这个SQL语句时,数据库会返回所有满足条件的用户数据,也就是年龄大于30岁的所有用户。假设这个查询结果中共有30个用户数据,这个时候我们的查询操作就结束了。

上面提到的“每个查询操作会被放入工作队列中”这句话,实际上是一个语言错误,抱歉让您产生困惑。其实正确的表述应该是“查询结果中的30个用户数据会被放入工作队列中等待处理”,也就是说,我们将查询操作获取到的结果数据放入一个队列中,然后使用线程池来处理这些数据。

在这个示例中,我们并没有将每个查询操作都放入工作队列中,而是将查询操作获取到的结果数据放入工作队列中等待处理。希望我的解释能够更加清晰明了。


假如一个用户调用这个方法:
    @Async("taskExecutor")
    @Override
    public CompletableFuture<List<User>> findByAgeGreaterThan(int age) {
        return CompletableFuture.completedFuture(userRepository.findByAgeGreaterThan(age));
    }  它会执行几次?

## 来自 ChatGPT 的消息:
如果一个用户调用了findByAgeGreaterThan方法,那么这个方法会在当前线程中立即执行。当进入到方法体中的 @Async("taskExecutor") 注解时,这个方法会以异步非阻塞的方式执行,并在另一个线程中执行查询操作。

因为我们在方法体中使用了CompletableFuture.completedFuture()方法,所以我们会立即获得一个已经完成的CompletableFuture对象,这个对象包含了查询操作得到的结果集(是一个User对象列表),并且这个结果集的获取也是非阻塞的。

具体来说,假设我们有10个用户同时调用了这个方法,那么这个方法会被执行10次,但是这10个用户会共用一个线程池中的线程去执行查询操作,而且查询操作是异步执行的,所以并不会阻塞当前线程。因此,这个方法执行的次数和查询操作执行的次数是不一样的,每个用户只会调用这个方法一次,并获得一个CompletableFuture对象,这个对象可以用于获取查询结果。

## 来自你的消息:
那么你写一个controller去调用这个方法,并且告诉我它到底有什么用,写一个业务逻辑,要不然这和直接查询有什么区别,为什么要异步操作?

## 来自你的消息:
 @Async("taskExecutor")
    @Override
    public CompletableFuture<List<User>> findByAgeGreaterThan(int age) {
        return CompletableFuture.completedFuture(userRepository.findByAgeGreaterThan(age));
    }写一个方法去调用它,充分的实现它异步的好处

## 来自 ChatGPT 的消息:
假设我们现在有一个业务需求是查询年龄大于等于30岁的用户,并将所有这些用户的名字以及年龄均大于35岁的用户的职业信息保存到一个文件中。如果我们使用同步的方式来实现这个需求,那么可能需要的时间会比较长,因为查询和计算操作都需要执行完毕后才能继续进行。现在我们可以借助Spring中提供的异步调用机制来提高效率。

首先,我们先定义一个Controller类,并提供一个RESTful风格的接口,用于查询并计算这些数据:```java

@RestController
@RequestMapping("/users")
public class UserController {private final UserService userService;public UserController(UserService userService) {this.userService = userService;}@GetMapping("/")public CompletableFuture<String> getUsers() {return userService.findUsersByAgeGreaterThan(30).thenApply(users -> users.stream().filter(user -> user.getAge() > 35).map(user -> user.getName() + ":" + user.getAge()).collect(Collectors.toList())).thenApply(userNames -> {String fileName = "users-info.txt";try {// 将符合条件的用户信息写入到文件中Files.write(Paths.get(fileName), userNames);return "Saved users info to " + fileName;} catch (IOException e) {e.printStackTrace();return "Failed to write users info to file";}});}
}


```

上面的代码中,我们先注入UserService,并在getUsers()方法中调用它提供的异步查询方法findUsersByAgeGreaterThan(),它会返回一个CompletableFuture<List<User>>对象。这个对象会异步地执行查询操作,查询出年龄大于30岁的所有User对象列表,并在获取到查询结果后,将这个结果传给下一个阶段。

下一个阶段中,我们使用filter()方法过滤出年龄大于35岁的用户,然后使用map()方法将这些用户的名字和年龄拼接成一个字符串,最后使用collect()方法将所有这样的结果收集为一个字符串列表。

在最后一个阶段中,我们将符合条件的用户信息写入一个文本文件中,并将结果作为字符串返回给调用方。

需要注意的是,这个接口方法返回的是一个CompletableFuture<String>对象,这个对象会异步地执行告诉调用方查询、计算以及写文件操作的结果。因此,调用这个接口的用户无需等待查询和计算操作的完成,可以在后台继续执行其他任务,提高了效率。

我们可以使用Postman或浏览器直接访问http://localhost/users/路径,执行这个接口方法。接口返回的是一个CompletableFuture对象,最终结果会以同步的方式呈现给调用方。对于异步的操作,我们并不知道它何时会结束,因此如果我们想要知道它何时结束以及获取它的结果,可以通过回调函数的方式来实现:```java

CompletableFuture<String> future = userService.findUsersByAgeGreaterThan(30).thenApply(users -> users.stream().filter(user -> user.getAge() > 35).map(user -> user.getName() + ":" + user.getAge()).collect(Collectors.toList())).thenApply(userNames -> {String fileName = "users-info.txt";try {Files.write(Paths.get(fileName), userNames);return "Saved users info to " + fileName;} catch (IOException e) {e.printStackTrace();return "Failed to write users info to file";}});// 添加回调函数处理结果future.whenComplete((result, throwable) -> {if (throwable != null) {System.out.println("error: " + throwable.getMessage());} else {System.out.println("result: " + result);}});


```

在上面的代码中,我们首先获得一个CompletableFuture对象,然后使用whenComplete()方法添加回调函数。这个回调函数用于处理异步操作的结果,如果异步操作抛出了异常,那么回调函数会接收到异常信息;如果异步操作成功完成,那么回调函数会接收到操作结果。

这段代码的作用是定义了一个异步任务,在异步任务执行完成后,打印执行结果。具体解释如下:

首先,我们创建了一个名为future的CompletableFuture对象,它会异步地执行查询列表并将符合条件的用户信息写入到文件中的操作。

接着,我们调用了CompletableFuture对象的whenComplete()方法,并定义了回调函数,用于处理异步操作的执行结果。当异步操作执行完毕时,回调函数会被执行。

在回调函数中,我们判断了是否存在异常。如果存在异常,则输出错误信息;如果执行成功,则输出结果信息。这样的方式非常方便,让我们可以在异步操作执行完毕后及时地获取执行结果,而无需在等待过程中阻塞线程。

要返回异步任务的结果,我们可以在Controller中直接返回CompletableFuture对象。在异步任务完成之前,客户端仍然会保持连接状态,因此需要根据具体情况而定,决定客户端如何处理这种异步响应。

假设我们在Web应用程序中使用这段代码,在客户端使用Web浏览器或类似工具时,异步任务执行完成后会显示在控制台中。如果客户端使用Ajax方式向服务器发送请求,它可以使用JavaScript从前端获取到异步结果并及时处理。最后,需要注意的是,当使用异步任务时,由于线程切换和上下文切换等操作的开销较小,因此可以提高应用程序的性能和并发能力。

相关文章:

springboot使用线程池的实际应用(一)

在实际Spring Boot项目中&#xff0c;我们可以使用Java的原生多线程或者使用Spring自带的线程池进行多线程编程。多线程的好处在于能够提高应用程序的运行效率&#xff0c;特别是在某些计算密集型场景下。以下是一些使用多线程的典型场景&#xff1a; 并发处理请求&#xff1a…...

ESP-8266学习笔记

1、学习地址 【XMF09F系列资源】基于MicroPython的ESP8266物联网应用开发-赛教资源目录汇总-小蜜蜂笔记 Quick reference for the ESP8266 — MicroPython latest documentation 2、MicroPython及相关开发资源 3、固件烧录与uPyLoader的使用 烧录教程参考: https://www.…...

Java泛型简单的使用

前言 Java里面的泛型在实际开发中运用的很多&#xff0c;学过C的同学一定知道C的模板&#xff0c;而Java中的泛型&#xff0c;一定程度上和它还是挺像的。 相信写Java的人&#xff0c;大都有用过List的实现类ArrayList。在Java没有泛型之前&#xff0c;它的内部是一个Object的…...

深度探索:Qt CMake工程编译后的自动打包策略

深度探索&#xff1a;Qt CMake工程编译后的自动打包策略 1. 引言&#xff08;Introduction&#xff09;1.1 Qt和CMake的基本概念&#xff08;Basic Concepts of Qt and CMake&#xff09;1.2 自动打包的重要性&#xff08;Importance of Automatic Packaging&#xff09; 2. Qt…...

2.7 编译型和解释型

2.7 编译型和解释型 前面我们使用java和javac命令把Hello&#xff0c;World&#xff01;在控制台输出。那为什么输出&#xff0c;这里我们需要掌握两个知识点。编译型语言和解释型语言。在计算机的高级编程语言就分为编译型语言和解释型语言。而我们的Java既有编译型的特点也有…...

校园网自动登陆(河南科技学院)

1. 介绍 河南科技学院校园网自动登陆&#xff08;新乡的很多系统相似&#xff0c;可能也可以用&#xff1f;&#xff09;&#xff0c;java版。可以实现电脑&#xff0c;路由器&#xff0c;软路由的自动认证wifi,后续会上传docker版本的。 源码地址 github&#xff1a;https://…...

C++11 override和final关键字

C11中的override和final关键字是为了增强代码的编译时类型检查和面向对象设计中的继承机制。 override关键字用于显示地表明派生类中的成员函数覆盖了基类中的虚函数。当派生类中的函数与基类中的虚函数签名不同或者没有使用override关键字时&#xff0c;编译器会给出警告或错…...

kafka的log存储解析

kafka的log存储解析——topic的分区partition分段segment以及索引等 引言Kafka中的Message是以topic为基本单位组织的&#xff0c;不同的topic之间是相互独立的。每个topic又可以分成几个不同的partition(每个topic有几个partition是在创建topic时指定 的)&#xff0c;每个…...

4.文件系统

组成 Linux&#xff1a;一切皆文件 索引节点&#xff08;I-node&#xff09; I-node&#xff08;Index Node&#xff09;&#xff1a;文件系统的内部数据结构&#xff0c;用于管理文件的元数据和数据块。 文件的元数据&#xff1a;包括文件的权限、拥有者、大小、时间戳、索引…...

Shell脚本case in esac分支语句应用

记录&#xff1a;434 场景&#xff1a;Shell脚本case in esac分支语句应用。 版本&#xff1a;CentOS Linux release 7.9.2009。 1.case in esac格式 格式&#xff1a; case 值 in 模式1)expression;; 模式2)expression;; 模式n)expression;; esac 解析&#xff1a;case…...

【线性dp必学四道题】线性dp四道经典例题【最长上升子序列】、【最长公共子序列】、【最长公共上升子序列(maxv的由来)】【最长公共子串】

【最长上升子序列】、【最长公共子序列】、【最长公共上升子序列】 最长上升子序列f[i] 表示以i结尾的最长子序列 最长公共子序列f[i][j] 表示 a前i 和 b前j个 最长公共长度 最长公共上升子序列f[i][j]代表所有a[1 ~ i]和b[1 ~ j]中以b[j]结尾的公共上升子序列的集合 最长公共子…...

追寻幸福:探索幸福的关键特征和行为

目录 1. 积极的心态 2. 良好的人际关系 3. 自我接纳和自尊 4. 追求意义和目标 5. 健康的身心状态 6. 感知和实现个人价值 幸福是一个主观的感受&#xff0c;因此不同的人对于幸福的定义和追求方式可能会有所不同。然而&#xff0c;有一些共同的特点和行为模式&#xff0c…...

Redis-02-集群

一、redis5搭建集群 1.1、案例&#xff1a;搭建6台redis主机&#xff0c;配置如下 redis并发量&#xff1a;https://www.gxlcms.com/redis-350423.html主机IP&#xff1a;192.168.168.60~65修改redis配置文件hash槽移动&#xff0c;槽内的数据也随之移动 [root60 ~]# vim /e…...

【2023 · CANN训练营第一季】MindSpore模型快速调优攻略 第三章——MindSpore云上调试调优

1.ModelArts云上调试调优 ModelArts密钥初始化 详细教程&#xff1a; 初始化OBS服务 创建训练作业 2.MindSpore IDE插件效率提升 通过智能代码块推荐、代码自动补全等特性&#xff0c;提升MindSpore脚本开发效率&#xff0c;对接ModelArts云服务&#xff0c;实现模型训…...

python笔记17_实例演练_二手车折旧分析p2

…… 书接上文 4.车辆等级维度 探查车龄为5年的车辆&#xff0c;折旧价值与车辆等级的关系。 # 筛选出车龄为5的数据创建新表 data_age5 data[data[age] 5] data_age5 # 分组聚合计算均值 data_car_level data_age5.groupby(car_level_name)[lowest_price].mean().reset…...

android 12.0长按Power弹出关机对话框去掉屏幕截图和紧急呼救功能

1.概述 在12.0的系统长按关机键,会弹出关机的对话框,关机对话框里面由关机重启截图和紧急呼叫等功能,而由于开发功能需求要求去掉屏幕截图和紧急呼叫等功能,所以就要先找到关机对框的代码 然后实现功能 功能分析: 长按电源键弹出关机对话框,通过adb shell命令发现 就是f…...

2023年下半年软考高级需要报班吗?

首先&#xff0c;对于软考高级考试报班与否的问题&#xff0c;需要根据自身的情况来做出决定。如果你有较强的自学能力&#xff0c;且具备丰富的实际工作经验和技术知识&#xff0c;那么不报班也完全可以自学备考。但如果你对软件工程的知识掌握程度较低&#xff0c;或者时间紧…...

使用WordPress提高企业敏捷性

喜欢WordPress的原因有很多&#xff1a;该平台非常适合内容管理以及控制预算。此外&#xff0c; 在 提高开发效率和简化项目管理方面&#xff0c;WordPress可以通过多种方式提供帮助。 对于任何企业业务&#xff0c;目标始终是在不影响质量的情况下更快地启动项目、发布修复和…...

SSM编程---Day 07

目录 SpringMVC 一、概念 二、springMVC的请求处理流程 三、mvc:annotation-driven 标签的作用 四、HandlerMapping、Handler和HandlerAdapter的介绍 五、SpringMVC 体系结构 六、SpringMVC的常用注解 七、view和controller之间的传值 SpringMVC 一、概念 1、 Spring…...

Seata术语

1.什么是Seata Seata是一款开源的分布式事务解决方案&#xff0c;致力于在微服务架构下提供高性能和简单易用的分布式事务服务。 官网 2.Seata能干嘛 一个典型的分布式事务过程 分布式事务处理过程的一ID三组件模型&#xff1a; Transaction ID XID 全局唯一的事务ID三组…...

【Axure教程】通过文本框维护下拉列表选项

下拉列表&#xff08;Dropdown List&#xff09;是一种常见的用户界面元素&#xff0c;用于提供一组选项供用户选择。它通常以一个展开的列表形式出现&#xff0c;用户可以点击或选择列表中的一个选项。一般来说&#xff0c;他的选项值是由系统代码组成的&#xff0c;所以一般是…...

【C++】基础知识--输入/输出(5)

前面部分的示例程序几乎没有提供与用户的交互&#xff08;如果有的话&#xff09;。他们只是在屏幕上打印简单的值&#xff0c;但标准库提供了许多其他方式通过其输入/输出功能与用户交互。本节将简要介绍一些最有用的方法。 cin标准输入cout标准输出cerr标准错误&#xff08;输…...

经典文献阅读之--PIBT(基于可见树的实时规划方案)

0. 简介 作为路径规划而言&#xff0c;不单单有单个机器人自主路径规划&#xff0c;近年来随着机器人行业的兴起&#xff0c;多机器人自主路径规划也越来越受到关注&#xff0c;对于多智能体寻路(MAPF)。一般的操作会给定一个地图、机器人集群、以及它们的初始位置和目的地&am…...

SAP-MM-计算方案字段解析

01、 “步骤”&#xff1a;标识此条件类型在计算方案中的顺序编号&#xff0c;此编号会影响到后续业务中条件类型的排序&#xff0c;不同条件类型之间的编号最好间隔大一些&#xff0c;这样设置便于以后对计算方案进行扩展&#xff1b; 02、 “计数器”&#xff1…...

go-gf框架两个表以事务方式写入示例

下面是对每一行代码的中文解释&#xff1a; // 创建数据库连接对象 var tx gdb.TX这行代码声明了一个名为tx的变量&#xff0c;类型为gdb.TX&#xff0c;表示数据库事务对象。 // 开启事务 if tx, err g.DB().Ctx(ctx).Begin(ctx); err nil {这行代码通过在数据库连接&…...

2023-5-31第三十一天

conform顺从&#xff0c;遵从&#xff0c;一致 squeeze挤压 proprietary专卖权&#xff0c;专利的&#xff0c;所有的 endeavor努力&#xff0c;尽力 comprise由...组成&#xff0c;包含 compose组成&#xff0c;写作 compact小型的 consult咨询&#xff0c;查阅 expan…...

什么是MQTT?mqtt协议和http协议区别

摘要&#xff1a; 什么是MQTT&#xff1f;MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;译为&#xff1a;消息队列遥测传输&#xff0c;是一种轻量级的通讯协议&#xff0c;用于在网络上传输消息。MQTT 最初由 IBM 发布&#xff0c;后来成为 OASIS&#xf…...

平台使用篇 | 批处理(bat)脚本使用教程(四)

导读 一个开启多机软件在环仿真的批处理文件 (对应卓面RflyTools文件夹中SITLRun快捷方式)&#xff0c;双击它&#xff0c;输入想要生成的飞机数量&#xff0c;即可生成多机软件在环仿真&#xff0c;等待RflySim3D显示3DFixed 4/4&#xff0c;然后可通过QGC控制飞机起飞。运行…...

接口的讲解

在这里之前我想童鞋们都学习过了springmvc。mybatis-plus。Springboot等一些框架 那么下面我们就整合这些框架 我们通过写crud这些接口 写接口的第一步就是引入pom文件 在pom文件里引入一下几种依赖 引入父级工程 thymeleaf导入模版工具类 SpringMVCjar包文件 热部署工具 l…...

G0第21章 :gin框架介绍、RESTful API、Gin渲染

G0第21章 &#xff1a;gin框架 01 内容介绍 https://gin-gonic.com/zh-cn/docs/ web本质 Web是基于HTTP协议进行交互的应用网络Web就是通过使用浏览器/APP访问的各种资源 package mainimport ("fmt""net/http" )func sayHello(w http.ResponseWriter, r…...