https://blog.csdn.net/qq_27093465/article/details/64124330 debug技巧
第5章 webflux服务端开发讲解
Spring5 非组塞的开发模式
SpringMvc 与 SpringWebFlux 对比
学习工作机制 工作思想 更加重要
Netty 很重要 读一下 Netty源码
先垂直扩展 –》 后水平扩展
5-2 异步servlet
问题: 1同步servlet阻塞了什么?
答案: 阻塞了Tomcat容器的servlet线程
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class SyncServlet
*/
@WebServlet("/SyncServlet")
public class SyncServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public SyncServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
long t1 = System.currentTimeMillis();
// 执行业务代码
doSomeThing(request, response);
System.out.println("sync use:" + (System.currentTimeMillis() - t1));
}
private void doSomeThing(HttpServletRequest request,
HttpServletResponse response) throws IOException {
// 模拟耗时操作
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
}
//
response.getWriter().append("done");
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
问题: 2异步servlet是怎么样工作的呢?
答案: 线程池,另外一个线程去处理耗时的操作
答案: 同步和异步对于浏览器都是一样的,耗时是一样的,同步异步仅仅是对于后台来说的
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class AsyncServlet
*/
@WebServlet(asyncSupported = true, urlPatterns = { "/AsyncServlet" })
public class AsyncServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public AsyncServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
long t1 = System.currentTimeMillis();
// 开启异步
AsyncContext asyncContext = request.startAsync();
// 执行业务代码
CompletableFuture.runAsync(() -> doSomeThing(asyncContext,
asyncContext.getRequest(), asyncContext.getResponse()));
System.out.println("async use:" + (System.currentTimeMillis() - t1));
}
private void doSomeThing(AsyncContext asyncContext,
ServletRequest servletRequest, ServletResponse servletResponse) {
// 模拟耗时操作
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
}
//
try {
servletResponse.getWriter().append("done");
} catch (IOException e) {
e.printStackTrace();
}
// 业务代码处理完毕, 通知结束
asyncContext.complete();
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class SSE
*/
@WebServlet("/SSE")
public class SSE extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public SSE() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/event-stream");
response.setCharacterEncoding("utf-8");
for (int i = 0; i < 5; i++) {
// 指定事件标识
response.getWriter().write("event:me\n");
// 格式: data: + 数据 + 2个回车
response.getWriter().write("data:" + i + "\n\n");
response.getWriter().flush();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
可以去看官方文档,官方文档是正解,毕竟轮子是别人创造的
<html>
<head>
<meta charset="UTF-8">
<title>Insert title heretitle>
head>
<body>
<script type="text/javascript">
// 初始化, 参数为url
// 依赖H5
var sse = new EventSource("SSE");
sse.onmessage = function(e) {
console.log("message", e.data, e);
}
// 监听指定事件, (就不会进入onmessage了)
sse.addEventListener("me", function(e) {
console.log("me event", e.data);
// 如果不关闭,会自动重连
if (e.data == 3) {
sse.close();
}
});
script>
body>
html>
SSE 与WebSocket的区别
**5-6 完整例子**
**MongoDB 的数据存储格式 **
![这里写图片描述](https://img-blog.csdn.net/20180803093140534?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RndXRsaWFuZ3h1YW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
import javax.validation.constraints.NotBlank;
import org.hibernate.validator.constraints.Range;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Data;
// MongoDB的表
@Document(collection = “user”)
@Data
public class User {
@Id
private String id;
@NotBlank
private String name;
@Range(min=10, max=100)
private int age;
}
package com.imooc.controller;
import javax.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.imooc.domain.User;
import com.imooc.repository.UserRepository;
import com.imooc.util.CheckUtil;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping(“/user”)
public class UserController {
private final UserRepository repository;
public UserController(UserRepository repository) {
this.repository = repository;
}
/**
* 以数组形式一次性返回数据
*
* @return
*/
@GetMapping("/")
public Flux getAll() {
return repository.findAll();
}
/**
* 以SSE形式多次返回数据
*
* @return
*/
@GetMapping(value = "/stream/all", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux streamGetAll() {
return repository.findAll();
}
/**
* 新增数据
*
* @param user
* @return
*/
@PostMapping("/")
public Mono createUser(@Valid @RequestBody User user) {
// spring data jpa 里面, 新增和修改都是save. 有id是修改, id为空是新增
// 根据实际情况是否置空id
user.setId(null);
CheckUtil.checkName(user.getName());
return this.repository.save(user);
}
/**
* 根据id删除用户 存在的时候返回200, 不存在返回404
*
* @param id
* @return
*/
@DeleteMapping("/{id}")
public Mono> deleteUser(@PathVariable("id") String id) {
// deletebyID 没有返回值, 不能判断数据是否存在
// this.repository.deleteById(id)
return this.repository.findById(id)
// 当你要操作数据, 并返回一个Mono 这个时候使用flatMap
// 如果不操作数据, 只是转换数据, 使用map
.flatMap(user -> this.repository.delete(user).then(
Mono.just(new ResponseEntity(HttpStatus.OK))))
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
/**
* 修改数据 存在的时候返回200 和修改后的数据, 不存在的时候返回404
*
* @param id
* @param user
* @return
*/
@PutMapping("/{id}")
public Mono> updateUser(@PathVariable("id") String id,
@Valid @RequestBody User user) {
CheckUtil.checkName(user.getName());
return this.repository.findById(id)
// flatMap 操作数据
.flatMap(u -> {
u.setAge(user.getAge());
u.setName(user.getName());
return this.repository.save(u);
})
// map: 转换数据
.map(u -> new ResponseEntity(u, HttpStatus.OK))
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
/**
* 根据ID查找用户 存在返回用户信息, 不存在返回404
*
* @param id
* @return
*/
@GetMapping("/{id}")
public Mono> findUserById(
@PathVariable("id") String id) {
return this.repository.findById(id)
.map(u -> new ResponseEntity(u, HttpStatus.OK))
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
/**
* 根据年龄查找用户
*
* @param start
* @param end
* @return
*/
@GetMapping("/age/{start}/{end}")
public Flux findByAge(@PathVariable("start") int start,
@PathVariable("end") int end) {
return this.repository.findByAgeBetween(start, end);
}
/**
* 根据年龄查找用户
*
* @param start
* @param end
* @return
*/
@GetMapping(value = "/stream/age/{start}/{end}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux streamFindByAge(@PathVariable("start") int start,
@PathVariable("end") int end) {
return this.repository.findByAgeBetween(start, end);
}
/**
* 得到20-30用户
* @return
*/
@GetMapping("/old")
public Flux oldUser() {
return this.repository.oldUser();
}
/**
* 得到20-30用户
*
* @return
*/
@GetMapping(value = "/stream/old", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux streamOldUser() {
return this.repository.oldUser();
}
}
![这里写图片描述](https://img-blog.csdn.net/20180803093809636?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RndXRsaWFuZ3h1YW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
**Spring官方网站推荐用构造方法注入 但是也不方便啊 添加一个 减少一个 怎么办呢?**
![这里写图片描述](https://img-blog.csdn.net/20180803093626270?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RndXRsaWFuZ3h1YW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
**5-9 完整例子-参数校验**
[源代码链接,请点击我](https://github.com/linliangxuan/SpringBoot2.0-WebFlux-/tree/master)
spring data jpa 里面, 新增和修改都是save. 有id是修改, id为空是新增
**5-10 RouterFunction模式-1**
![这里写图片描述](https://img-blog.csdn.net/20180803145043925?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RndXRsaWFuZ3h1YW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
![这里写图片描述](https://img-blog.csdn.net/20180803145110341?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RndXRsaWFuZ3h1YW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
package com.imooc.handlers;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;
import static org.springframework.web.reactive.function.server.ServerResponse.notFound;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import com.imooc.domain.User;
import com.imooc.repository.UserRepository;
import com.imooc.util.CheckUtil;
import reactor.core.publisher.Mono;
@Component
public class UserHandler {
private final UserRepository repository;
public UserHandler(UserRepository rep) {
this.repository = rep;
}
/**
* 得到所有用户
*
* @param request
* @return
*/
public Mono getAllUser(ServerRequest request) {
return ok().contentType(APPLICATION_JSON_UTF8)
.body(this.repository.findAll(), User.class);
}
/**
* 创建用户
*
* @param request
* @return
*/
public Mono createUser(ServerRequest request) {
// 2.0.0 是可以工作, 但是2.0.1 下面这个模式是会报异常
Mono user = request.bodyToMono(User.class);
return user.flatMap(u -> {
// 校验代码需要放在这里
CheckUtil.checkName(u.getName());
return ok().contentType(APPLICATION_JSON_UTF8)
.body(this.repository.save(u), User.class);
});
}
/**
* 根据id删除用户
*
* @param request
* @return
*/
public Mono deleteUserById(ServerRequest request) {
String id = request.pathVariable("id");
return this.repository.findById(id)
.flatMap(
user -> this.repository.delete(user).then(ok().build()))
.switchIfEmpty(notFound().build());
}
}
package com.imooc.routers;
import static org.springframework.web.reactive.function.server.RequestPredicates.DELETE;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RequestPredicates.path;
import static org.springframework.web.reactive.function.server.RouterFunctions.nest;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import com.imooc.handlers.UserHandler;
@Configuration
public class AllRouters {
@Bean
RouterFunction userRouter(UserHandler handler) {
return nest(
// 相当于类上面的 @RequestMapping("/user")
path("/user"),
// 下面的相当于类里面的 @RequestMapping
// 得到所有用户
route(GET("/"), handler::getAllUser)
// 创建用户
.andRoute(POST("/").and(accept(MediaType.APPLICATION_JSON_UTF8)),
handler::createUser)
// 删除用户
.andRoute(DELETE("/{id}"), handler::deleteUserById));
}
}
“`
5-11 RouterFunction模式-2