欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 本篇是《支持JDK19虚拟线程的web框架》系列的第二篇,前文咱们体验了有虚拟线程支持的web服务,经过测试,发现性能上它与其他两种常见web架构并无明显区别,既然如此,还有必要研究和学习吗?
  • 当然有必要,而且还要通过实战更深入了解虚拟线程与常规线程的区别,在各大框架和库广泛支持虚拟线程之前,打好理论和实践基础,这才是本系列的目标
  • 为了接下来的深入了解,咱们先在本篇打好基础:详细说明前文的web功能是如何开发出来的
  • 为了突出重点,这里先提前剧透,从编码的角度说清楚如何开启虚拟线程支持,其实非常简单,如下图,左侧是quarkus框架下的一个普通web服务,每收到一个web请求,是由线程池中的线程负责响应的,右侧的web服务多了个@RunOnVirtualThread注解,就变成了由新建的虚拟线程去处理web请求,没错,在quarkus框架下使用虚拟线程就是这么简单

  • 在前文中,我们通过返回值也看到了上述两个web服务中,负责web响应的线程的不同,如下所示,从线程名称上很容易看出线程池和虚拟线程的区别

  • 看到这里,您可能会说:就这?一个注解就搞定的事情,你还要写一篇文章?这不是在浪费作者你自己和各位读者的时间吗?

  • 确实,开启虚拟线程,编码只要一行,然而就目前而言,虚拟线程是JDK19专属,而且还只是预览功能,要想在实际运行的时候真正开启并不容易,需要从JDK、maven、IDE等方方面面都要做相关设置,而且如果要做成前文那样的docker镜像,一行docker run命令就能开启虚拟线程,还要在Dockerfile上做点事情(quarkus提供的基础镜像中没有JDK19版本,另外启动命令也要调整)

  • 上述这些都是本文的重点,欣宸已经将这些梳理清楚了,接下来咱们一起实战吧,让前文体验过的web从无到有,再到顺利运行,达到预期

  • 整个开发过程如下图所示,一共十步,接下来开始动手

开发环境

  • 开发电脑:MacBook Pro M1,macOS Monterey 12.6
  • IDE:IntelliJ IDEA 2022.3 EAP (Ultimate Edition) (即未发布前的早期预览版)
  • 另外,M1芯片的电脑上开发和运行JDK19应用,与普通的X86相比感受不到任何变化,只有一点要注意:上传docker镜像到hub.docker.com时,镜像的系统架构是ARM的,这样的镜像在X86电脑上下载下来后不能运行

下载JDK19

  • 实际上,azul的jdk很全面,x86芯片的各平台版本安装包都提供了,您可以根据自己电脑环境选择下载,下面是我选择的适合M1芯片的版本

  • 下载完成后双击安装即可

修改maven的配置

  • 我这里使用的是本地maven,其对应的JDK也要改成19,修改方法是调整环境变量JAVA_HOME,令其指向JDK19目录(在我的电脑上,环境变量是在~/.zshrc里面)

  • 修改后令环境变量生效,然后执行一下命令确认已经使用了JDK19
  1. ~ mvn -version
  2. Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0)
  3. Maven home: /Users/zhaoqin/software/apache-maven-3.8.5
  4. Java version: 19, vendor: Azul Systems, Inc., runtime: /Library/Java/JavaVirtualMachines/zulu-19.jdk/Contents/Home
  5. Default locale: zh_CN_#Hans, platform encoding: UTF-8
  6. OS name: "mac os x", version: "12.6", arch: "aarch64", family: "mac"

创建Quarkus项目

  • 打开IDEA,新建项目,选择Quarkus项目

  • 接下来选择要用到的扩展包(其实就是在图形化页面添加jar依赖),这里的选择如下图:Reactive PostgreSQL client和RESTEasy Reactive Jackson

  • 点击上图右下角的Create按钮后项目开始创建,稍作等待,项目创建完成,如下图,此刻只能感慨:quarkus太贴心,不但有demo源码,还有各种版本的Dockerfile文件,而且git相关的配置也有,甚至README.md都写得那么详细,我是不是可以点击运行按钮直接把程序run起来了

IDEA设置

  • 由于要用到JDK19,下面几项设置需要检查并确认
  • 首先是Project设置,如下图

  • 其次是Modules设置,先配置Sources这个tab页

  • 接下来是Dependencies这个tab页

  • 进入IDEA系统设置菜单

  • 如下图,三个位置需要设置

  • 设置完成了,接下来开始编码

