WebFlux响应式编程基础之 5 webflux服务端开发讲解

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);
    }

}

5-3 webflux开发-1
这里写图片描述

5-5 server-sent events
SSE H5
这里写图片描述


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);
    }

}

可以去看官方文档,官方文档是正解,毕竟轮子是别人创造的

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</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<User> getAll() {
    return repository.findAll();
}

/**
 * 以SSE形式多次返回数据
 * 
 * @return
 */
@GetMapping(value = "/stream/all", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<User> streamGetAll() {
    return repository.findAll();
}

/**
 * 新增数据
 * 
 * @param user
 * @return
 */
@PostMapping("/")
public Mono<User> 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<ResponseEntity<Void>> 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<Void>(HttpStatus.OK))))
            .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}

/**
 * 修改数据 存在的时候返回200 和修改后的数据, 不存在的时候返回404
 * 
 * @param id
 * @param user
 * @return
 */
@PutMapping("/{id}")
public Mono<ResponseEntity<User>> 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<User>(u, HttpStatus.OK))
            .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}

/**
 * 根据ID查找用户 存在返回用户信息, 不存在返回404
 * 
 * @param id
 * @return
 */
@GetMapping("/{id}")
public Mono<ResponseEntity<User>> findUserById(
        @PathVariable("id") String id) {
    return this.repository.findById(id)
            .map(u -> new ResponseEntity<User>(u, HttpStatus.OK))
            .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}

/**
 * 根据年龄查找用户
 * 
 * @param start
 * @param end
 * @return
 */
@GetMapping("/age/{start}/{end}")
public Flux<User> 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<User> streamFindByAge(@PathVariable("start") int start,
        @PathVariable("end") int end) {
    return this.repository.findByAgeBetween(start, end);
}

/**
 *  得到20-30用户
 * @return
 */
@GetMapping("/old")
public Flux<User> oldUser() {
    return this.repository.oldUser();
}

/**
 * 得到20-30用户
 * 
 * @return
 */
@GetMapping(value = "/stream/old", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<User> 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<ServerResponse> getAllUser(ServerRequest request) {
    return ok().contentType(APPLICATION_JSON_UTF8)
            .body(this.repository.findAll(), User.class);
}

/**
 * 创建用户
 * 
 * @param request
 * @return
 */
public Mono<ServerResponse> createUser(ServerRequest request) {
    // 2.0.0 是可以工作, 但是2.0.1 下面这个模式是会报异常
    Mono<User> 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<ServerResponse> 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<ServerResponse> 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

猜你喜欢

转载自blog.csdn.net/dgutliangxuan/article/details/81332816