1 依存性
新しいプロジェクト、迅速なイテレーション、
テクノロジーの選択: Spring
MVC ではなくSpring WebFlux
これまで Spring WebFlux を触ったことがなく、すべてのプロジェクトが Spring MVC を使用していますが、
今回 Spring WebFlux について新しい知識を学んだので記録します。
2 SpringMVC と Spring WebFlux
Spring 製品には、次の 2 つの並行した技術ルートが用意されています。
- Spring MVC および Spring Data に基づくサーブレット API ルート
- Spring WebFlux および Spring Data Reactive に基づくリアクティブ ストリーム ルート
: https://spring.io/reactive
リアクティブ システムは、低遅延、高スループットのワークロードにとって理想的なソリューションです。WebFlux は
すべてをストリーミングします。
Spring WebFlux と Spring MVC の 2 つの技術的ルートの比較を見てみましょう:
出典: https://spring.io/reactive
上の図からわかるように、
Spring WebFlux は非リレーショナル データベースをサポートしていますが、
これはまだサポートされていません。 Spring MVC とはまったく異なります。大規模で、
リレーショナル データベースを使用する必要がある場合は、Spring MVC に基づいてプロジェクトを構築することが最良の選択です。
したがって、Spring WebFlux に基づいてプロジェクトを構築することは、非構造化ストレージに適しています。
この記事では主にアプリケーションについて説明し、レスポンシブプログラミングの詳細については他の記事で共有する予定です。
3 つのプロジェクトの依存関係
バージョン:
SpringBoot2.4.5
Swagger: 3.0.0
ここでは、起動コンテナとして spring-boot-starter-webflux を使用します (デフォルトは Netty)。
<?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>2.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.monkey</groupId>
<artifactId>spring-boot-template</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-template</name>
<description>Template project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<io.springfox.version>3.0.0</io.springfox.version>
<io.swagger.version>1.5.22</io.swagger.version>
<fastjson.version>1.2.60</fastjson.version>
<logback.version>1.2.3</logback.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--接口管理工具-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${io.springfox.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${io.springfox.version}</version>
<exclusions>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${io.springfox.version}</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${io.swagger.version}</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>${io.swagger.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!--日志工具-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!--AOP-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4つのデータベース
この記事では、永続化レイヤーとして MongoDB を選択します。
MongoDB のデプロイ: https://blog.csdn.net/Xin_101/article/details/130902224
4.1 MongoDB の構成
spring:
devtools:
restart:
enabled: true
data:
mongodb:
uri: mongodb://tutorial:[email protected]:27017/tutorial?authSource=tutorial
4.2 ドキュメントの構成
MongoDB ではデータを保存するための基本単位として Document が使用されますが、MySQL のテーブルの場合は、新しく作成されたデータが保存される Document を明確に指定する必要があります。次に、ユーザー データを保存するための新しいユーザー ドキュメントを作成します
。
このうち、@Id user は、そのデータが MongDB 内の属性であり_id
、MongoDB によって生成されたものであることを示します。
ただし、MongoDB を使用してデータを操作する場合、自動データ マッピングは行われず、手動でマッピングする必要があるため、マッピングが必要な各フィールドの下に新しい静的な最終属性名が作成されます。
たとえば_id
、username
データを操作するときに使用される 、 。
package com.monkey.springboottemplate.modules.user.entity;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
/**
* 用户信息.
*
* @author xindaqi
* @since 2023-05-28 11:38
*/
@Document("user")
public class UserEntity {
/**
* 用户主键id,MongoDB自动生成
*/
@Id
private String id;
public final static String _id = "_id";
/**
* 用户id
*/
private String uid;
/**
* 用户姓名
*/
private String username;
public static final String _username = "username";
/**
* 用户性别
*/
private String sex;
public static final String _sex = "sex";
/**
* 创建时间
*/
private Long createdTime;
/**
* 更新时间
*/
private Long updatedTime;
public static final String _updatedTime = "updatedTime";
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Long getCreatedTime() {
return createdTime;
}
public void setCreatedTime(Long createdTime) {
this.createdTime = createdTime;
}
public Long getUpdatedTime() {
return updatedTime;
}
public void setUpdatedTime(Long updatedTime) {
this.updatedTime = updatedTime;
}
@Override
public String toString() {
return "UserEntity{" +
"id='" + id + '\'' +
", uid='" + uid + '\'' +
", username='" + username + '\'' +
", sex='" + sex + '\'' +
", createdTime='" + createdTime + '\'' +
", updatedTime='" + updatedTime + '\'' +
'}';
}
}
5 サービス
WebFlux は戻りオブジェクトとして Mono または Flux を使用します。したがって、最終結果をコントローラー層でカプセル化するのではなく、
サービス層の最終結果タイプとして Mono または Flux を直接使用します。WebFlux: すべてがストリーミング データであるため、コーディングは非常にエレガントです。ウォーターフォール プログラミングを実現するには、データが層ごとに流れていき、最終的に結果が出力されます。
package com.monkey.springboottemplate.modules.user.service.impl;
import com.monkey.springboottemplate.common.response.Response;
import com.monkey.springboottemplate.modules.user.convert.UserConvert;
import com.monkey.springboottemplate.modules.user.dao.UserDAO;
import com.monkey.springboottemplate.modules.user.entity.UserEntity;
import com.monkey.springboottemplate.modules.user.service.IUserService;
import com.monkey.springboottemplate.modules.user.vo.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
/**
* 用户服务实现类.
*
* @author xindaqi
* @date 2021-05-08 16:01
*/
@Service
public class UserServiceImpl implements IUserService {
private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
@Autowired
private ReactiveMongoTemplate mongoTemplate;
@Override
public Mono<Response<String>> addUser(UserAddInputVO params) {
UserEntity userEntity = new UserEntity();
userEntity.setUid(params.getUid());
userEntity.setUsername(params.getUsername());
userEntity.setSex(params.getSex());
userEntity.setCreatedTime(System.currentTimeMillis());
return mongoTemplate.insert(userEntity).flatMap(user -> Mono.just(Response.success(user.getId())));
}
@Override
public Mono<Response<String>> deleteUser(String id) {
Query query = Query.query(Criteria.where(UserEntity._id).is(id));
return mongoTemplate.findAndRemove(query, UserEntity.class).flatMap(user ->
Mono.just(Response.success(id))
);
}
@Override
public Mono<Response<String>> editUser(UserEditInputVO params) {
Query query = Query.query(Criteria.where(UserEntity._id).is(params.getId()));
Update update = new Update()
.set(UserEntity._sex, params.getSex())
.set(UserEntity._username, params.getUsername())
.set(UserEntity._updatedTime, System.currentTimeMillis());
return mongoTemplate.updateFirst(query, update, UserEntity.class).flatMap(user -> Mono.just(Response.success(params.getId())));
}
@Override
public Mono<Response<UserInfoVO>> queryUserById(String id) {
Query query = Query.query(Criteria.where(UserEntity._id).is(id));
return mongoTemplate.findOne(query, UserEntity.class).flatMap(user -> {
UserInfoVO userInfo = new UserInfoVO();
userInfo.setUid(user.getUid());
userInfo.setId(user.getId());
userInfo.setSex(user.getSex());
userInfo.setUsername(user.getUsername());
return Mono.just(Response.success(userInfo));
});
}
@Override
public Mono<Response<List<UserInfoVO>>> queryUserByPage(UserPageInputVO params) {
Response<List<UserInfoVO>> resp = Response.success();
Query query = new Query();
return mongoTemplate.count(query, UserEntity.class).flatMap(count -> {
if (Objects.isNull(count)) {
resp.setTotal(0);
}
resp.setTotal(count);
query.skip((long) (params.getPageStart() - 1) * params.getPageSize()).limit(params.getPageSize());
return mongoTemplate.find(query, UserEntity.class).collectList();
}).map(userLi -> {
List<UserInfoVO> li = userLi.stream().map(UserConvert::convert).collect(Collectors.toList());
resp.setData(li);
return resp;
});
}
}
6 コントローラー
インターフェイス層は Mono を直接使用して結果をカプセル化します。
package com.monkey.springboottemplate.api;
import com.monkey.springboottemplate.common.response.Response;
import com.monkey.springboottemplate.modules.user.service.IUserService;
import com.monkey.springboottemplate.modules.user.vo.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.data.repository.query.Param;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.List;
import static com.monkey.springboottemplate.common.constant.DigitalConstant.ONE;
/**
* 用户增删改查接口.
*
* @author xindaqi
* @date 2021-05-08 16:09
*/
@RestController
@RequestMapping("/api/v1/user")
@Api(tags = "人员配置")
public class UserApi {
@Resource
IUserService userService;
@PostMapping("/add")
@ApiOperation("添加用户")
public Mono<Response<String>> addUser(@RequestBody UserAddInputVO params) {
return userService.addUser(params);
}
@GetMapping("/delete")
@ApiOperation("删除用户")
public Mono<Response<String>> deleteUser(@Param("id") String id) {
return userService.deleteUser(id);
}
@PostMapping("/edit")
@ApiOperation("编辑/修改用户")
public Mono<Response<String>> editUser(@RequestBody UserEditInputVO params) {
return userService.editUser(params);
}
@GetMapping("/query")
@ApiOperation("根据ID查询用户")
public Mono<Response<UserInfoVO>> queryUserById(@Param("id") String id) {
return userService.queryUserById(id);
}
@PostMapping("/query/page")
@ApiOperation("分页查询用户")
public Mono<Response<List<UserInfoVO>>> queryUserByPage(@RequestBody UserPageInputVO params) {
return userService.queryUserByPage(params);
}
}
7 Swagger の構成
Swagger はバージョン 3.0.0 を使用します。
package com.monkey.springboottemplate.common.config;
import springfox.documentation.service.Contact;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import org.springframework.core.env.Profiles;
import org.springframework.core.env.Environment;
import springfox.documentation.oas.annotations.EnableOpenApi;
/**
* Swagger配置.
*
* @author xindaqi
* @since 2023-05-27 22:20
*/
@Configuration
@EnableSwagger2
@EnableOpenApi
public class SwaggerConfig {
@Bean
public Docket createRestApi(Environment environment){
// 配置Swagger显示,仅dev和test环境显示
Profiles profiles = Profiles.of("dev");
boolean b = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(b)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("接口文档")
.contact(new Contact("xindaqi", "[email protected]", "[email protected]"))
.version("1.0")
.description("个人信息")
.termsOfServiceUrl("http://localhost:9121/api/v1")
.build();
}
}
アクセス: http://localhost:9121/swagger-ui/index.htm
8 まとめ
技術の選択は実情に応じて行うものであり、技術を導入するために技術を導入するのではなく、課題を解決するために技術を導入するものである。
(1) Spring WebFlux は戻り値の型として Mono または Flux を使用します;
(2) Spring WebFlux のすべてがフローし、データはフローを通じてレイヤーごとに渡され、プログラミング方法は非常にエレガントです: ウォーターフォール; (3) Spring
WebFlux Swagger3 を使用します。0.0、テスト時に Swagger2.9.2 を使用するとドキュメントを生成できません;
(4) Spring WebFlux は、応答性の高いプログラミング、低遅延、高スループットのシステムの最初の選択肢ですが、データ層の選択肢は限られています。限定的な非リレーショナル データベースのサポート: MongoDB、Redis。