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响应体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();