背景描述
Java並不是為了Web而誕生,但似乎B/S架構讓Java生機無限,Spring全家桶的助推也使得Java在Web更為強大,微服務體系Spring Cloud更是順風順水,不得不說的Spring應用的痛點就是啟動過慢,內存占用偏高,對服務器資源占用較大,而且JVM的本身就難逃離內存的過度依賴。
隨着容器化技術Docker、Kubernetes,讓雲原生似乎成為了未來的發展方向,雲原生(Cloud-Native)這個概念最早由Pivotal公司的Matt Stine於2013年首次提出,提到雲原生首先想到的關鍵詞可能就是容器化、微服務、Lambda,服務網格等,當然這些是必要元素,但是不代表擁有這些元素就是雲原生應用,很多應用的部署只能說是基於雲來完成,比如私有雲、公有雲,這也是未來的趨勢。雲原生本質上不是部署,而是以什么方式來構建應用,雲原生的最終目的是為了提高開發效率,提升業務敏捷度、擴容性、可用性、資源利用率,降低成本。
Go語言作為一種雲原生語言也體現出了強大的生命力,Java也在變化,2018年Java出現了大量輕量級微服務框架,來面對未來的雲原生趨勢,Red Hat推出的Quarkus、Oracle的Helidon以及Spring Native都在快速發展,擁抱雲原生。
什么是Quarkus
傳統的Java技術棧是為了單機應用設計的,需要耗費大量的內存和CPU,並且啟動速度慢,當然也不存在雲、容器、k8s等技術體系,在雲原生時代,Java技術需要一個變革去迎接這個新世界的挑戰。
Quarkus就是為了應對雲原生而設計,並且是針對HotSpot、GraalVM定制化的k8s native框架,目標是使 Java 成為 Kubernetes 和serverless環境中的領先平台,同時為開發人員提供一個框架來解決更廣泛的分布式應用程序架構。這也反應在官方對Quarkus的介紹Supersonic Subatomic Java。
Quarkus有哪些特殊的地方,官方列舉了以下特點。
這些特點里面最過於吸引人的應該就是:
- Kubernetes-native
- Imperative and reactive code
Quarkus提供了k8s擴展支持,可以讓開發人員不用關心k8s底層復雜的框架設計,也可以很好的使用其進行應用部署。此外還提供了native鏡像的能力,大大提高了應用的啟動時間和減小了內存的占用,同時對命令式和反應式編程的支持,也加大了容錯性,反應式可以提高應用對大量請求的響應速度。
官方文檔:CREATING YOUR FIRST APPLICATION
准備
使用Quarkus需要准備環境
- GraalVM Open-JDK-11
- Maven 3.8.1+
- Quarkus 2.6.2.Final
更多內容查看文檔:https://quarkus.io/guides/getting-started
下載
Quarkus官方網址:https://quarkus.io
GraalVM下載地址:
下載時候注意Graal有基於不同JDK版本的支持,如JDK8,JDK11,JDK11,可根據自己需求下載。
環境變量
配置環境變量方式同Java一樣
- 配置JAVA_HOME或者GRAALVM_HOME C:\Program Files\Java\graalvm-ce-java8-21.0.0.2
- 環境變量配置不生效解決方案:
多個JDK版本情況下出現環境變量切換不生效,Windows本身系統system32里面的環境變量加載等級要優先於用戶設置的環境變量,其根本原因是%JAVA_HOME%在path中配置的位置在%SystemRoot%\system32;后面,放到path最前面就好了。
- 配置好后執行命令確保使用的是GraalVM
C:\Users\starsray>java -version
openjdk version "1.8.0_282"
OpenJDK Runtime Environment (build 1.8.0_282-b07)
OpenJDK 64-Bit Server VM GraalVM CE 21.0.0.2 (build 25.282-b07-jvmci-21.0-b06, mixed mode)
創建Web應用
使用maven創建
使用maven來快速創建一個應用,創建的位置在當前命令執行終端所在的根目錄,項目名稱為getting-started。
- Linux & MacOS
mvn io.quarkus.platform:quarkus-maven-plugin:2.6.2.Final:create \
-DprojectGroupId=org.acme \
-DprojectArtifactId=getting-started \
-DclassName="org.acme.getting.started.GreetingResource" \
-Dpath="/hello"
- Windows
- cmd
mvn io.quarkus.platform:quarkus-maven-plugin:2.6.2.Final:create -DprojectGroupId=org.acme -DprojectArtifactId=getting-started -DclassName="org.acme.getting.started.GreetingResource" -Dpath="/hello"
- powershell
mvn io.quarkus.platform:quarkus-maven-plugin:2.6.2.Final:create "-DprojectGroupId=org.acme" "-DprojectArtifactId=getting-started" "-DclassName=org.acme.getting.started.GreetingResource" "-Dpath=/hello"
使用web頁面創建
使用web頁面:訪問通過網站:https://code.quarkus.io,這種方法類似於spring.io提供的web頁面
使用IDEA創建
使用IDEA,這里不得不說IDEA的強大,其實也是去加載https://code.quarkus.ioweb資源。
運行應用
GraalVM運行
項目創建后通過IDEA打開加載相關依賴啟動項目,在項目根目錄下,執行命令啟動
./mvnw compile quarkus:dev:
或者
mvn quarkus:dev
成功啟動應用后,查看控制台輸出
Listening for transport dt_socket at address: 5005
__ ____ __ _____ ___ __ ____ ______
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2022-01-20 14:38:01,336 INFO [io.quarkus] (Quarkus Main Thread) getting-started 1.0.0-SNAPSHOT on JVM (powered by Quarkus 2.6.2.Final) started in 3.635s.
Listening on: http://localhost:8080
2022-01-20 14:38:01,347 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2022-01-20 14:38:01,348 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy, smallrye-context-propagation, vertx]
通過地址訪問 http://localhost:8080,Quarkus提供了一個Dev UI。
如果配置文件引入了數據庫相關驅動,卻沒有指定可用的jdbc配置信息,啟動項目會要求有可用的docker環境,自動創建docker鏡像並運行,這也說明了quarkus對雲原生的良好支持。
/opt/develop/graalvm-ce-java11-21.3.0/bin/java -Dmaven.multiModuleProjectDirectory=/home/starsray -Dmaven.home=/opt/ideaIU-2021.2.3/plugins/maven/lib/maven3 -Dclassworlds.conf=/opt/ideaIU-2021.2.3/plugins/maven/lib/maven3/bin/m2.conf -javaagent:/opt/ideaIU-2021.2.3/lib/idea_rt.jar=43095:/opt/ideaIU-2021.2.3/bin -Dfile.encoding=UTF-8 -classpath /opt/ideaIU-2021.2.3/plugins/maven/lib/maven3/boot/plexus-classworlds.license:/opt/ideaIU-2021.2.3/plugins/maven/lib/maven3/boot/plexus-classworlds-2.6.0.jar org.codehaus.classworlds.Launcher -Didea.version=2021.2.3 -DskipTests=true quarkus:dev
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< cn.starsray:quarkus-web >-----------------------
[INFO] Building quarkus-web 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- quarkus-maven-plugin:2.6.2.Final:dev (default-cli) @ quarkus-web ---
[INFO] Invoking org.apache.maven.plugins:maven-resources-plugin:2.6:resources) @ quarkus-web
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Invoking io.quarkus.platform:quarkus-maven-plugin:2.6.2.Final:generate-code) @ quarkus-web
[INFO] Invoking org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile) @ quarkus-web
[INFO] Nothing to compile - all classes are up to date
[INFO] Invoking org.apache.maven.plugins:maven-resources-plugin:2.6:testResources) @ quarkus-web
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/starsray/IdeaProjects/quarkus-web/quarkus-web/src/test/resources
[INFO] Invoking io.quarkus.platform:quarkus-maven-plugin:2.6.2.Final:generate-code-tests) @ quarkus-web
[INFO] Invoking org.apache.maven.plugins:maven-compiler-plugin:3.8.1:testCompile) @ quarkus-web
[INFO] Nothing to compile - all classes are up to date
Listening for transport dt_socket at address: 5005
Press [h] for more options>
Tests paused
Press [r] to resume testing, [h] for more options>
Press [r] to resume testing, [o] Toggle test output, [h] for more options>
2022-01-16 23:25:11,416 INFO [org.tes.doc.DockerClientProviderStrategy] (build-37) Loaded org.testcontainers.dockerclient.UnixSocketClientProviderStrategy from ~/.testcontainers.properties, will try it first
2022-01-16 23:25:11,888 INFO [org.tes.doc.DockerClientProviderStrategy] (build-37) Found Docker environment with local Unix socket (unix:///var/run/docker.sock)
2022-01-16 23:25:11,890 INFO [org.tes.DockerClientFactory] (build-37) Docker host IP address is localhost
2022-01-16 23:25:11,935 INFO [org.tes.DockerClientFactory] (build-37) Connected to docker:
Server Version: 19.03.8
API Version: 1.40
Operating System: UnionTech OS Desktop 20 Home
Total Memory: 15688 MB
2022-01-16 23:25:11,938 INFO [org.tes.uti.ImageNameSubstitutor] (build-37) Image name substitution will be performed by: DefaultImageNameSubstitutor (composite of 'ConfigurationFileImageNameSubstitutor' and 'PrefixingImageNameSubstitutor')
2022-01-16 23:25:12,005 INFO [org.tes.uti.RegistryAuthLocator] (build-37) Failure when attempting to lookup auth config. Please ignore if you don't have images in an authenticated registry. Details: (dockerImageName: testcontainers/ryuk:0.3.3, configFile: /home/starsray/.docker/config.json. Falling back to docker-java default behaviour. Exception message: /home/starsray/.docker/config.json (沒有那個文件或目錄)
2022-01-16 23:25:12,676 INFO [org.tes.DockerClientFactory] (build-37) Ryuk started - will monitor and terminate Testcontainers containers on JVM exit
2022-01-16 23:25:12,676 INFO [org.tes.DockerClientFactory] (build-37) Checking the system...
2022-01-16 23:25:12,677 INFO [org.tes.DockerClientFactory] (build-37) ✔︎ Docker server version should be at least 1.6.0
2022-01-16 23:25:12,755 INFO [org.tes.DockerClientFactory] (build-37) ✔︎ Docker environment should have more than 2GB free disk space
2022-01-16 23:25:12,780 INFO [🐳 .io/.0.24]] (build-37) Creating container for image: docker.io/mysql:8.0.24
2022-01-16 23:25:12,781 INFO [org.tes.uti.RegistryAuthLocator] (build-37) Failure when attempting to lookup auth config. Please ignore if you don't have images in an authenticated registry. Details: (dockerImageName: docker.io/mysql:8.0.24, configFile: /home/starsray/.docker/config.json. Falling back to docker-java default behaviour. Exception message: /home/starsray/.docker/config.json (沒有那個文件或目錄)
2022-01-16 23:25:12,874 INFO [🐳 .io/.0.24]] (build-37) Starting container with ID: ec24cbadb7cdd72ce95cbdcb01011173939705e4c7685c070074fa7c0fbb270e
2022-01-16 23:25:13,122 INFO [🐳 .io/.0.24]] (build-37) Container docker.io/mysql:8.0.24 is starting: ec24cbadb7cdd72ce95cbdcb01011173939705e4c7685c070074fa7c0fbb270e
2022-01-16 23:25:13,131 INFO [🐳 .io/.0.24]] (build-37) Waiting for database connection to become available at jdbc:mysql://localhost:32773/default using query 'SELECT 1'
2022-01-16 23:25:26,498 INFO [🐳 .io/.0.24]] (build-37) Container is started (JDBC URL: jdbc:mysql://localhost:32773/default)
2022-01-16 23:25:26,499 INFO [🐳 .io/.0.24]] (build-37) Container docker.io/mysql:8.0.24 started in PT13.732728S
2022-01-16 23:25:26,499 INFO [io.qua.dev.mys.dep.MySQLDevServicesProcessor] (build-37) Dev Services for MySQL started.
2022-01-16 23:25:26,500 INFO [io.qua.dat.dep.dev.DevServicesDatasourceProcessor] (build-37) Dev Services for the default datasource (mysql) started.
__ ____ __ _____ ___ __ ____ ______
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2022-01-16 23:25:27,968 ERROR [io.qua.hib.orm.run.sch.SchemaManagementIntegrator] (Hibernate post-boot validation thread for <default>) Failed to validate Schema: Schema-validation: missing table [employee]
2022-01-16 23:25:27,979 ERROR [io.qua.hib.orm.run.sch.SchemaManagementIntegrator] (Hibernate post-boot validation thread for <default>) The following SQL may resolve the database issues, as generated by the Hibernate schema migration tool. WARNING: You must manually verify this SQL is correct, this is a best effort guess, do not copy/paste it without verifying that it does what you expect.
create table employee (emp_no integer not null, birth_date date not null, first_name varchar(14) not null, gender char(1) not null, hire_date date not null, last_name varchar(16) not null, primary key (emp_no)) engine=InnoDB;
2022-01-16 23:25:28,003 INFO [io.quarkus] (Quarkus Main Thread) quarkus-web 1.0.0-SNAPSHOT on JVM (powered by Quarkus 2.6.2.Final) started in 17.707s. Listening on: http://localhost:8080
2022-01-16 23:25:28,006 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2022-01-16 23:25:28,006 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, config-yaml, hibernate-orm, hibernate-orm-panache, jdbc-mysql, mybatis, mybatis-plus, narayana-jta, reactive-mysql-client, rest-client, rest-client-jackson, resteasy, resteasy-jackson, smallrye-context-propagation, smallrye-openapi, swagger-ui, vertx]
可以看到在啟動過程中創建了Docker容器,並且互通兩個容器。
Native Image運行
這也是quarkus最強大的地方,通過GraalVM的可選組件native image可以將Java應用打包成原生鏡像,真正做到秒級啟動,解決了傳統spring啟動的痛點,當然也期待spring的下一代產品Spring Native。
- 使用命令安裝native支持
sudo gu install native-image
- 打包項目
mvn package -Pnative -DskipTests
- 啟動 做到了秒啟動。
注意事項
啟動編譯失敗
如果本地存在多個JDK版本,項目啟動編譯失敗,請確保Java和Maven的版本是否正確,正確配置后重啟IDEA。
- java
$ java -version
openjdk version "11.0.13" 2021-10-19
OpenJDK Runtime Environment GraalVM CE 21.3.0 (build 11.0.13+7-jvmci-21.3-b05)
OpenJDK 64-Bit Server VM GraalVM CE 21.3.0 (build 11.0.13+7-jvmci-21.3-b05, mixed mode, sharing)
- maven
$ mvn -v
Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537)
Maven home: D:\maven\apache-maven-3.8.4
Java version: 11.0.13, vendor: GraalVM Community, runtime: C:\Program Files\Java\graalvm-ce-java11-21.3.0
Default locale: zh_CN, platform encoding: GBK
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"
windows native build
native image需要先打包native可執行文件,我這里使用的是Linux環境,所以打包沒什么問題,在Windows環境下打包始終不能成功,參考官方文檔也一直未能成功打包,可能跟我使用的Windows11環境有關,有興趣的小伙伴可以自己試試。
項目介紹
對比Spring應用,Quarkus應用在目錄結構上並沒有太大不同,主要是一些依賴實現的區別。Spring針對JavaEE規范有一套自己的實現,Quarkus整合了多個優秀的開源框架實現,類似於Spring Cloud對微服務組件的整合。Quarkus主要整合了以下常用組件,有興趣的可以單獨了解
在getting-started項目中主要了解以下三方面內容
- pom文件
- REST Resource
- Bean
Quarkus項目目錄樹結構
├─.idea
├─.mvn
│ └─wrapper
├─src
│ ├─main
│ │ ├─docker
│ │ ├─java
│ │ │ └─org
│ │ │ └─acme
│ │ │ └─getting
│ │ │ └─started
│ │ └─resources
│ │ └─META-INF
│ │ └─resources
│ └─test
│ └─java
│ └─org
│ └─acme
│ └─getting
│ └─started
└─target
└─classes
└─META-INF
└─resources
pom.xml
在pom.xml中默認依賴了Quarkus的BOM清單,在
<dependencyManagement>
<dependencies>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-bom</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
pom.xml中還添加了打包相關的插件,使用quarkus-maven-plugin來進行打包、啟動、native image等操作。
<build>
<plugins>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus-plugin.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
其中quarkus-resteasy是REST應用的依賴
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
其他依賴
- quarkus-smallrye-openapi openapi實現引入支持swagger-ui
- quarkus-mybatis/quarkus-mybatis-plus mybatis及mybatis-plus組件
- quarkus-hibernate-orm-panache hibernate JPA實現
- quarkus-config-yaml yaml語法配置文件相關依賴
- quarkus-reactive-mysql-client mysql客戶端依賴
- quarkus-jdbc-mysql mysql JDBC驅動依賴
- quarkus-resteasy-jackson rest接口依賴
REST Resource
項目中默認生成了GreetingResource一個REST資源類
package org.acme.getting.started;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/hello")
public class GreetingResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello RESTEasy";
}
}
@Path、@GET、@Produces都是基於JAX-RS規范實現的注解。在Spring盛行的今天,JavaWeb方向國內幾乎人人都是Spring程序員,對於JavaEE的規范了解使用都不多。
JAX-RS是JAVA EE6引入的一個新技術,英文全稱為Java API for RESTful Web Services,它的核心概念是面向資源,即Resource形式,常見的實現有Jersey、RESTEasy,就好比JPA的實現Hibernate。
關於JAX-RS參考:https://cloud.tencent.com/developer/article/1600752
Bean
Spring核心的功能就是IoC和DI,DI是指依賴注入,Quarkus中依賴注入使用了ArC,ArC是JavaEE規范中的CDI的一個實現,是Quarkus定制化的依賴注入組件。
CDI全稱Contexts and Dependency Injection,即上下文依賴注入,JEE眾多規范中的一個,從JavaEE開始CDI成為正式規范。CDI參考文檔:
- Java EE CDI Dependency Injection (@Inject) tutorial
- Java EE CDI Producer methods tutorial
- Java EE CDI bean scopes
Quarkus CDI參考文檔:https://quarkus.io/guides/cdi
通過CDI來初始化一個被容器管理的Bean時候都會指定Bean的生命周期,在Spring中指定的Bean默認都是單例的,生命周期通過@Scope注解來修改。
CDI提供了提供了一系列注解,根據注解名稱就能看出修飾Bean生命周期。
- @ApplicationScoped
- @SessionScoped
- @RequestScoped
- @ConversationScoped
在Quarkus中不需要指定創建Application級別的類,一般也不需要特定標注Bean的生命周期。
With Quarkus, there is no need to create an Application class. It’s supported, but not required. In addition, only one instance of the resource is created and not one per request. You can configure this using the different *Scoped annotations (ApplicationScoped, RequestScoped, etc)
Quarkus同樣支持類似於Spring中Controller注入Service的形式,新增一個GreetingService類
package org.acme.getting.started;
import javax.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class GreetingService {
public String greeting(String name) {
return "hello " + name;
}
}
修改GreetResource類,並注入GreetingService類
package org.acme.getting.started;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.jboss.resteasy.annotations.jaxrs.PathParam;
@Path("/hello")
public class GreetingResource {
@Inject
GreetingService service;
@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("/greeting/{name}")
public String greeting(@PathParam String name) {
return service.greeting(name);
}
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "hello";
}
}
開發
Development Mode
通過quarkus:dev命令會使應用在開發模式啟動,可以對pom.xml、Java類、Resouce目錄下的變動進行熱加載,減少重啟,這大大加快了開發調試效率。
debug模式會默認監聽5005端口,如果想要在項目啟動前進入調試模式通過命令行指定 -Dsuspend,也可以通過-Ddebug=false跳過所有調試。
Testing
在生成的pom.xml文件中默認引入了test依賴和相關打包插件
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
創建的項目中默認包含了一個測試類GreetingResourceTest
package org.acme.getting.started;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import java.util.UUID;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
@QuarkusTest
public class GreetingResourceTest {
@Test
public void testHelloEndpoint() {
given()
.when().get("/hello")
.then()
.statusCode(200)
.body(is("hello"));
}
@Test
public void testGreetingEndpoint() {
String uuid = UUID.randomUUID().toString();
given()
.pathParam("name", uuid)
.when().get("/hello/greeting/{name}")
.then()
.statusCode(200)
.body(is("hello " + uuid));
}
}
類似於SpringBootRunner,Quarkus使用QuarkusTest runner來運行,校驗返回結果。
Package
項目打包可以使以下命令,打包后的可執行jar或者native-image在./target目錄。
- ./mvnw package & mvn package
- mvn package -Pnative -DskipTests(打包native)
其他
banner
Quarkus也提供了類似於SpringBoot banner的功能。
- 禁用banner
- quarkus.banner.enabled=false (application.properties)
- -Dquarkus.banner.enabled=false(Java System Property)
- QUARKUS_BANNER_ENABLED=false (ENV)
- 自定義banner,添加文件到src/main/resources目錄,在application.properties中指定文件路徑
- quarkus.banner.path=name-of-file
Spring對比
注解相關說明:
Spring中有一套自己針對相關功能的注解實現,Quarkus也有自己的整合實現,列舉相關的對應關系。
DI --> CDI
Spring Web --> JAX-RS
Spring Data JPA --> Panache
Swagger/Knife --> OpenAPI
創建Reactive Web應用
Reactive(反應式)與Imperative(命令式)相對應, Reactive一般都關聯着back-pressure, monads, or event-driven architecture等詞匯,也往往表現出一些特點。
- Responsive - they must respond in a timely fashion(及時響應)
- Elastic - they adapt themselves to the fluctuating load(彈性伸縮)
- Resilient - they handle failures gracefully(優雅的處理失敗)
- Asynchronous message passing - the component of a reactive system interact using messages(消息交互)
更多內容:https://principles.reactive.foundation
Reactive 反應式並不是Java所特有,是一種應對高並發、快速響應編程的一系列原則,Spring 5提供了全新的Spring WebFlux框架,相比於傳統的Spring MVC,實現了Reactive Streams規范,不需要Servlet API的支持,創建基於事件循環執行模型的完全異步且非阻塞的應用程序。
Quarkus同樣提供了對Reactive編程的支持。這些原則在使用資源(CPU和內存)的同時更有效地處理比傳統方法更多的負載,同時也會優雅地反應失敗。Quarkus相比於Spring WebFlux的優勢在於你不用糾結如何選擇,可以在同一個應用程序中同時使用反應式和非反應式編程,不需要依賴第三方應用或者技術棧。
Quarkus可以作為反應式和非反應式應用的橋梁。
Quarkus如何實現反應式,借用官方描述的一張圖片,通過Eclipse Vert.x and Netty等構建了一個特有的引擎,來處理non-blocking I/O交互。Quarkus或者應用可以通過代碼來編排數據庫、消息隊列等I/O事件交互。
創建應用
同創建Quarkus Web應用類似,創建反應式應用只需要選擇相應的反應式組件即可。
- Hibernate Reactive with Panache
- RESTEasy Reactive
- Reactive MySQL client
pom.xml對應的依賴
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-reactive-panache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-mysql-client</artifactId>
</dependency>
創建對應的實體類
package com.starsray.entity;
import io.quarkus.hibernate.reactive.panache.PanacheEntity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Lob;
import javax.persistence.Table;
import java.time.LocalDate;
@Entity
@Table(name = "employee")
public class Employee extends PanacheEntity {
@Column(name = "emp_no", nullable = false)
private Integer empNo;
@Column(name = "birth_date", nullable = false)
private LocalDate birthDate;
@Column(name = "first_name", nullable = false, length = 14)
private String firstName;
@Column(name = "last_name", nullable = false, length = 16)
private String lastName;
@Lob
@Column(name = "gender", nullable = false)
private String gender;
@Column(name = "hire_date", nullable = false)
private LocalDate hireDate;
public LocalDate getHireDate() {
return hireDate;
}
public void setHireDate(LocalDate hireDate) {
this.hireDate = hireDate;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public LocalDate getBirthDate() {
return birthDate;
}
public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate;
}
public Integer getEmpNo() {
return empNo;
}
public void setEmpNo(Integer empNo) {
this.empNo = empNo;
}
}
創建實體類對應的Resource,里面包含一個Reactive API和普通API。
package com.starsray;
import com.starsray.entity.Employee;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.smallrye.mutiny.Uni;
import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.List;
@ApplicationScoped
@Path("employee")
public class EmployeeReactiveResource {
@GET
@Path("/{empNo}")
@Produces
public Uni<List<Employee>> get(@PathParam("empNo")int empNo) {
return Employee.list("id", 1L);
}
@POST
@Path("create")
@Produces
@Consumes
public Uni<Response> create(Employee Employee) {
return Panache.<Employee>withTransaction(Employee::persist)
.onItem()
.transform(inserted -> Response.created(URI.create("/Employees/" + inserted.id))
.build());
}
@GET
@Path("/{id}")
@Produces
public Uni<Employee> getSingle(@PathParam("id") Long id) {
return Employee.findById(id);
}
}
配置數據庫連接
quarkus.datasource.db-kind=mysql
quarkus.datasource.username=root
quarkus.datasource.password=root
quarkus.datasource.reactive.url=mysql://localhost:3306/employees
quarkus.hibernate-orm.log.format-sql=true
quarkus.hibernate-orm.log.sql=true
啟動項目
./mvnw quarkus:dev
在體驗上幾乎看不出來區別。Quarkus底層都已經實現了。
Reactive
由於Reactive通過一種異步非阻塞式I/O和數據庫進行交互,因此需要一種異步HTTP實現結構體,Quarkus使用Mutiny 作為其核心的反應式編程模塊,因此HTTP請求支持返回兩種Mutiny類型(Uni and Multi),在引入的Hibernate Reactive with Panache模塊中只需要實體類繼承PanacheEntity類,就可以暴漏並使用這兩種返回類型。沒有特殊指定。RESTEasy Reactive會默認返回List對象為JSONArray。
- GET
注意查看這一段代碼,返回類型是通過Uni來包裝的實體Value,沒有直接返回結果集。當數據庫讀取到相關數據后,Uni會獲取並返回結果。
@GET
@Path("/{empNo}")
@Produces
public Uni<List<Employee>> get(@PathParam("empNo") int empNo) {
return Employee.list("id", 1L);
}
Uni<?>是一種異步返回類型,有點類似future,Uni作為一個占位符等待結果(暫稱為Item)返回,當接收到Mutiny分發的Item時,開發人員可以進行一些業務邏輯的處理,也可以表達為Reactive的延續性(相對於傳統命令式阻塞I/O的順序性),體現在獲取一個Uni,當Uni占位符得到分發的Item時,執行其他過程。
為什么返回類型是通過Uni來返回Item,通過關系型數據庫查詢,直接返回List
- POST
查看添加的POST請求代碼,根據JAX-RS的規范沒有提供一種類似於Spring @RequestBody的注解,默認會以JSON類型接收POST請求。
@POST
@Path("create")
public Uni<Response> create(Employee Employee) {
return Panache.<Employee>withTransaction(Employee::persist)
.onItem()
.transform(inserted -> Response.created(URI.create("/Employees/" + inserted.id))
.build());
}
為了對數據庫進行寫操作,需要開啟一個數據庫事務,Panache.
命令式與反應式
參考官方文檔上面的例子簡單的示例了反應式編程,在使用上幾乎和傳統命令式編程沒有區別,你可能會疑惑反應式編程和命令式編程有哪些不同或者有哪些好處。為了更好的理解和對比,我們需要先了解反應式和命令式在執行模型上的不同,理解執行模型的是理解Reactive的必要前提。
- blocking I/O
在傳統的命令式編程中,依賴阻塞式I/O模型,框架會分配一個線程去處理一個請求,請求的整個過程都在這個線程上進行,這種模型非常不適合大規模擴展,為了處理大量的請求就需要大量的線程,應用的並發性能會受限於工作線程的數量,當需要與遠程服務進行交互調用時候,這些線程就會被阻塞。並且每個線程都映射到 OS 線程,因此在內存和 CPU 方面都有成本,大大降低了資源的利用率。
blocking I/O
- non-blocking I/O
反應式編程依賴非阻塞式I/O並且使用不同的執行模型,non-blocking I/O提供了一種高效的方式來處理並發I/O,很小一部分I/O線程可以處理大量的並發I/O,通過這種模型,處理請求不會委托給工作線程,而是直接使用這些 I/O 線程。這種模型節省了內存和 CPU,因為不需要創建工作線程來處理請求,提高了並發性,並且消除了對線程數量的限制,由於減少了線程切換的數量,它還提高了響應時間。
- 順序性到延續性
命令式體現在順序性,反應式體現在延續性,是兩種本質風格的轉變,二者在模型上最大的差別體現在,反應式編程的請求是由I/O線程來處理的,少量的線程即可處理大量的並發請求。
延續性風格編碼在處理請求過程需要與遠程服務(如 HTTP API 或數據庫)交互時,它不會阻塞執行等待響應結果返回,相反,它會調度 I/O 操作並延續處理請求的剩余代碼。 這種延續可以作為回調(使用 I/O 結果調用的函數)傳遞,或者使用更高級的結構體,例如反應式編程或協程。 不管延續如何表達,重要的是對I/O 線程的釋放,因此該線程可用於處理另一個請求。 當調度的 I/O 完成時,I/O 線程執行繼續,並且繼續處理掛起的請求。
因此,與 I/O 阻塞執行的命令式模型不同,反應式切換到基於延續的設計,其中 I/O 線程被釋放,並在 I/O 完成時調用延續。 因此,I/O 線程可以處理多個並發請求,從而提高應用程序的整體並發性。
Quarkus提供了不同的反應式編程庫:
- Mutiny - an intuitive and event-driven reactive programming library
- Kotlin co-routines - a way to write asynchronous code in a sequential manner
參考文檔:
更多資料: