新的项目,快速迭代,
技术选型:Spring WebFlux,
非Spring MVC,
之前没有接触过Spring WebFlux,项目中都是使用Spring MVC,
这次学到了新的知识Spring WebFlux,记录下。
Spring产品提供了两个并行的技术路线:
本篇主要讲解应用,响应式编程的细节会在其他文章中分享。
版本:
SpringBoot2.4.5
Swagger:3.0.0
这里,使用spring-boot-starter-webflux作为启动容器(默认为Netty)。
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.4.5version>
<relativePath/>
parent>
<groupId>com.monkeygroupId>
<artifactId>spring-boot-templateartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>spring-boot-templatename>
<description>Template project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
<io.springfox.version>3.0.0io.springfox.version>
<io.swagger.version>1.5.22io.swagger.version>
<fastjson.version>1.2.60fastjson.version>
<logback.version>1.2.3logback.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webfluxartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-boot-starterartifactId>
<version>${io.springfox.version}version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>${io.springfox.version}version>
<exclusions>
<exclusion>
<groupId>io.swaggergroupId>
<artifactId>swagger-annotationsartifactId>
exclusion>
<exclusion>
<groupId>io.swaggergroupId>
<artifactId>swagger-modelsartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>${io.springfox.version}version>
dependency>
<dependency>
<groupId>io.swaggergroupId>
<artifactId>swagger-annotationsartifactId>
<version>${io.swagger.version}version>
dependency>
<dependency>
<groupId>io.swaggergroupId>
<artifactId>swagger-modelsartifactId>
<version>${io.swagger.version}version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>${fastjson.version}version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-coreartifactId>
<version>${logback.version}version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>${logback.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-mongodb-reactiveartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
本文选择MongoDB作为持久层。
MongoDB的部署:https://blog.csdn.net/Xin_101/article/details/130902224
spring:
devtools:
restart:
enabled: true
data:
mongodb:
uri: mongodb://tutorial:[email protected]:27017/tutorial?authSource=tutorial
MongoDB使用Document作为存储数据的基本单位,对比而言,MySQL中的表,新建的数据需要明确指定存储的Document,
下面新建一个user文档,存储用户数据。
其中,@Id用户表示该数据为MongDB中的_id
属性,由MongoDB生成。
而,使用MongoDB操作数据没有自动的数据映射,需要手动映射,因此,在每个需要映射的字段下新建了一个static final的属性名称。
如_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 + '\'' +
'}';
}
}
WebFlux中使用Mono或者Flux作为返回对象,
因此,在Service层直接使用Mono或Flux作为最终的结果类型,
而不是在Controller层中封装最终结果,
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;
});
}
}
接口层,直接使用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);
}
}
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
技术选型依实际情况而定,不是为了引入技术而引入技术,是为了解决问题,引入技术。
(1)Spring WebFlux使用Mono或Flux作为返回类型;
(2)Spring WebFlux一切皆流,通过流将数据一层一层地传递下去,编程方式非常优雅:瀑布式;
(3)Spring WebFlux使用Swagger3.0.0,测试时,使用了Swagger2.9.2无法生成文档;
(4)Spring WebFlux是响应式编程,低延迟、高吞吐系统的首选,但是,数据层的选择受限,如仅支持有限的非关系型数据库:MongoDB、Redis。