编码

  • 首先确认pom.xml,这是IDEA帮我们创建的,内容如下,有两处改动稍后会说到
  1. <?xml version="1.0"?>
  2. <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
  3. xmlns="http://maven.apache.org/POM/4.0.0"
  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>com.bolingcavalry</groupId>
  7. <artifactId>quarkus-virual-threads-demo</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <properties>
  10. <compiler-plugin.version>3.8.1</compiler-plugin.version>
  11. <maven.compiler.release>19</maven.compiler.release>
  12. <maven.compiler.source>19</maven.compiler.source>
  13. <maven.compiler.target>19</maven.compiler.target>
  14. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  15. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  16. <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
  17. <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
  18. <quarkus.platform.version>2.13.2.Final</quarkus.platform.version>
  19. <skipITs>true</skipITs>
  20. <surefire-plugin.version>3.0.0-M7</surefire-plugin.version>
  21. </properties>
  22. <dependencyManagement>
  23. <dependencies>
  24. <dependency>
  25. <groupId>${quarkus.platform.group-id}</groupId>
  26. <artifactId>${quarkus.platform.artifact-id}</artifactId>
  27. <version>${quarkus.platform.version}</version>
  28. <type>pom</type>
  29. <scope>import</scope>
  30. </dependency>
  31. </dependencies>
  32. </dependencyManagement>
  33. <dependencies>
  34. <dependency>
  35. <groupId>io.quarkus</groupId>
  36. <artifactId>quarkus-resteasy-reactive-jackson</artifactId>
  37. </dependency>
  38. <dependency>
  39. <groupId>io.quarkus</groupId>
  40. <artifactId>quarkus-reactive-pg-client</artifactId>
  41. </dependency>
  42. <dependency>
  43. <groupId>io.quarkus</groupId>
  44. <artifactId>quarkus-arc</artifactId>
  45. </dependency>
  46. <dependency>
  47. <groupId>io.quarkus</groupId>
  48. <artifactId>quarkus-resteasy-reactive</artifactId>
  49. </dependency>
  50. <!-- 生成测试数据 -->
  51. <dependency>
  52. <groupId>net.datafaker</groupId>
  53. <artifactId>datafaker</artifactId>
  54. <version>1.6.0</version>
  55. </dependency>
  56. <dependency>
  57. <groupId>io.quarkus</groupId>
  58. <artifactId>quarkus-junit5</artifactId>
  59. <scope>test</scope>
  60. </dependency>
  61. <dependency>
  62. <groupId>io.rest-assured</groupId>
  63. <artifactId>rest-assured</artifactId>
  64. <scope>test</scope>
  65. </dependency>
  66. </dependencies>
  67. <build>
  68. <plugins>
  69. <plugin>
  70. <groupId>${quarkus.platform.group-id}</groupId>
  71. <artifactId>quarkus-maven-plugin</artifactId>
  72. <version>${quarkus.platform.version}</version>
  73. <extensions>true</extensions>
  74. <executions>
  75. <execution>
  76. <goals>
  77. <goal>build</goal>
  78. <goal>generate-code</goal>
  79. <goal>generate-code-tests</goal>
  80. </goals>
  81. </execution>
  82. </executions>
  83. <!-- 这里是新增的虚拟线程相关特性,start -->
  84. <configuration>
  85. <source>19</source>
  86. <target>19</target>
  87. <compilerArgs>
  88. <arg>--enable-preview</arg>
  89. </compilerArgs>
  90. <jvmArgs>--enable-preview --add-opens java.base/java.lang=ALL-UNNAMED</jvmArgs>
  91. </configuration>
  92. <!-- 这里是新增的虚拟线程相关特性,end -->
  93. </plugin>
  94. <plugin>
  95. <artifactId>maven-compiler-plugin</artifactId>
  96. <version>${compiler-plugin.version}</version>
  97. <configuration>
  98. <compilerArgs>
  99. <arg>-parameters</arg>
  100. </compilerArgs>
  101. </configuration>
  102. </plugin>
  103. <plugin>
  104. <artifactId>maven-surefire-plugin</artifactId>
  105. <version>${surefire-plugin.version}</version>
  106. <configuration>
  107. <systemPropertyVariables>
  108. <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
  109. <maven.home>${maven.home}</maven.home>
  110. </systemPropertyVariables>
  111. </configuration>
  112. </plugin>
  113. <plugin>
  114. <artifactId>maven-failsafe-plugin</artifactId>
  115. <version>${surefire-plugin.version}</version>
  116. <executions>
  117. <execution>
  118. <goals>
  119. <goal>integration-test</goal>
  120. <goal>verify</goal>
  121. </goals>
  122. <configuration>
  123. <systemPropertyVariables>
  124. <native.image.path>${project.build.directory}/${project.build.finalName}-runner
  125. </native.image.path>
  126. <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
  127. <maven.home>${maven.home}</maven.home>
  128. </systemPropertyVariables>
  129. </configuration>
  130. </execution>
  131. </executions>
  132. </plugin>
  133. </plugins>
  134. </build>
  135. <profiles>
  136. <profile>
  137. <id>native</id>
  138. <activation>
  139. <property>
  140. <name>native</name>
  141. </property>
  142. </activation>
  143. <properties>
  144. <skipITs>false</skipITs>
  145. <quarkus.package.type>native</quarkus.package.type>
  146. </properties>
  147. </profile>
  148. </profiles>
  149. </project>
  • pom.xml的第一处改动如下图,要确保全部是19

  • 第二处改动,是在quarkus-maven-plugin插件中增加额外的配置参数,如下图红框

  • 接下来新增配置文件application.properties,在resources目录下
  1. quarkus.datasource.db-kind=postgresql
  2. quarkus.datasource.jdbc.max-size=8
  3. quarkus.datasource.jdbc.min-size=2
  4. quarkus.datasource.username=quarkus
  5. quarkus.datasource.password=123456
  6. quarkus.datasource.reactive.url=postgresql://192.168.0.1:5432/quarkus_test
  • 开始写java代码了,首先是启动类VirtualThreadsDemoApp.java
  1. package com.bolingcavalry;
  2. import io.quarkus.runtime.Quarkus;
  3. import io.quarkus.runtime.annotations.QuarkusMain;
  4. @QuarkusMain
  5. public class VirtualThreadsDemoApp {
  6. public static void main(String... args) {
  7. Quarkus.run(args);
  8. }
  9. }
  • 数据库对应的model类有两个,第一个是gender字段的枚举
  1. package com.bolingcavalry.model;
  2. public enum Gender {
  3. MALE, FEMALE;
  4. }
  • 表对应的实体类
  1. package com.bolingcavalry.model;
  2. import io.vertx.mutiny.sqlclient.Row;
  3. public class Person {
  4. private Long id;
  5. private String name;
  6. private int age;
  7. private Gender gender;
  8. private Integer externalId;
  9. public String getThreadInfo() {
  10. return threadInfo;
  11. }
  12. public void setThreadInfo(String threadInfo) {
  13. this.threadInfo = threadInfo;
  14. }
  15. private String threadInfo;
  16. public Person() {
  17. }
  18. public Person(Long id, String name, int age, Gender gender, Integer externalId) {
  19. this.id = id;
  20. this.name = name;
  21. this.age = age;
  22. this.gender = gender;
  23. this.externalId = externalId;
  24. this.threadInfo = Thread.currentThread().toString();
  25. }
  26. public Long getId() {
  27. return id;
  28. }
  29. public void setId(Long id) {
  30. this.id = id;
  31. }
  32. public String getName() {
  33. return name;
  34. }
  35. public void setName(String name) {
  36. this.name = name;
  37. }
  38. public int getAge() {
  39. return age;
  40. }
  41. public void setAge(int age) {
  42. this.age = age;
  43. }
  44. public Gender getGender() {
  45. return gender;
  46. }
  47. public void setGender(Gender gender) {
  48. this.gender = gender;
  49. }
  50. public Integer getExternalId() {
  51. return externalId;
  52. }
  53. public void setExternalId(Integer externalId) {
  54. this.externalId = externalId;
  55. }
  56. public static Person from(Row row) {
  57. return new Person(
  58. row.getLong("id"),
  59. row.getString("name"),
  60. row.getInteger("age"),
  61. Gender.valueOf(row.getString("gender")),
  62. row.getInteger("external_id"));
  63. }
  64. }
  • 接下来是操作数据库的dao类,可见使用操作方式还是很原始的,还要在代码中手写SQL,取出也要逐个字段匹配,其实quarkus也支持JPA,只不过本篇使用的是响应式数据库驱动,所以选用的是Vert.x生成的连接池PgPool
  1. package com.bolingcavalry.repository;
  2. import com.bolingcavalry.model.Person;
  3. import io.vertx.mutiny.pgclient.PgPool;
  4. import io.vertx.mutiny.sqlclient.Row;
  5. import io.vertx.mutiny.sqlclient.RowSet;
  6. import io.vertx.mutiny.sqlclient.Tuple;
  7. import javax.enterprise.context.ApplicationScoped;
  8. import javax.inject.Inject;
  9. import java.util.ArrayList;
  10. import java.util.List;
  11. @ApplicationScoped
  12. public class PersonRepositoryAsyncAwait {
  13. @Inject
  14. PgPool pgPool;
  15. public Person findById(Long id) {
  16. RowSet<Row> rowSet = pgPool
  17. .preparedQuery("SELECT id, name, age, gender, external_id FROM person WHERE id = $1")
  18. .executeAndAwait(Tuple.of(id));
  19. List<Person> persons = iterateAndCreate(rowSet);
  20. return persons.size() == 0 ? null : persons.get(0);
  21. }
  22. private List<Person> iterateAndCreate(RowSet<Row> rowSet) {
  23. List<Person> persons = new ArrayList<>();
  24. for (Row row : rowSet) {
  25. persons.add(Person.from(row));
  26. }
  27. return persons;
  28. }
  29. }
  • 接下来就是前面截图看到的web服务类VTPersonResource.java,它被注解@RunOnVirtualThread修饰,表示收到web请求在虚拟线程中执行响应代码
  1. package com.bolingcavalry.resource;
  2. import com.bolingcavalry.model.Person;
  3. import com.bolingcavalry.repository.PersonRepositoryAsyncAwait;
  4. import io.smallrye.common.annotation.RunOnVirtualThread;
  5. import javax.inject.Inject;
  6. import javax.ws.rs.GET;
  7. import javax.ws.rs.Path;
  8. import javax.ws.rs.PathParam;
  9. @Path("/vt/persons")
  10. @RunOnVirtualThread
  11. public class VTPersonResource {
  12. @Inject
  13. PersonRepositoryAsyncAwait personRepository;
  14. @GET
  15. @Path("/{id}")
  16. public Person getPersonById(@PathParam("id") Long id) {
  17. return personRepository.findById(id);
  18. }
  19. }
  • 最后是用于对比的常规web服务类PoolPersonResource.java,这个就是中规中矩的在线程池中取一个线程来执行响应代码
  1. package com.bolingcavalry.resource;
  2. import com.bolingcavalry.model.Person;
  3. import com.bolingcavalry.repository.PersonRepositoryAsyncAwait;
  4. import io.smallrye.common.annotation.RunOnVirtualThread;
  5. import javax.inject.Inject;
  6. import javax.ws.rs.GET;
  7. import javax.ws.rs.Path;
  8. import javax.ws.rs.PathParam;
  9. @Path("/pool/persons")
  10. public class PoolPersonResource {
  11. @Inject
  12. PersonRepositoryAsyncAwait personRepository;
  13. @GET
  14. @Path("/{id}")
  15. public Person getPersonById(@PathParam("id") Long id) {
  16. return personRepository.findById(id);
  17. }
  18. }
  • 至此,编码完成

