spring-webflux

nio概念

全程是Non-Blocking IO 或 New IO 非阻塞IO
1.NIO是面向缓冲区Buffer
每一种java类型都对应一种缓冲区类型
如: ByteBuffer - byte 等
2.selectors:选择器,用于监听多个管道的事件,在非阻塞通道,选择器可以让我们知道什么时候通道准备好了
3.通道(Channel):通道就是io中的流,发送地和目的地都必须通过一个Channel对象
如图
在这里插入图片描述
Channel的分类

  • fileChannel 从文件读写数据
  • SocketChannel 通过TCP读写网络数据
  • ServerSocketChannel监听tcp连接,对每个连接创建对应的SocketChannel
  • DatagramChannel通过udp读写网络数据
  • pipe
    可以通过FileInputStream等getChannel()方法获取通道,socket.getChanner()获取的并非新通道,实现ReadableByteChannel接口read方法和WriteableByteChannel中的write方法为单向,双向同时实现即可,通道的工作模式有两种:阻塞或非阻塞,通道的close()比较特殊,无论在通道时在阻塞模式下还是非阻塞模式下,由于close()方法的调用而导致底层I/O的关闭都可能会造成线程的暂时阻塞。

FileChannel

//通过FileChannel写入数据
    public static void testFileChannelOnWrite() {
        try {
            RandomAccessFile accessFile = new RandomAccessFile("D://file1.txt","rw");
            FileChannel fc = accessFile.getChannel();
            byte[] bytes = new String("hello every one").getBytes();
            ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
            fc.write(byteBuffer);
            byteBuffer.clear();
            byteBuffer.put(new String(",a good boy").getBytes());
            byteBuffer.flip();
            fc.write(byteBuffer);
            fc.close();
 
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //通过FileChannel读取数据
    private static void testFileChannelOnRead() {
        try {
            FileChannel fileChannel = new FileInputStream(new File("D://file.txt")).getChannel();
            ByteBuffer byteBuffer=ByteBuffer.allocate(size);
            int n=0;
            while (fileChannel.read(byteBuffer) != -1) {
                byteBuffer.flip();//缓冲区写——> 读
                while (byteBuffer.hasRemaining()) {
                    System.out.print((char) byteBuffer.get());
                }
                byteBuffer.clear();//缓冲区不会被自动覆盖,需要主动调用该方法
            }
            fileChannel.force(true);
            fileChannel.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //transferFrom()方法可以将数据从源通道传输到FileChannel中
    public static void testTransferFrom(){
        try {
            RandomAccessFile fromFile = new RandomAccessFile("D://file1.txt", "rw");
            FileChannel fromChannel = fromFile.getChannel();
            RandomAccessFile toFile = new RandomAccessFile("D://file2.txt", "rw");
            FileChannel toChannel = toFile.getChannel();
 
            long position =0;
            long count = fromChannel.size();
            toChannel.transferFrom(fromChannel, position, count);
 
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        //ransferTo()方法将数据从FileChannel传输到其他的channel中
        public static void testTransferTo() {
    try {
        RandomAccessFile fromFile = new RandomAccessFile("D://file1.txt", "rw");
        FileChannel fromChannel = fromFile.getChannel();
        RandomAccessFile toFile = new RandomAccessFile("D://file3.txt", "rw");
        FileChannel toChannel = toFile.getChannel();
 
        long position=0;
        long count = fromChannel.size();
        fromChannel.transferTo(position,count,toChannel);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

serverSocketChanel

public class ServerSocketChannelTest {
 
    private int size=1024;
 
    public void initChannel() throws IOException {
        ServerSocketChannel socketChannel = ServerSocketChannel.open();
        socketChannel.configureBlocking(false);
        //socketChannel.socket().bind(new InetSocketAddress(9999));jdk 1.7之前
        socketChannel.bind(new InetSocketAddress(9999));
        ByteBuffer byteBuffer = ByteBuffer.allocate(size);
        while (true) {
            SocketChannel channel = socketChannel.accept();
            if (channel != null) {
                InetSocketAddress remoteAddress = (InetSocketAddress) channel.getRemoteAddress();
                System.out.println(remoteAddress.getAddress());
                System.out.println(remoteAddress.getPort());
                channel.read(byteBuffer);
                byteBuffer.flip();
                while (byteBuffer.hasRemaining()) {
                    System.out.print((char) byteBuffer.get());
                }
            }
        }
    }
 
    public static void main(String[] args) {
        try {
            new ServerSocketChannelTest().initChannel();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

SocketChannel


public class SocketChannelTest {
    private int size=1024;
 
    public void connectServer() throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999));
        ByteBuffer byteBuffer=ByteBuffer.allocate(size);
        byteBuffer.put(new String("hello server").getBytes());
        byteBuffer.flip();
            while (byteBuffer.hasRemaining()) {
                socketChannel.write(byteBuffer);
            }
    }
 
    public static void main(String[] args) throws IOException {
        new SocketChannelTest().connectServer();
    }
}`

原链接

NIO和BIO的区别

1.BIO是面向流,NIO是面向块(缓冲区)
2.BIO是阻塞的,NIO是非阻塞的。
JDK1.7引入了AIO:异步阻塞IO
适用场景
BIO:适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中。比如:文件的上传下载
NIO:适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂
AIO:使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂

Netty服务框架:

Netty是典型的Reactor模型结构,其中常用的Reactor线程模型有三种,
分别为:
Reactor单线程模型
Reactor多线程模型
主从Reactor多线程模型
在这里插入图片描述
原链接

spring webflux 接口声明

接口声明除了保留原有注解式声明的方式,为了满足reactor的编程风格,额外支持了函数式声明的方式。通工具类RouterFunctions过构造RounterFunction对象,并向Spring注入实现函数式接口声明。

@Bean
    public TestHandler testHandler() {
        return new TestHandler();
    }

    @Bean
    public RouterFunction<ServerResponse> routes(TestHandler testHandler) {
        return RouterFunctions.route(RequestPredicates.POST("/route"),
            testHandler::echoName);
    }

    @GetMapping("anno")
    public String sayHello(String name) {
        return "hello world! " + name;
    }

    class TestHandler {
        public Mono<ServerResponse> echoName(ServerRequest request) {
            return request.bodyToMono(Post.class)
              .map(Post::getName)
              .flatMap(name -> ServerResponse.ok()
                .contentType(MediaType.TEXT_PLAIN)
                .body(BodyInserters.fromObject("hello world!" + name)));
        }
    }

在WebFlux中,request和respose不再是原来的ServletRequest和ServletRequest,取而代之的是ServerRequest和ServerResponse。

过滤器Filter

过滤器的使用方法和spring mvc类似,不过与ServerRequest和ServerResponse相同的是,webflux提供了一个新的过滤器接口WebFilter以提供对Mono和Flux的支持

@Component
public class DemoWebFilter implements WebFilter{

    @Override
    public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
        if (!serverWebExchange.getRequest().getHeaders().containsKey("token")) {
            serverWebExchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return Mono.empty();
        }
        return webFilterChain.filter(serverWebExchange);
    }
}

异常处理

@Component
//要比DefaultErrorWebExceptionHandler优先级-1高
//比较底层,如果异常被@ExceptionHandler处理了,那么将不会由此处理
//可以处理filter和webHandler中的异常
@Order(-2)
public class ErrorLogHandler implements WebExceptionHandler {
    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        exchange.getResponse().setStatusCode(HttpStatus.OK);
        byte[] bytes = ("ErrorLogHandler: " + ex.getMessage()).getBytes(StandardCharsets.UTF_8);
        DataBuffer wrap = exchange.getResponse().bufferFactory().wrap(bytes);
        return exchange.getResponse().writeWith(Flux.just(wrap));
    }
}
@ExceptionHandler(Exception.class)
    public String test(Exception e) {
        return "@ExceptionHandler: " + e.getMessage();
}

Multipart和Stream

在基础框架reactor中Mono代表一个单次发送的数据源,而Flux代表一个可多次发送的数据源。在spring webflux的controller中,Mono很好理解,代表前端的一次传参或接口的一次返回。那么Flux该如何使用呢?简单来说Flux在这两个场景下使用:接受Multipart参数、返回Stream类型数据或者用于分批返回。

@PostMapping(value = "", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    Mono<String> requestBodyFlux(@RequestBody Flux<Part> parts) {
        return parts.map(part -> part instanceof FilePart
              ? part.name() + ":" + ((FilePart) part).filename()
              : part.name())
          .collect(Collectors.joining(",", "[", "]"));
    }
    @GetMapping(value = "stream", produces = MediaType.APPLICATION_STREAM_JSON_VALUE)
    public Flux<Post> getBeanStream() {
        return Flux.interval(Duration.ofMillis(500))
          .map(l -> new Post("bian", LocalDateTime.now()))
          .log();
    }

WebSocket

在webflux中使用WebSocket功能很简单,只要注册WebSocketHandlerAdapter用于websocket协议的握手,再定义对应路径的websocket消息处理器即可:

@Configuration
@ComponentScan
@EnableWebFlux
class WebConfig {

    @Bean
    public HandlerMapping handlerMapping() {
        Map<String, WebSocketHandler> map = new HashMap<>();
        map.put("/echo", new EchoWebSocketHandler());
        SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
        mapping.setUrlMap(map);
        return mapping;
    }
    
    @Bean
    WebSocketHandlerAdapter webSocketHandlerAdapter(){
        return new WebSocketHandlerAdapter();
    }
}
public class EchoWebSocketHandler implements WebSocketHandler {

    public EchoWebSocketHandler() {
    }

    @Override
    public Mono<Void> handle(WebSocketSession session) {
        return session.send(    //1. 向一个websocket连接发送一段消息
          session.receive()     //2. 获得入站消息流
            .doOnNext(          //3. 对每一个websocket消息进行处理,相当于stream的map,返回的仍是一个流
              WebSocketMessage::retain  //4. 保留消息(主要针对池化内存(内部使用了netty的ByteBuf),使之引用计数+1,避免过早回收)
            )
        );
    }
}

需要注意的是,通过webSocketSession.receive() 获得的Flux,其每一次发射的数据WebSocketMessage如果是再Netty容器中,是一个对Netty中ByteBuf的保证,而ByteBuf在使用中有一点要注意,就是谁使用谁释放、retain()和release()成对出现。所以当把Flux发射的WebSocketMessage传递给其他方法使用时,注意要retain()增加一次计数,避免上一级方法release()使ByteBuf引用计数归零,导致过早回收。

Mongo

@SpringBootApplication
@EnableMongoAuditing
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}


@Component
@Slf4j
class DataInitializer implements CommandLineRunner {

    private final PostRepository posts;

    public DataInitializer(PostRepository posts) {
        this.posts = posts;
    }

    @Override
    public void run(String[] args) {
        log.info("start data initialization  ...");
        this.posts
          .deleteAll()
          .thenMany(
            Flux
              .just("bianzhaoyu", "xinan")
              .flatMap(
                name -> this.posts.save(Post.builder().name(name).age(25).build())
              )
          )
          .log()
          .subscribe(
            null,
            null,
            () -> log.info("done initialization...")
          );

    }

}

@RestController()
@RequestMapping(value = "/posts")
class PostController {

    private final PostRepository posts;

    public PostController(PostRepository posts) {
        this.posts = posts;
    }

    @GetMapping("")
    public Flux<Post> all() {
        return this.posts.findAll();
    }

    @PostMapping("")
    public Mono<Post> create(@RequestBody Post post) {
        return this.posts.save(post);
    }

    @GetMapping("/{id}")
    public Mono<Post> get(@PathVariable("id") String id) {
        return this.posts.findById(id);
    }

    @PutMapping("/{id}")
    public Mono<Post> update(@PathVariable("id") String id, @RequestBody Post post) {
        return this.posts.findById(id)
          .map(p -> {
              p.setName(post.getName());
              p.setAge(post.getAge());

              return p;
          })
          .flatMap(p -> this.posts.save(p));
    }

    @DeleteMapping("/{id}")
    public Mono<Void> delete(@PathVariable("id") String id) {
        return this.posts.deleteById(id);
    }

}

interface PostRepository extends ReactiveMongoRepository<Post, String> {
}

@Data
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
class Post {

    @Id
    private String id;
    private String name;
    private Integer age;

    @CreatedDate
    private LocalDateTime createdDate;
}

配置:

spring:
  data:
    mongodb:
      uri: mongodb://localhost:27017/blog
      grid-fs-database: images


<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
        </dependency>
</dependencies>

Redis

@Bean
    public ReactiveRedisTemplate<String, Post> reactiveJsonPostRedisTemplate(
      ReactiveRedisConnectionFactory connectionFactory) {
        
        RedisSerializationContext<String, Post> serializationContext = RedisSerializationContext
          .<String, Post>newSerializationContext(new StringRedisSerializer())
          .hashKey(new StringRedisSerializer())
          .hashValue(new Jackson2JsonRedisSerializer<>(Post.class))
          .build();
        return new ReactiveRedisTemplate<>(connectionFactory, serializationContext);
    }

@Component
class PostRepository {

    ReactiveRedisOperations<String, Post> template;

    public PostRepository(ReactiveRedisOperations<String, Post> template) {
        this.template = template;
    }

    Flux<Post> findAll() {
        return template.<String, Post>opsForHash().values("posts");
    }

    Mono<Post> findById(String id) {
        return template.<String, Post>opsForHash().get("posts", id);
    }

    Mono<Post> save(Post post) {
        if (post.getId() != null) {
            String id = UUID.randomUUID().toString();
            post.setId(id);
        }
        return template.<String, Post>opsForHash().put("posts", post.getId(), post)
          .log()
          .map(p -> post);

    }

    Mono<Void> deleteById(String id) {
        return template.<String, Post>opsForHash().remove("posts", id)
          .flatMap(p -> Mono.<Void>empty());
    }

    Mono<Boolean> deleteAll() {
        return template.<String, Post>opsForHash().delete("posts");
    }

}

MySQL

pom

<dependency>
            <groupId>com.github.davidmoten</groupId>
            <artifactId>rxjava2-jdbc</artifactId>
            <version>0.1-RC23</version>
        </dependency>
/**
 * spring-data-jpa是同步的,repository返回的结果并不是Mono或者Flux形式。
 *     可以使用第三方异步jdbc连接池rxjava2-jdbc,但是由于每个方法是异步的,
 * 当数个异步方法组合起来时,并不能保证每个方法都是由一个线程按顺序调用的,
 * 这就使基于ThreadLocal的@Transactional无法使用
 *     当然,可以手动在一个异步方法中开启并提交事务,但是这还是失去了@Transactional组合
 * 不同方法到一个事物的便利性和可扩展性
 * @author xinan
 */
@Component
public class RxJava2PostRepository {
    private Database db;

    RxJava2PostRepository(Database db) {
        this.db = db;
    }

    public Observable<Post> findAll() {
        return this.db.select("select * from posts")
            .get(
                rs -> new Post(rs.getLong("id"),
                    rs.getString("name"),
                    rs.getInt("age")
                )
            )
            .toObservable();
    }

    public Single<Post> findById(Long id) {
        return this.db.select("select * from posts where id=?")
            .parameter(id)
            .get(
                rs -> new Post(rs.getLong("id"),
                    rs.getString("name"),
                    rs.getInt("age")
                )
            )
            .firstElement()
            .toSingle();
    }

    public Single<Integer> save(Post post) {
        return this.db.update("insert into posts(name, age) values(?, ?)")
            .parameters(post.getName(), post.getAge())
            .returnGeneratedKeys()
            .getAs(Integer.class)
            .firstElement()
            .toSingle();
    }

    String sql = "insert into posts(title, content) values(?, ?)";

    //使用事务
    public Single<Integer> saveTx(Post post) {
        return db.connection()
          .map(connection -> {
              connection.setAutoCommit(false);
              PreparedStatement pstmt = connection.prepareStatement(sql);
              pstmt.setInt(1, post.getAge());
              pstmt.setInt(2, post.getAge());
              int i = pstmt.executeUpdate();
              pstmt.close();
              connection.commit();
              return i;
          });
    }
}

原文链接

webclient

webclient响应体text/html utf8

String mono = webClient.post().uri((String) url).body(Mono.just(map),Map.class).retrieve().bodyToMono(String.class).block();
                        JsonResults json=JSONObject.parseObject(mono, JsonResults.class);

webclient响应体application/json转java对象

    JsonResults mono = webClient.post().uri((String) url).body(Mono.just(map),Map.class).retrieve().bodyToMono(JsonResults.class).block();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

犇儿犇儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值