介绍
响应式:Web、网络、IO(存储)、中间件(Redis、MySQL)
应用开发:
● 网络
● 存储:MySQL、Redis
● Web:Webflux
● 前端; 后端:Controller – Service – Dao(r2dbc;mysql)
R2dbc
用法:
1、导入驱动: 导入连接池(r2dbc-pool)、导入驱动(r2dbc-mysql )
2、使用驱动提供的API操作
<dependency><groupId>io.asyncer</groupId><artifactId>r2dbc-mysql</artifactId><version>1.0.5</version></dependency>
daima
最外面的pom
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.10</version></parent><groupId>com.learn</groupId><artifactId>learn-programming</artifactId><version>1.0-SNAPSHOT</version><modules>
<!-- <module>chapter01-stream</module>-->
<!-- <module>chapter02-reactor</module>--><module>learn-webflux</module><module>learn-r2dbc</module>
<!-- <module>chapter05-security</module>--></modules><packaging>pom</packaging><properties>
<!-- <maven.compiler.source>17</maven.compiler.source>-->
<!-- <maven.compiler.source>12</maven.compiler.source>--><maven.compiler.source>1.8</maven.compiler.source>
<!-- <maven.compiler.target>17</maven.compiler.target>-->
<!-- <maven.compiler.target>12</maven.compiler.target>--><maven.compiler.target>1.8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties></project>
r2dbc pom
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>learn-programming</artifactId><groupId>com.learn</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>learn-r2dbc</artifactId><properties><!-- <maven.compiler.source>17</maven.compiler.source>--><maven.compiler.source>12</maven.compiler.source><!-- <maven.compiler.target>17</maven.compiler.target>--><maven.compiler.target>12</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!-- https://mvnrepository.com/artifact/io.asyncer/r2dbc-mysql --><dependency><groupId>io.asyncer</groupId><artifactId>r2dbc-mysql</artifactId><version>1.0.5</version></dependency><!-- 响应式 Spring Data R2dbc--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-r2dbc</artifactId></dependency><!-- 响应式Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies></project>
package com.learn.r2dbc.config.converter;import com.learn.r2dbc.entity.TAuthor;
import com.learn.r2dbc.entity.TBookAuthor;
import io.r2dbc.spi.Row;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;/*** @author lfy* @Description* @create 2023-12-23 22:04** 告诉Spring Data 怎么封装Book对象*/
@ReadingConverter //读取数据库数据的时候,把row转成 TBook
public class BookConverter implements Converter<Row, TBookAuthor> {//1)、@Query 指定了 sql如何发送//2)、自定义 BookConverter 指定了 数据库返回的一 Row 数据,怎么封装成 TBook//3)、配置 R2dbcCustomConversions 组件,让 BookConverter 加入其中生效@Overridepublic TBookAuthor convert(Row source) {if(source == null) return null;//自定义结果集的封装TBookAuthor tBook = new TBookAuthor();tBook.setId(source.get("id", Long.class));tBook.setTitle(source.get("title", String.class));Long author_id = source.get("author_id", Long.class);tBook.setAuthorId(author_id);
// tBook.setPublishTime(source.get("publish_time", Instant.class));//让 converter兼容更多的表结构处理if (source.getMetadata().contains("name")) {TAuthor tAuthor = new TAuthor();tAuthor.setId(author_id);tAuthor.setName(source.get("name", String.class));tBook.setAuthor(tAuthor);}return tBook;}
}
package com.learn.r2dbc.config;import com.learn.r2dbc.config.converter.BookConverter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.r2dbc.convert.R2dbcCustomConversions;
import org.springframework.data.r2dbc.dialect.MySqlDialect;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;/*** @author lfy* @Description* @create 2023-12-23 21:37** 1、写 AuthorRepositories 接口*/
@EnableR2dbcRepositories //开启 R2dbc 仓库功能;jpa
@Configuration
public class R2DbcConfiguration {@Bean //替换容器中原来的@ConditionalOnMissingBeanpublic R2dbcCustomConversions conversions(){//把我们的转换器加入进去; 效果新增了我们的 Converterreturn R2dbcCustomConversions.of(MySqlDialect.INSTANCE,new BookConverter());}
}
package com.learn.r2dbc.controller;import com.learn.r2dbc.entity.TAuthor;
import com.learn.r2dbc.entity.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;/*** @author lfy* @Description* @create 2023-12-23 20:58*/@RestController
public class AuthorController {@GetMapping("/author")public Flux<TAuthor> getAllAuthor(){return null;}@GetMapping("/hello")public String hello() {return "Hello, WebFlux !";}@GetMapping("/user")public Mono<User> getUser() {User user = new User();user.setName("犬小哈");user.setDesc("欢迎关注我的公众号: 小哈学Java");return Mono.just(user);}}
package com.learn.r2dbc.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.relational.core.mapping.Table;import java.util.List;/*** @author lfy* @Description* @create 2023-12-23 20:35*/@Table("t_author")
@NoArgsConstructor
@AllArgsConstructor
@Data
public class TAuthor {@Idprivate Long id;private String name;//1-N如何封装@Transient //临时字段,并不是数据库表中的一个字段
// @Field(exist=false)private List<TBook> books;
}
package com.learn.r2dbc.entity;import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;import java.time.Instant;
import java.util.Date;/*** @author lfy* @Description* @create 2023-12-23 21:52*/
@Table("t_book")
@Data
public class TBook {@Idprivate Long id;private String title;private Long authorId;
// private Instant publishTime; //响应式中日期的映射用 Instant 或者 LocalXxx// private TAuthor author; //每本书有唯一作者;}
package com.learn.r2dbc.entity;import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;/*** @author lfy* @Description* @create 2023-12-24 20:17*/
@Table("t_book")
@Data
public class TBookAuthor {@Idprivate Long id;private String title;private Long authorId;
// private Instant publishTime; //响应式中日期的映射用 Instant 或者 LocalXxxprivate TAuthor author; //每本书有唯一作者;
}
package com.learn.r2dbc.entity;import lombok.Data;/*** @author cg* @ClassName User* @date 2024-07-08 10:09*/@Data
public class User {/*** 姓名*/private String name;/*** 描述*/private String desc;
}
package com.learn.r2dbc.repositories;import com.learn.r2dbc.entity.TAuthor;
import org.springframework.data.r2dbc.repository.Query;
import org.springframework.data.r2dbc.repository.R2dbcRepository;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;import java.util.Collection;/*** @author lfy* @Description* @create 2023-12-23 21:38*/
@Repository
public interface AuthorRepositories extends R2dbcRepository<TAuthor,Long> {//默认继承了一堆CRUD方法; 像mybatis-plus//QBC: Query By Criteria//QBE: Query By Example//成为一个起名工程师 where id In () and name like ?//仅限单表复杂条件查询Flux<TAuthor> findAllByIdInAndNameLike(Collection<Long> id, String name);//多表复杂查询@Query("select * from t_author") //自定义query注解,指定sql语句Flux<TAuthor> findHaha();// 1-1:关联// 1-N:关联//场景:// 1、一个图书有唯一作者; 1-1// 2、一个作者可以有很多图书: 1-N}
package com.learn.r2dbc.repositories;import com.learn.r2dbc.entity.TBookAuthor;
import org.springframework.data.r2dbc.repository.Query;
import org.springframework.data.r2dbc.repository.R2dbcRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Mono;/*** @author lfy* @Description* @create 2023-12-23 21:54*/
@Repository
public interface BookAuthorRepostory extends R2dbcRepository<TBookAuthor,Long> {// 1-1关联关系; 查出这本图书以及它的作者@Query("select b.*,t.name as name from t_book b" +" LEFT JOIN t_author t on b.author_id = t.id " +" WHERE b.id = :bookId")Mono<TBookAuthor> hahaBook(@Param("bookId")Long bookId);}
package com.learn.r2dbc.repositories;import com.learn.r2dbc.entity.TBook;
import org.springframework.data.r2dbc.repository.R2dbcRepository;
import org.springframework.stereotype.Repository;/*** @author lfy* @Description* @create 2023-12-23 21:54*/
@Repository
public interface BookRepostory extends R2dbcRepository<TBook,Long> {// // 1-1关联关系; 查出这本图书以及它的作者
// @Query("select b.*,t.name as name from t_book b" +
// " LEFT JOIN t_author t on b.author_id = t.id " +
// " WHERE b.id = :bookId")
// Mono<TBook> hahaBook(@Param("bookId")Long bookId);}
package com.learn.r2dbc;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** @author lfy* @Description* @create 2023-12-23 20:59*** SpringBoot 对r2dbc的自动配置* 1、R2dbcAutoConfiguration: 主要配置连接工厂、连接池** 2、R2dbcDataAutoConfiguration: 主要给用户提供了 R2dbcEntityTemplate 可以进行CRUD操作* R2dbcEntityTemplate: 操作数据库的响应式客户端;提供CruD api ; RedisTemplate XxxTemplate* 数据类型映射关系、转换器、自定义R2dbcCustomConversions 转换器组件* 数据类型转换:int,Integer; varchar,String; datetime,Instant**** 3、R2dbcRepositoriesAutoConfiguration: 开启Spring Data声明式接口方式的CRUD;* mybatis-plus: 提供了 BaseMapper,IService;自带了CRUD功能;* Spring Data: 提供了基础的CRUD接口,不用写任何实现的情况下,可以直接具有CRUD功能;*** 4、R2dbcTransactionManagerAutoConfiguration: 事务管理**/@SpringBootApplication
public class R2DBCMainApplication {public static void main(String[] args) {SpringApplication.run(R2DBCMainApplication.class,args);}
}
spring:r2dbc:password: 123
# password: 123456username: user123url: r2dbc:mysql://localhost:3306/test?serverZoneId=Asia/Shanghainame: test
logging:level:org.springframework.r2dbc: debug
package com.learn.r2dbc;import com.learn.r2dbc.entity.TAuthor;
import com.learn.r2dbc.entity.TBook;
import com.learn.r2dbc.repositories.AuthorRepositories;
import com.learn.r2dbc.repositories.BookAuthorRepostory;
import com.learn.r2dbc.repositories.BookRepostory;
import io.asyncer.r2dbc.mysql.MySqlConnectionConfiguration;
import io.asyncer.r2dbc.mysql.MySqlConnectionFactory;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.r2dbc.convert.R2dbcCustomConversions;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.query.Query;
import org.springframework.r2dbc.core.DatabaseClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;@SpringBootTest
/*** @author lfy* @Description* @create 2023-12-23 20:16*/
public class R2DBCTest {//最佳实践: 提升生产效率的做法//1、Spring Data R2DBC,基础的CRUD用 R2dbcRepository 提供好了//2、自定义复杂的SQL(单表): @Query;//3、多表查询复杂结果集: DatabaseClient 自定义SQL及结果封装;//Spring Data 提供的两个核心底层组件@Autowired // join查询不好做; 单表查询用R2dbcEntityTemplate r2dbcEntityTemplate; //CRUD API; 更多API操作示例: https://docs.spring.io/spring-data/relational/reference/r2dbc/entity-persistence.html@Autowired //贴近底层,join操作好做; 复杂查询好用DatabaseClient databaseClient; //数据库客户端@AutowiredAuthorRepositories authorRepositories;@AutowiredBookRepostory bookRepostory;@AutowiredBookAuthorRepostory bookAuthorRepostory;@AutowiredR2dbcCustomConversions r2dbcCustomConversions;@Testvoid oneToN() throws IOException {// databaseClient.sql("select a.id aid,a.name,b.* from t_author a " +
// "left join t_book b on a.id = b.author_id " +
// "order by a.id")
// .fetch()
// .all(row -> {
//
// })// 1~6// 1:false 2:false 3:false 4: true 8:true 5:false 6:false 7:false 8:true 9:false 10:false// [1,2,3]// [4,8]// [5,6,7]// [8]// [9,10]// bufferUntilChanged:// 如果下一个判定值比起上一个发生了变化就开一个新buffer保存,如果没有变化就保存到原buffer中// Flux.just(1,2,3,4,8,5,6,7,8,9,10)
// .bufferUntilChanged(integer -> integer%4==0 )
// .subscribe(list-> System.out.println("list = " + list));; //自带分组Flux<TAuthor> flux = databaseClient.sql("select a.id aid,a.name,b.* from t_author a " +"left join t_book b on a.id = b.author_id " +"order by a.id").fetch().all().bufferUntilChanged(rowMap -> Long.parseLong(rowMap.get("aid").toString())).map(list -> {TAuthor tAuthor = new TAuthor();Map<String, Object> map = list.get(0);tAuthor.setId(Long.parseLong(map.get("aid").toString()));tAuthor.setName(map.get("name").toString());//查到的所有图书List<TBook> tBooks = list.stream().map(ele -> {TBook tBook = new TBook();tBook.setId(Long.parseLong(ele.get("id").toString()));tBook.setAuthorId(Long.parseLong(ele.get("author_id").toString()));tBook.setTitle(ele.get("title").toString());return tBook;}).collect(Collectors.toList());tAuthor.setBooks(tBooks);return tAuthor;});//Long 数字缓存 -127 - 127;// 对象比较需要自己写好equals方法flux.subscribe(tAuthor -> System.out.println("tAuthor = " + tAuthor));System.in.read();}@Testvoid author() throws IOException {authorRepositories.findById(1L).subscribe(tAuthor -> System.out.println("tAuthor = " + tAuthor));System.in.read();}@Testvoid book() throws IOException {
// bookRepostory.findAll()
// .subscribe(tBook -> System.out.println("tBook = " + tBook));// bookRepostory.findBookAndAuthor(1L)
// .map(book-> {
// Long authorId = book.getAuthorId();
// TAuthor block = authorRepositories.findById(authorId).block();
// book.setAuthor(block);
// return book;
// });//1-1: 第一种方式: 自定义转换器封装
// bookRepostory.hahaBook(1L)
// .subscribe(tBook -> System.out.println("tBook = " + tBook));//自定义转换器 Converter<Row, TBook> : 把数据库的row转成 TBook; 所有TBook的结果封装都用这个//工作时机: Spring Data 发现方法签名只要是返回 TBook。 利用自定义转换器进行工作//对以前的CRUD产生影响; 错误:Column name 'name' does not exist//解决办法:// 1)、新VO+新的Repository+自定义类型转化器// 2)、自定义类型转化器 多写判断。兼容更多表类型System.out.println("bookRepostory.findById(1L).block() = "+ bookRepostory.findById(1L).block());System.out.println("================");System.out.println("bookAuthorRepostory.hahaBook(1L).block() = " + bookAuthorRepostory.hahaBook(1L).block());//1-1:第二种方式
// databaseClient.sql("select b.*,t.name as name from t_book b " +
// "LEFT JOIN t_author t on b.author_id = t.id " +
// "WHERE b.id = ?")
// .bind(0, 1L)
// .fetch()
// .all()
// .map(row-> {
// String id = row.get("id").toString();
// String title = row.get("title").toString();
// String author_id = row.get("author_id").toString();
// String name = row.get("name").toString();
// TBook tBook = new TBook();
//
// tBook.setId(Long.parseLong(id));
// tBook.setTitle(title);
//
// TAuthor tAuthor = new TAuthor();
// tAuthor.setName(name);
// tAuthor.setId(Long.parseLong(author_id));
//
// tBook.setAuthor(tAuthor);
//
// return tBook;
// })
// .subscribe(tBook -> System.out.println("tBook = " + tBook));// buffer api: 实现一对N;//两种办法://1、一次查询出来,封装好//2、两次查询// 1-N: 一个作者;可以查询到很多图书System.in.read();}//简单查询: 人家直接提供好接口//复杂条件查询:// 1、QBE API// 2、自定义方法// 3、自定义SQL@Testvoid authorRepositories() throws IOException {
// authorRepositories.findAll()
// .subscribe(tAuthor -> System.out.println("tAuthor = " + tAuthor));//statement// [SELECT t_author.id, t_author.name FROM t_author WHERE t_author.id IN (?, ?)// AND (t_author.name LIKE ?)]//方法起名
// authorRepositories.findAllByIdInAndNameLike(
// Arrays.asList(1L,2L),
// "张%"
// ).subscribe(tAuthor -> System.out.println("tAuthor = " + tAuthor));//自定义@Query注解authorRepositories.findHaha().subscribe(tAuthor -> System.out.println("tAuthor = " + tAuthor));System.in.read();}@Testvoid databaseClient() throws IOException {// 底层操作databaseClient.sql("select * from t_author")
// .bind(0,2L).fetch() //抓取数据.all()//返回所有.map(map -> { //map == bean 属性=值System.out.println("map = " + map);String id = map.get("id").toString();String name = map.get("name").toString();return new TAuthor(Long.parseLong(id), name, null);}).subscribe(tAuthor -> System.out.println("tAuthor = " + tAuthor));System.in.read();}@Testvoid r2dbcEntityTemplate() throws IOException {// Query By Criteria: QBC//1、Criteria构造查询条件 where id=1 and name=张三Criteria criteria = Criteria.empty().and("id").is(1L).and("name").is("张三");//2、封装为 Query 对象Query query = Query.query(criteria);r2dbcEntityTemplate.select(query, TAuthor.class).subscribe(tAuthor -> System.out.println("tAuthor = " + tAuthor));System.in.read();}//思想:// 1、有了r2dbc,我们的应用在数据库层面天然支持高并发、高吞吐量。// 2、并不能提升开发效率@Testvoid connection() throws IOException {// r2dbc基于全异步、响应式、消息驱动// jdbc:mysql://localhost:3306/test// r2dbc:mysql://localhost:3306/test//0、MySQL配置MySqlConnectionConfiguration configuration = MySqlConnectionConfiguration.builder().host("localhost").port(3306).username("root").password("123456").database("test").build();//1、获取连接工厂MySqlConnectionFactory connectionFactory = MySqlConnectionFactory.from(configuration);//2、获取到连接,发送sql// JDBC: Statement: 封装sql的//3、数据发布者Mono.from(connectionFactory.create()).flatMapMany(connection ->connection.createStatement("select * from t_author where id=?id and name=?name").bind("id", 1L) //具名参数.bind("name", "张三").execute()).flatMap(result -> {return result.map(readable -> {Long id = readable.get("id", Long.class);String name = readable.get("name", String.class);return new TAuthor(id, name, null);});}).subscribe(tAuthor -> System.out.println("tAuthor = " + tAuthor));//背压; 不用返回所有东西,基于请求量返回;System.in.read();}
}
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>learn-programming</artifactId><groupId>com.learn</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>learn-webflux</artifactId><properties><!-- <maven.compiler.source>17</maven.compiler.source>-->
<!-- <maven.compiler.source>12</maven.compiler.source>--><maven.compiler.source>1.8</maven.compiler.source><!-- <maven.compiler.target>17</maven.compiler.target>-->
<!-- <maven.compiler.target>12</maven.compiler.target>--><maven.compiler.target>1.8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency></dependencies></project>
package com.learn.webflux.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.CorsRegistry;
import org.springframework.web.reactive.config.WebFluxConfigurer;/*** @author lfy* @Description* @create 2023-12-01 22:16*/
@Configuration
public class MyWebConfiguration {//配置底层@Beanpublic WebFluxConfigurer webFluxConfigurer(){return new WebFluxConfigurer() {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedHeaders("*").allowedMethods("*").allowedOrigins("localhost");}};}
}
package com.learn.webflux.controller;import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.reactive.result.view.Rendering;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;import java.time.Duration;/*** @author lfy* @Description* @create 2023-12-01 20:52*/
@ResponseBody@Controller
public class HelloController {//WebFlux: 向下兼容原来SpringMVC的大多数注解和API;@GetMapping("/hello")public String hello(@RequestParam(value = "key",required = false,defaultValue = "哈哈") String key,ServerWebExchange exchange,WebSession webSession,HttpMethod method,HttpEntity<String> entity,@RequestBody String s,FilePart file){// file.transferTo() //零拷贝技术;ServerHttpRequest request = exchange.getRequest();ServerHttpResponse response = exchange.getResponse();String name = method.name();Object aaa = webSession.getAttribute("aaa");webSession.getAttributes().put("aa","nn");return "Hello World!!! key="+key;}// Rendering:一种视图对象。@GetMapping("/bai")public Rendering render(){
// Rendering.redirectTo("/aaa"); //重定向到当前项目根路径下的 aaareturn Rendering.redirectTo("http://www.baidu.com").build();}//现在推荐的方式//1、返回单个数据Mono: Mono<Order>、User、String、Map//2、返回多个数据Flux: Flux<Order>//3、配合Flux,完成SSE: Server Send Event; 服务端事件推送@GetMapping("/haha")public Mono<String> haha(){// ResponseEntity.status(305)
// .header("aaa","bbb")
// .contentType(MediaType.APPLICATION_CBOR)
// .body("aaaa")
// .return Mono.just(0).map(i-> 10/i).map(i->"哈哈-"+i);}@GetMapping("/hehe")public Flux<String> hehe(){return Flux.just("hehe1","hehe2");}//text/event-stream//SSE测试; chatgpt都在用; 服务端推送@GetMapping(value = "/sse",produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<ServerSentEvent<String>> sse(){return Flux.range(1,10).map(i-> {//构建一个SSE对象return ServerSentEvent.builder("ha-" + i).id(i + "").comment("hei-" + i).event("haha").build();}).delayElements(Duration.ofMillis(500));}//SpringMVC 以前怎么用,基本可以无缝切换。// 底层:需要自己开始编写响应式代码}
package com.learn.webflux.exception;import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;/*** @author lfy* @Description* @create 2023-12-01 21:40*/
//全局异常处理@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(ArithmeticException.class)public String error(ArithmeticException exception){System.out.println("发生了数学运算异常"+exception);//返回这些进行错误处理;
// ProblemDetail:
// ErrorResponse :return "炸了,哈哈...";}
}
package com.learn.webflux.filter;import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;/*** @author lfy* @Description* @create 2023-12-01 22:18*/
@Component
public class MyWebFilter implements WebFilter {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {ServerHttpRequest request = exchange.getRequest();ServerHttpResponse response = exchange.getResponse();System.out.println("请求处理放行到目标方法之前...");Mono<Void> filter = chain.filter(exchange); //放行//流一旦经过某个操作就会变成新流Mono<Void> voidMono = filter.doOnError(err -> {System.out.println("目标方法异常以后...");}) // 目标方法发生异常后做事.doFinally(signalType -> {System.out.println("目标方法执行以后...");});// 目标方法执行之后//上面执行不花时间。return voidMono; //看清楚返回的是谁 !!!}
}
package com.learn.webflux;import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import reactor.core.publisher.Mono;
import reactor.netty.http.server.HttpServer;import java.io.IOException;
import java.net.URI;/*** @author lfy* @Description* @create 2023-12-01 20:31*/
public class FluxMainApplication {public static void main(String[] args) throws IOException {//快速自己编写一个能处理请求的服务器//1、创建一个能处理Http请求的处理器。 参数:请求、响应; 返回值:Mono<Void>:代表处理完成的信号HttpHandler handler = (ServerHttpRequest request,ServerHttpResponse response)->{URI uri = request.getURI();System.out.println(Thread.currentThread()+"请求进来:"+uri);//编写请求处理的业务,给浏览器写一个内容 URL + "Hello~!"
// response.getHeaders(); //获取响应头
// response.getCookies(); //获取Cookie
// response.getStatusCode(); //获取响应状态码;
// response.bufferFactory(); //buffer工厂
// response.writeWith() //把xxx写出去
// response.setComplete(); //响应结束//数据的发布者:Mono<DataBuffer>、Flux<DataBuffer>//创建 响应数据的 DataBufferDataBufferFactory factory = response.bufferFactory();//数据BufferDataBuffer buffer = factory.wrap(new String(uri.toString() + " ==> Hello!").getBytes());// 需要一个 DataBuffer 的发布者return response.writeWith(Mono.just(buffer));};//2、启动一个服务器,监听8080端口,接受数据,拿到数据交给 HttpHandler 进行请求处理ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);//3、启动Netty服务器HttpServer.create().host("localhost").port(8080).handle(adapter) //用指定的处理器处理请求.bindNow(); //现在就绑定System.out.println("服务器启动完成....监听8080,接受请求");System.in.read();System.out.println("服务器停止....");}
}
package com.learn.webflux;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** @author lfy* @Description* @create 2023-12-01 20:52*///@EnableWebFlux // 注解: 开启WebFlux自定义; 禁用WebFLux的默认效果,完全自定义// WebFluxAutoConfiguration 的自动配置会生效//@EnableWebFlux //所有WebFluxAutoConfiguration 配置默认效果全部失效
@SpringBootApplication
public class WebFluxMainApplication {public static void main(String[] args) {SpringApplication.run(WebFluxMainApplication.class,args);}
}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h1>哈哈</h1><div id="app" style="width: 500px;height: 300px;background-color: gainsboro"></div><script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>const http = axios.create({baseURL: 'http://localhost:8080/',timeout: 10000000,responseType: 'stream',onDownloadProgress: function (progressEvent) {// 处理原生进度事件// console.log("progressEvent->",progressEvent.event.currentTarget.responseText);document.getElementById("app").innerHTML =progressEvent.event.currentTarget.responseText// responseText 就是sse的完整数据},});http.get('/sse').then(function (response) {// 处理成功情况console.log(response);}).catch(function (error) {// 处理错误情况console.log(error);}).finally(function () {// 总是会执行});
</script></body>
</html>