IDEA启动设置

  • 编码完成后,在IDEA上启动应用做本地调试是咱们的基本操作,所以IDEA运行环境也要设置成支持JDK19的预览特性
  • 打开入口类,点击main方法前面的绿色箭头,在弹出的菜单上选择Modify Run Configuration

  • 在运行应用的设置页面,如下操作

  • 选中Add VM options

  • 填入下图箭头所指的内容

  • 终于,设置完成,接下来要启动应用了

启动和验证

  • 启动应用之前,请确认postgresql数据库已启动,并且数据已经导入,具体启动和导入方法请参考前文
  • 点击下图红色箭头中指向的按钮,即可在IDEA中运行应用

  • 在前文中,咱们是在docker上运行应用的,另外在实际场景中应用运行在docker或者k8s环境也是普遍情况,所以接下来一起实战将用做成docker镜像并验证

构建镜像

  • 在创建工程的时候,IDEA就用quarkus模板自动创建了多个Dockerfile文件,下图红框中全是

  • 如果当前应用的JDK不是19,而是11或者17,那么上图红框中的Dockerfile文件就能直接使用了,然而,由于今天咱们应用的JDK必须是19,就无法使用这些Dockerfile了,必须自己写一个,原因很简单,打开Dockerfile.jvm,如下图红色箭头所示,基础镜像是jdk17,而这个仓库中并没有JDK19,也就是说quarkus还没有发布JDK19版本的基础镜像,咱们要自己找一个,另外,容器启动命令也要调整,需要加入--enable-preview才能开启JVM的虚拟线程

  • 自己写的Dockerile文件名为Dockerfile.19,内容如下,可见非常简单:先换基础镜像,再把mvn构建结果复制过去,最后加个启动命令就完事儿了(远不如官方的分层构建节省空间,然而在官方的JDK19镜像方案出来之前,先用下面这个将就着用吧)
  1. FROM openjdk:19
  2. ENV LANGUAGE='en_US:en'
  3. # 执行工作目录
  4. WORKDIR application
  5. COPY --chown=185 target/*.jar ./
  6. RUN mkdir config
  7. EXPOSE 8080
  8. USER 185
  9. ENTRYPOINT ["java", "-jar", "--enable-preview", "quarkus-virual-threads-demo-1.0-SNAPSHOT-runner.jar"]
  • 接下来可以制作镜像了,请确保自己电脑上docker已在运行

  • 首先是常规maven编译打包(uber-jar表示生成的jar中包含了所有依赖库)

  1. mvn clean package -U -DskipTests -Dquarkus.package.type=uber-jar
  • 构建docker镜像
  1. docker build -f src/main/docker/Dockerfile.19 -t bolingcavalry/quarkus-virual-threads-demo:0.0.2 .
  • 镜像制作成功,控制台输出如下图

  • 如果您有hub.docker.com的账号,也可以像我一样推送到公共仓库,方便大家使用

异常测试(没有enable-preview参数会怎么样?)

  • 回顾Dockerfile中启动应用的命令,由于虚拟线程是JDK19的预览功能,因此必须添加下图红色箭头所指的--enable-preview参数才能让虚拟线程功能生效

  • 于是我就在想:不加这个参数会咋样?也就是不开启虚拟线程,但是代码中却要用它,那么真正运行的时候会如何呢?
  • 瞎猜是没用的,还是试试吧,在启动参数中删除--enable-preview,如下图,再重新构建镜像

  • 像前文那样运行容器(再次提醒,确保数据库是正常的),再在浏览器访问http://localhost:8080/vt/persons/1,页面正常显示了,看来功能是不受影响的

  • 再用docker logs命令查看后台日志,如下图箭头所示,quarkus给出了WARN级别的提示:由于当前虚拟机不支持虚拟线程,改为使用默认的阻塞来执行业务逻辑

  • 小结:在不支持虚拟线程的环境强行使用虚拟线程,quarkus会选择兼容的方式继续完成任务

小结和展望

  • 至此,一个完整的quarkus应用已开发完成,该应用使用虚拟线程来响应web请求,而且在quarkus官方还没有提供方案的前提下,咱们依旧完成了docker镜像的制作,最后,因为好奇,还关闭重要参数尝试了一下,一系列操作下来,相信您已经对基础开发了如指掌了

  • 最后,还剩下两个遗留问题,相信您也会有类似困惑

    1. 虚拟线程和常规子线程的区别,究竟能不能看出来?前文已经验证了性能上区别不大,那还有别的方式来观察和区分吗?
    2. 能不能稍微深入一点,仅凭一个@RunOnVirtualThread注解就强行写了两篇博客,实在是太忽悠人了
  • 以上问题会在接下来的《支持JDK19虚拟线程的web框架,终篇》得到解决,还是那句熟悉的广告词:欣宸原创,不辜负您的期待

欢迎关注博客园:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...

支持JDK19虚拟线程的web框架,之二:完整开发一个支持虚拟线程的quarkus应用的更多相关文章

  1. 支持JDK19虚拟线程的web框架之四:看源码,了解quarkus如何支持虚拟线程

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 前文链接 支持JDK19虚拟线程的web框架,之一:体 ...

  2. 支持JDK19虚拟线程的web框架,之五(终篇):兴风作浪的ThreadLocal

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos <支持JDK19虚拟线程的web框架>系列 ...

  3. 支持JDK19虚拟线程的web框架,之一:体验

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 关于虚拟线程 随着JDK19 GA版本的发布,虚拟线程 ...

  4. python 开发一个支持多用户在线的FTP

    ### 作者介绍:* author:lzl### 博客地址:* http://www.cnblogs.com/lianzhilei/p/5813986.html### 功能实现 作业:开发一个支持多用 ...

  5. Python3学习之路~8.6 开发一个支持多用户在线的FTP程序-代码实现

    作业: 开发一个支持多用户在线的FTP程序 要求: 用户加密认证 允许同时多用户登录 每个用户有自己的家目录 ,且只能访问自己的家目录 对用户进行磁盘配额,每个用户的可用空间不同 允许用户在ftp s ...

  6. dotweb——go语言的一个微型web框架(二)启动dotweb

    以上的代码截图表示启动一个dotweb服务,在浏览器里输入127.0.0.1:8080,将会得到一个"index"的页面. app := dotweb.New() dotweb.N ...

  7. Python学习 - 编写一个简单的web框架(二)

    在上一篇日志中已经讨论和实现了根据url执行相应应用,在我阅读了bottle.py官方文档后,按照bottle的设计重写一遍,主要借鉴大牛们的设计思想. 一个bottle.py的简单实例 来看看bot ...

  8. web框架-(二)Django基础

    上面我们已经知道Python的WEB框架有Django.Tornado.Flask 等多种,Django相较与其他WEB框架其优势为:大而全,框架本身集成了ORM.模型绑定.模板引擎.缓存.Sessi ...

  9. 如何快速开发一个支持高效、高并发的分布式ID生成器

    ID生成器是指能产生不重复ID服务的程序,在后台开发过程中,尤其是分布式服务.微服务程序开发过程中,经常会用到,例如,为用户的每个请求产生一个唯一ID.为每个消息产生一个ID等等,ID生成器也是进行无 ...

随机推荐

  1. jsp获取多选框组件的值

    jsp获取多选框组件的值 1.首先写一个带有多选框的前台页 1 <%@ page language="java" contentType="text/html; c ...

  2. 深入解析Flutter下一代渲染引擎Impeller

    作者 魏国梁:字节 Flutter Infra 工程师, Flutter Member,长期专注 Flutter 引擎技术 袁    欣:字节 Flutter Infra 工程师, 长期关注渲染技术发 ...

  3. SpringMVC 02: SpringMVC响应get和post请求 + 5种获取前端数据的方式

    响应get和post请求 SpringMVC中使用@RequestMapping注解完成对get请求和post请求的响应 项目结构和配置文件与SpringMVC博客集中的"SpringMVC ...

  4. 【读书笔记】C#高级编程 第二章 核心C#

    (一)第一个C#程序 创建一个控制台应用程序,然后输入代码,输入完毕后点击F5 Console.WriteLine();这条语句的意思:把括号内的内容输出到界面上: Console.ReadKey() ...

  5. 让Python更优雅更易读(第二集)

    友情链接 让Python更优雅更易读(第一集) 1.装饰器 1.1装饰器特别适合用来实现以下功能 运行时校验:在执行阶段进行特定校验,当校验通不过时终止执行. 适合原因:装饰器可以方便地在函数执行前介 ...

  6. 对表白墙wxss的解释

    一.index.wxss 1 /* 信息 */ 2 .Xinxi{ 3 display: flex; 4 flex-wrap: wrap; 5 margin: 0rpx 1%; 6 } 7 8 9 / ...

  7. win10设置vmware 虚拟机开机自启

    Windows10设置VMware虚拟机开机自启的具体步骤如下: 一.配置vmrun环境变量 1)找到VMware的安装目录,并将目录路径拷贝进入环境变量进行添加,如下图 2)检查添加的环境变量是否生 ...

  8. Jenkins+Gitlab实现持续集成持续部署

    一.GITLAB安装与使用 官网:https://about.gitlab.com/ 1.GITLAB安装要求 (1)中文文档地址 ​ https://docs.gitlab.cn/jh/instal ...

  9. 如何使用 Git 管理配置文件

    现在很多软件的配置都可以在线同步或者支持导入导出,可以很方便的在不同设备上使用.但电脑上还有很多本地配置文件没有办法同步,夸多个设备使用时很难保持一致,换电脑也很麻烦.其实可以使用 Git 来管理这些 ...

  10. SSH 克隆跟HTTP 克隆地址的区别

    1.使用SSH 克隆 需要事先把本机生成的SSH公钥配置到项目中,然后直接复制ssh克隆地址就能直接克隆了 2.使用HTTP克隆 可以不配置本机的SSH公钥,但是克隆时需要使用项目用户的账号密码登录进 ...