教学视频:响应式开发教学视频
代码资源:weblux学习资源
文章目录
一、函数式编程/lambda
- 概念
略 - 为何要使用函数式编程
简化编程
int[] num = {1,-88,23,76,88,-67,56,8,23};
//找出最小值:传统方法
int min = Integer.MAX_VALUE;
for (int i : num) {
if (i < min) {
min = i;
}
}
System.out.println(min);
//函数式编程,paralle并发执行
int min2 = IntStream.of(num).parallel().min().getAsInt();
System.out.println(min2);
//创建线程:传统方法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
}).start();
//函数式
new Thread(() -> System.out.println("hello")).start();
1、lambda表达式
要实现的接口中只能有一个方法
jdk8的一些新特性:
public class lambda {
public static void main(String[] args) {
interface1 interface1 = i -> i*2;
interface1 interface2 = (i) ->{
return i*2;
};
interface1 interface3 = (int i) -> {
System.out.println("----");
return i*2;
};
System.out.println(interface1.hello3(2));
}
}
//说明是函数式接口,这样就不能有两个方法了,不然会报错,能加的尽量都加上这个注解
@FunctionalInterface
interface interface1{
int hello(int i);
// int hello2(int i);
//带默认实现方法的接口方法
default int hello3(int i){
return i;
}
}
@FunctionalInterface
interface interface2{
int hello(int i);
//带默认实现方法的接口方法
default int hello3(int i){
return i;
}
}
interface interface3 extends interface1,interface2{
//具有相同的默认实现方法,需要重写方法避免冲突
@Override
default int hello3(int i) {
return 0;
}
}
2、函数接口
public class 函数接口 {
public static void main(String[] args) {
interface5 i5 = i -> {
return "hello"+i;
};
temp temp1 = new temp();
temp1.fun(i5);
Function<Integer, String> function = i -> {
return "hello" + i;
};
//还支持链式操作,将上一个lambda表达式当成一个接口,重新嵌套lambda的输入输出模式
temp1.fun2(function.andThen(s -> {return "***"+s;}));
}
}
interface interface5 {
String hello(int i);
}
class temp {
public void fun(interface5 i5) {
System.out.println(i5.hello(2));
}
//用lambda不需要知道接口的定义是什么,只需要知道输入什么输出什么就可以
//因此可以使用jdk8的函数接口Function,用apply调用Function的唯一方法,则不用声明接口
public void fun2(Function<Integer,String> i5) {
System.out.println(i5.apply(3));
}
}
其他函数接口:
public static void main(String[] args) {
//断言函数:判断Integer是否。。。。
// IntPredicate:结合声明,适用于基本的数据类型
Predicate<Integer> predicate = i->i>0;
System.out.println(predicate.test(8));
//消费接口:只有一个输入
// IntConsumer
Consumer<String> consumer = s-> System.out.println(s);
consumer.accept("hello");
}
3、方法引用
可以使方法更加简介
public static void main(String[] args) {
//传统方法
Consumer<String> consumer = s -> System.out.println(s);
//方法引用:传入参数只有一个,且函数执行体只有一个调用,且函数参数和传入参数是一样的话则可以使用方法引用
Consumer<String> consumer2 = System.out::println;
consumer.accept("hello1");
consumer2.accept("hello2");
}
/**
* 方法引用的方式
*/
public class test2 {
public static void main(String[] args) {
//静态方法的引用,直接使用方法名引用方法
Consumer<Dog> consumer = Dog::bark;
Dog dog = new Dog();
dog.setName("8809");
consumer.accept(dog);
//非静态方法,使用实例调用方法
dog.setNum(8);
// Function<Integer, Integer> function = dog::eat;
// UnaryOperator<Integer> function = dog::eat;
// System.out.println("还剩下"+function.apply(2)+"斤");
IntUnaryOperator intUnaryOperator = dog::eat;
System.out.println("还剩下"+intUnaryOperator.applyAsInt(2)+"斤");
//手动将实例传给非静态方法,实现非静态方法调用
//用jdk默认的参数方法名来调用
BiFunction<Dog,Integer,Integer> biFunction = Dog::eat;
System.out.println("还剩下"+biFunction.apply(dog,2)+"斤");
//构造方法的调用:默认的空构造方法
Supplier<Dog> supplier = Dog::new;
System.out.println("创建了"+supplier.get());
//带参数的构造方法调用
Function<String, Dog> function = Dog::new;
System.out.println("创建了"+function.apply("旺财"));
}
}
class Dog {
Dog() {
}
Dog(String name) {
this.name = name;
}
private String name;
private int num;
public static void bark(Dog dog) {
System.out.println(dog.name);
}
//jdk默认会将当前实例传入当前非静态方法,参数名为this,位置在第一个
//手动加不加没有关系
public int eat(Dog this,int number) {
System.out.println("吃了"+number+"斤狗粮");
num =num-number;
return num;
}
public void setNum(int num) {
this.num = num;
}
public void setName(String name) {
this.name = name;
}
}
4、 类型推断
public class test {
public static void main(String[] args) {
//变量类型定义
Fun fun = (x,y)->x+y;
test test1 = new test();
// test1.test((x,y)->x+y);
//重载有歧义就直接强转
test1.test((Fun) (x,y)->x+y);
//数组
Fun[] funs = {(x,y)->x+y};
//强转
Object fun2 = (Fun)(x,y)->x+y;
//通过返回类型
Fun fun3 = create();
}
public void test(Fun fun) {
}
//重载
public void test(Fun2 fun) {
}
public static Fun create() {
return (x,y)->x+y;
}
}
@FunctionalInterface
interface Fun{
int add(int x, int y);
}
@FunctionalInterface
interface Fun2{
int sub (int x, int y);
}
5、级联表达式和柯里化
- 基本概念
级联表达式:有多个箭头的lambda表达式
柯里化:把有多个参数的函数转化为只有一个参数的函数,达到标准化函数的目的
public static void main(String[] args) {
Function<Integer,Function<Integer, Integer>> fun = x->y->x+y;
System.out.println(fun.apply(2).apply(3));
Function<Integer, Function<Integer, Function<Integer, Integer>>>
fun2 = x -> y -> z -> x + y + z;
System.out.println(fun2.apply(1).apply(2).apply(3));
//循环调用
int[] temp = {1,2,3};
Function tempFun = fun2;
for (int i = 0; i < temp.length; i++) {
if (tempFun instanceof Function) {
Object object = tempFun.apply(temp[i]);
if (object instanceof Function) {
//返回的还是一个函数的话,则替换tempFun,继续循环
tempFun = (Function) object;
} else {
//不是函数则输出内容
System.out.println(object);
}
}
}
}
6、Stream流编程
- 概念:高效的数据迭代器,与生活中的流水线差不多
- 外部迭代、内部迭代
public static void main(String[] args) {
int[] nums = {1, 2, 3};
int sum = 0;
//外部迭代
for (int num : nums) {
sum += num;
}
System.out.println(sum);
//内部迭代
sum = IntStream.of(nums).sum();
System.out.println(sum);
}
- 中间操作/终止操作和惰性求值
public class Test {
public static void main(String[] args) {
int[] nums = {1, 2, 3};
int sum = 0;
//外部迭代
for (int num : nums) {
sum += num;
}
System.out.println(sum);
//内部迭代
//map就是中间操作:返回的是Stream的操作
//sum就是终止操作
sum = IntStream.of(nums).map(Test::fun).sum();
System.out.println(sum);
//即计算的结果(或者说还没有结果)没有别人要用到,因此就不会浪费算力去执行
System.out.println("惰性求值:没有终止操作,中间操作不会执行");
IntStream.of(nums).map(Test::fun);
}
public static int fun(int i) {
System.out.println("执行了相乘操作");
return i * 2;
}
}
- 创建
public static void main(String[] args) {
List list = new ArrayList<>();
//从集合创建
list.stream();
//并行流
list.parallelStream();
//从数组创建
Arrays.stream(new int[]{1, 2, 3});
//用数字流创建
//直接输入数字流
IntStream.of(1, 2, 3);
//1-10的边界流
IntStream.rangeClosed(1, 10);
//随机数字流,限制10个
new Random().ints().limit(10);
//自己创建
// generate参数是一个提供者
Stream.generate(() -> {
int[] ints = {1, 2, 3};
return ints;
});
Stream.generate(() -> new Random().nextInt()).limit(10);
//iterate,参数是一个初始值用于传入lambda,和一个输入输出相同的lambda
Stream.iterate(0,x->x+2).limit(10).forEach(System.out::println);
}
- 中间操作
unordered:并行流里才会用到,用得不多,将流无序化。
public static void main(String[] args) {
String s = "my name is 8076";
//从数字流创建
// 用“ ”分词
// filter:参数断言函数式接口,过滤长度小于等于2的
//map:参数Function,转化
//forEach:参数消费者函数式接口
Stream.of(s.split(" ")).filter(str->str.length()>2)
.map(str->str.length()).forEach(System.out::println);
//flatMap:A->B属性(是个集合),结果是得到A元素里面所有B属性的集合
//flatMap:Function需要一个string和一个流
//intStream/longStream并不是Stream的子类所以需要装箱boxd
//在i(Integer)转成char之前需要拆箱
Stream.of(s.split(" ")).flatMap(str->str.chars().boxed())
.forEach(i->System.out.println((char)i.intValue()));
//peek:用于debug,与forEach一样,但peek属性中间操作
Stream.of(s.split(" ")).peek(System.out::println)
.forEach(System.out::println);
//distinct:去重
Stream.of(1,1,2,3,4,4).distinct().forEach(System.out::println);
//sorted:排序
Stream.of(1,6,3,8,3,9).sorted().forEach(System.out::println);
//limit:用于无限流的限制数量
new Random().ints().limit(10).forEach(System.out::println);
//skip:跳过一个
Stream.of(1,2,3,4,5,6).skip(1).forEach(System.out::println);
}
- 终止操作
短路操作:不需要等所有流都计算完就可以结束这个流的操作
public static void main(String[] args) {
String str = "my name is 007";
//使用并行流(charsStream),乱序,开启并行就会乱序
str.chars().parallel().forEach(i->System.out.print((char)i));
System.out.println();
//forEachOrdered:不会乱序
str.chars().parallel().forEachOrdered(i->System.out.print((char)i));
System.out.println();
//collect:将元素收集到某个数据类型中
List<String> collect = Stream.of(str.split(" ")).collect(Collectors.toList());
System.out.println(collect);
//toArray:不带参数的:复制数据直接返回object的数组
Object[] objects = Stream.of(1, 2, 3, 4, 5, 6).toArray();
for (Object object : objects) {
System.out.println(object);
}
//带参数的,不懂
//reduce:减少,将流合成一个数据(返回值继续作为第二次运算的一个元素)
Optional<String> reduce = Stream.of(str.split(" "))
.reduce((s1, s2) -> s1 + "|" + s2);
//orElse与get一样,但如果取不到则返回空不会报错
System.out.println(reduce.orElse(""));
//带初始值的reduce
String reduce1 = Stream.of(str.split(" "))
.reduce("", (s1, s2) -> s1 + "|" + s2);
System.out.println(reduce1);
//求所有单词总长度
Integer reduce2 = Stream.of(str.split(" ")).map(s -> s.length())
.reduce(0, (s1, s2) -> s1 + s2);
System.out.println(reduce2);
//max:取最大值,参数是一个比较器,查看源码可知该比较器需要传入两个泛型,输出一个int
//min同理,但是求出最小的那一个
//求最长单词
Optional<String> max = Stream.of(str.split(" "))
.max((s1, s2) -> s1.length() - s2.length());
Optional<String> min = Stream.of(str.split(" "))
.min((s1, s2) -> s1.length() - s2.length());
System.out.println(max.get()+" "+min.get());
//count:输出流的元素个数
System.out.println(Stream.of(str.split(" ")).count());
//findFirst:返回第一个,并终止流
//findAny:返回任意一个,通常是第一个,并行情况下则不确定
Optional<Integer> first = Stream.of(1, 2, 3, 4, 5, 6).findAny();
Optional<Integer> first1 = Stream.of(1, 2, 3, 4, 5, 6).findFirst();
System.out.println(first.orElse(0));
System.out.println(first1.orElse(0));
//allMatch:参数是一个断言,对所有元素进行判断,都满足条件则返回true,否则返回false
//anyMatch:任意一个满足则返回true
//noneMatch:都不满足返回true
System.out.println(Stream.of(1, 2, 3, 4, 5, 6).noneMatch(i -> i > 6));
}
7、并行流
public static void main(String[] args) {
//一秒输出一个数字,count终止流
// IntStream.range(1,100).peek(Test::fun).count();
//加上并行后,即可同时输出多个
// IntStream.range(1, 100).parallel().peek(Test::fun).count();
//并行串行同时调用时,后面的操作会覆盖前面的操作
// IntStream.range(1,100)
// //并行
// .parallel().peek(Test::fun)
// //串行
// .sequential().peek(Test::fun2).count();
//防止线程被阻塞,可以自己创建线程池
ForkJoinPool pool = new ForkJoinPool(10);
pool.submit(() -> IntStream.range(1, 100).parallel().peek(Test::fun).count());
pool.shutdown();
//防止main线程比pool先结束,给pool也就是当前对象main加锁
synchronized (pool) {
try {
pool.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void fun(int i) {
System.out.println(Thread.currentThread().getName() + " " + i);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void fun2(int i) {
System.out.println(i);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
8、收集器(重要)
public class Test {
public static void main(String[] args) {
List<Student> list = Arrays.asList(
new Student("zhangsan", 34,1),
new Student("lisi",43,1),
new Student("wangwu",51,2)
);
//获取所有学生的年龄
//优化s -> s.getAge()换成Student::getAge
List<Integer> ages = list.stream().map(Student::getAge).collect(Collectors.toList());
ages.forEach(System.out::println);
//set,自动去重
Set<Integer> set = list.stream().map(Student::getAge).collect(Collectors.toSet());
//自定义类型
TreeSet<Integer> treeSet = list.stream().map(Student::getAge)
.collect(Collectors.toCollection(TreeSet::new));
//统计汇总信息
IntSummaryStatistics summaryAges = list.stream()
.collect(Collectors.summarizingInt(Student::getAge));
System.out.println(summaryAges);
//分块
Map<Boolean, List<Student>> choose = list.stream()
.collect(Collectors.partitioningBy(s -> s.getAge() > 40));
System.out.println(choose);
//分组
Map<Integer, List<Student>> group = list.stream()
.collect(Collectors.groupingBy(Student::getGroup));
System.out.println(group);
//统计分组个数,平均值等其他信息同理,也可以继续分组同理嵌套
Map<Integer, Long> group2 = list.stream()
.collect(Collectors.groupingBy(Student::getGroup,Collectors.counting()));
System.out.println(group2);
}
}
class Student{
private String name;
private int age;
private int group;
public Student() {
}
public Student(String name, int age,int group) {
this.name = name;
this.age = age;
this.group = group;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getGroup() {
return group;
}
public void setGroup(int group) {
this.group = group;
}
}
9、Stream的运行机制
/**
* 机制:
* 1、所有操作链式执行,所有元素只迭代一次
* 2、所有中间操作返回一个新的流,有一个属性sourceStage都指向下一个操作的链表头部,以此循环
* 3、有状态操作会把无状态操作阶段,单独处理
* 4、在并行环境下,有状态的中间操作并不一定可以并行操作
* 5、parallel,sequential也是中间操作(返回stream),但是他们创建流,只修改流Head的并行标志
*/
public class test {
public static void main(String[] args) {
Stream.generate(() -> new Random().nextInt())
.limit(50)
//第一个无状态操作
.peek(s -> {
System.out.println(Thread.currentThread().getName()+"peek:" + s);
})
//第二个无状态操作
.filter(s -> {
System.out.println(Thread.currentThread().getName()+"filter");
return s > 10000;
})
//有状态操作:基本都是两个操作数以上
.sorted((i1,i2)->{
System.out.println(Thread.currentThread().getName()+"排序"+i1+" "+i2);
return i1.compareTo(i2);
})
//第三个无状态操作
.peek(s -> {
System.out.println(Thread.currentThread().getName()+"peek2:" + s);
})
.parallel()
.count();
}
}
二、JDK9 ReactiveStream响应式流
- 概念
- 背压
使得发布者和订阅者可以沟通,不至于让订阅者压力太大
publisher:发布者,发布消息
subscriber:接受者,接受,下一条,完成,出错
subscription:订阅关系,实现背压的关键,要求,取消
processor:处理者,没有任何的方法,但是实现了发布者和订阅者,可以用于处理中间某个环节的事务
//简单的发布订阅模式
public class Test {
public static void main(String[] args) throws InterruptedException {
//定义发布者,发布类型是Integer
//使用SubmissionPublisher,他实现了publish接口
SubmissionPublisher<Integer> integerSubmissionPublisher = new SubmissionPublisher<>();
//定义订阅者:消费类型是Integer
Flow.Subscriber<Integer> integerSubscriber = new Flow.Subscriber<Integer>() {
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription subscription) {
//建立订阅关系的时候会调用
//保存响应关系,需要用他来给发布者响应
this.subscription = subscription;
//请求一个数据
subscription.request(1);
}
@Override
public void onNext(Integer integer) {
//接收到数据的时候触发该方法
//接受数据,处理
System.out.println("接收到的数据:"+integer);
//处理完数据再请求一个数据:响应式的关键,调节处理压力
subscription.request(1);
//或者已经达到目标,告诉发布者不再需要数据了
// subscription.cancel();
}
@Override
public void onError(Throwable throwable) {
//出错的时候触发
throwable.printStackTrace();
//也可以告诉发布者,不再接收数据了
subscription.cancel();
}
@Override
public void onComplete() {
//发布者关闭的时候触发
System.out.println("数据处理完了");
}
};
//发布者和订阅者建立关系
integerSubmissionPublisher.subscribe(integerSubscriber);
//生产数据,并发布
int data = 11;
integerSubmissionPublisher.submit(data);
// integerSubmissionPublisher.submit(22);
// integerSubmissionPublisher.submit(33);
//数据发布结束后,关闭发布者
//正式环境应该放在finally里面,或者用try-resource确保关闭
integerSubmissionPublisher.close();
//主线程延迟关闭,由于当前的测试环境避免主线程在发布出去前结束,所以进行一定的延迟
Thread.currentThread().join(1000);
}
}
//加入处理器
public class Test {
public static void main(String[] args) throws InterruptedException {
//定义发布者,发布类型是Integer
//使用SubmissionPublisher,他实现了publish接口
SubmissionPublisher<Integer> integerSubmissionPublisher = new SubmissionPublisher<>();
//定义处理器:将数据转换为String
MyProcessor myProcessor = new MyProcessor();
//定义订阅者:消费String数据
Flow.Subscriber<String> integerSubscriber = new Flow.Subscriber<String>() {
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription subscription) {
//建立订阅关系的时候会调用
//保存响应关系,需要用他来给发布者响应
this.subscription = subscription;
//请求一个数据
subscription.request(1);
}
@Override
public void onNext(String string) {
//接收到数据的时候触发该方法
//接受数据,处理
System.out.println("订阅者 接收数据:" + string);
//处理完数据再请求一个数据:响应式的关键,调节处理压力
subscription.request(1);
//或者已经达到目标,告诉发布者不再需要数据了
// subscription.cancel();
}
@Override
public void onError(Throwable throwable) {
//出错的时候触发
throwable.printStackTrace();
//也可以告诉发布者,不再接收数据了
subscription.cancel();
}
@Override
public void onComplete() {
//发布者关闭的时候触发
System.out.println("订阅者 数据接收完毕");
}
};
//发布者 和 处理器 建立关系
integerSubmissionPublisher.subscribe(myProcessor);
//处理器 和 订阅者 建立关系
myProcessor.subscribe(integerSubscriber);
//生产数据,并发布
int data = 11;
integerSubmissionPublisher.submit(data);
integerSubmissionPublisher.submit(22);
integerSubmissionPublisher.submit(33);
//数据发布结束后,关闭发布者
//正式环境应该放在finally里面,或者用try-resource确保关闭
integerSubmissionPublisher.close();
//主线程延迟关闭,由于当前的测试环境避免主线程在发布出去前结束,所以进行一定的延迟
Thread.currentThread().join(2000);
}
}
class MyProcessor extends SubmissionPublisher<String>
implements Flow.Processor<Integer, String> {
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription subscription) {
//保存订阅关系
this.subscription = subscription;
//请求一个数据
subscription.request(1);
}
@Override
public void onNext(Integer integer) {
//接收到数据,处理
System.out.println("处理器 接收数据:" + integer);
//具体的处理逻辑:过滤掉小于零的,然后发布
if (integer >= 0) {
this.submit("处理器 处理数据、发布数据:" + integer);
}
//处理完数据再请求一个数据:响应式的关键,调节处理压力
subscription.request(1);
//或者已经达到目标,告诉发布者不再需要数据了
// subscription.cancel();
}
@Override
public void onError(Throwable throwable) {
//出错的时候触发
throwable.printStackTrace();
//也可以告诉发布者,不再接收数据了
subscription.cancel();
}
@Override
public void onComplete() {
//发布者关闭的时候触发
System.out.println("处理器 数据处理完毕");
}
}
- 运行机制
订阅者接收数据有个缓冲池,在运行onNext时才取出数据,当缓冲池满了之后发布者就不会再发布消息,而是等订阅者消费一个数据后缓冲池有空间了,在继续发布消息
三、Spring Webflux
- 概念
spring5的新型非阻塞模式,支持较高并发量 - 与MVC的区别
关系型数据库现在有r2dbc driver了,可以支持响应式了
1、异步Servlet
//同步servlet
@WebServlet(name = "testServlet", value = "/1")
public class testServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
long l = System.currentTimeMillis();
//具体业务逻辑
fun(request,response);
System.out.println(System.currentTimeMillis()-l);
}
private void fun(HttpServletRequest request, HttpServletResponse response) {
//模拟延迟
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//回应
try {
response.getWriter().append("done");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
//异步servlet
@WebServlet(name = "testServlet2", value = "/testServlet2")
public class testServlet2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
long l = System.currentTimeMillis();
//开启异步
AsyncContext asyncContext = request.startAsync();
//具体业务逻辑,使用线程池执行业务逻辑
CompletableFuture.runAsync(()->
fun(asyncContext,asyncContext.getRequest(),asyncContext.getResponse()));
System.out.println(System.currentTimeMillis()-l);
}
private void fun(AsyncContext asyncContext, ServletRequest request, ServletResponse response) {
//模拟延迟
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//回应
try {
response.getWriter().append("done");
} catch (IOException e) {
e.printStackTrace();
}
//业务处理完毕,通知结束
asyncContext.complete();
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
2、webflux开发原理
- 创建项目
- 什么是Mono
public class ReactTest {
public static void main(String[] args) {
//这是学习jdk9 响应式流时学习的订阅者
//定义订阅者:消费类型是Integer
Subscriber<Integer> integerSubscriber = new Subscriber<Integer>() {
private Subscription subscription;
@Override
public void onSubscribe(Subscription subscription) {
//建立订阅关系的时候会调用
//保存响应关系,需要用他来给发布者响应
this.subscription = subscription;
//请求一个数据
subscription.request(1);
}
@Override
public void onNext(Integer integer) {
//接收到数据的时候触发该方法
//接受数据,处理
System.out.println("接收到的数据:"+integer);
//处理完数据再请求一个数据:响应式的关键,调节处理压力
subscription.request(1);
//或者已经达到目标,告诉发布者不再需要数据了
// subscription.cancel();
}
@Override
public void onError(Throwable throwable) {
//出错的时候触发
throwable.printStackTrace();
//也可以告诉发布者,不再接收数据了
subscription.cancel();
}
@Override
public void onComplete() {
//发布者关闭的时候触发
System.out.println("数据处理完了");
}
};
//reactor=jdk8 stream + jdk9 reactive Stream
//Mono:0-1个元素
//flux:0-N个元素
String[] strings = {"1", "2", "3", "4", "5", "6"};
//jdk8的stream:相当于发布者
Flux.fromArray(strings).map(s->Integer.valueOf(s))
//jdk9 的reactive stream:订阅者
.subscribe(integerSubscriber);
}
}
- 代码
@RestController
@Slf4j
public class TestController {
//传统方式
@GetMapping("/1")
private String beforeFunction() {
log.info("start1");
String str = createStr();
log.info("end1");
return str;
}
//响应式
@GetMapping("/2")
private Mono<String> monoFunction() {
log.info("start2");
Mono<String> str = Mono.fromSupplier(()->createStr());
log.info("end2");
return str;
}
//告诉前端生产者类型,也就是流的形式
@GetMapping(value = "/3",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
private Flux<String> fluxFunction() {
Flux<String> str = Flux.fromStream(IntStream.range(0,20).mapToObj(i->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "fluxdata-->"+i;
}));
return str;
}
public String createStr() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "some string";
}
}
- 实现原理:SSE(server sent events)
//后端
@WebServlet(name = "SSEServlet", value = "/SSEServlet")
public class SSEServlet extends HttpServlet {
@Override
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().append("event:me\n");
//消息格式:data:+内容+两个回车
response.getWriter().append("data:123"+i+"\n\n");
response.getWriter().flush();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<script type="text/javascript">
//初始化,参数为url
var sse = new EventSource("sse");
sse.onmessage = function (e) {
console.log("message",e.data,e);
};
//有事件标识就可以这样输出,可以自动重连
sse.addEventListener("me",function (e) {
console.log("me event",e.data);
//也可以主动关闭连接
if (e.data == 3) {
sse.close();
}
})
</script>
</body>
</html>
3、webflux开发例子
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserRepository userRepository;
//获取所有user
@GetMapping("/")
public Flux<User> getAll() {
//数据以数组形式一下子全部返回
//使用仓库的集成功能
return userRepository.findAll();
}
@GetMapping(value = "/stream",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<User> streamGetAll() {
//流式返回数据,以SSE形式一个一个慢慢返回
return userRepository.findAll();
}
//增加一个user
@PostMapping("/")
public Mono<User> addUser(@RequestBody User user) {
//java data jpi里面修改和新增都是用save,id为空则是新增,不为空则是修改
//不允许用户修改也可以将id主动置空
// user.setId(null);
return userRepository.save(user);
}
//根据id删除用户,删除成功返回200,失败返回404
@DeleteMapping("/{id}")
public Mono<ResponseEntity<Void>> deleteById(@PathVariable("id") long id) {
//由于deleteById没有返回值,所以不用这个方法
// userRepository.deleteById(id);
//先查找是否有这个id
return userRepository.findById(id)
//如果不操作数据,只是对数据做转换,就是用map
//如果需要操作数据,并返回一个mono,就是用flatmap
//id存在,就可以删掉了
.flatMap(s->userRepository.deleteById(id)
//id存在返回mono,正确状态码
.then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK))))
//id不存在,返回错误码
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
//修改用户
//存在时返回200、修改后的数据,不存在的时候返回404
@PutMapping("/{id}")
public Mono<ResponseEntity<User>> updateById(@PathVariable("id") long id,
@RequestBody User user) {
return userRepository.findById(id)
//flatmap操作数据
.flatMap(u->{
//改变数据
u.setName(user.getName());
u.setAge(user.getAge());
//利用返回值,返回当前改变后的数据
return userRepository.save(u);
})
//map转换数据
//返回正确状态码
.map(u->new ResponseEntity<User>(u,HttpStatus.OK))
//如果为空,返回错误状态码
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
//根据用户id查找用户
@GetMapping("/getUserById/{id}")
public Mono<ResponseEntity<User>> getUserById(@PathVariable("id") long id) {
return userRepository.findById(id)
.map(u->new ResponseEntity<User>(u, HttpStatus.OK))
.defaultIfEmpty(new ResponseEntity<User>(HttpStatus.NOT_FOUND));
}
//根据年龄段查询用户
@GetMapping("/getAgeRange/{start}/{end}")
public Flux<User> getAgeRange(@PathVariable("start") int start,
@PathVariable("end") int end) {
return userRepository.findByAgeBetween(start, end);
}
//流的方式
@GetMapping(value = "/streamGetAgeRange/{start}/{end}"
,produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<User> streamGetAgeRange(@PathVariable("start") int start,
@PathVariable("end") int end) {
return userRepository.findByAgeBetween(start, end);
}
//查询大龄用户
@GetMapping("/getOld")
public Flux<User> getOld() {
return userRepository.findOldUsers();
}
//流的方式
@GetMapping(value = "/streamGetOld",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<User> streamGetOld() {
return userRepository.findOldUsers();
}
}
/**
* Integer是id的类型
* 封装了crud的各种常见方法
* 不常见的方法需要自己声明(名字需要按照一定规范),就可以自动生成方法
* 也可以自定义方法,自己写SQL语句
*/
@Repository
public interface UserRepository extends R2dbcRepository<User, Long> {
// 根据年龄段查询用户
public Flux<User> findByAgeBetween(int start,int end);
//自定义
@Query("select id,name,age from user where age>50")
Flux<User> findOldUsers();
}
4、webflux参数校验
- 方法一:用validation注解的方式来校验
关于validation的注解
捕获异常
- 方法二:自定义校验方法
controller添加检查方法
方法的具体逻辑
创建错误类
同理捕获异常
四、Spring RouterFunction
webflux(上)、routerfunction(下)的运行过程,对应servlet中的httpservletRequest,httpservletResponse时
- CRUD基本功能
//handler,相当于controller
@Component
public class UserHandler {
private final UserRepository userRepository;
public UserHandler(UserRepository repository) {
this.userRepository = repository;
}
//最好构造方法注入
// @Autowired
// UserRepository userRepository;
/**
* 获取所有user
* @param :ServerRequest
* @return :ServerResponse
*/
public Mono<ServerResponse> getAllUsers(ServerRequest request) {
//ok标识返回状态码200
return ServerResponse.ok()
//数据类型
.contentType(MediaType.APPLICATION_JSON_UTF8)
//数据包装:所有数据,数据类型
.body(userRepository.findAll(), User.class);
}
/**
* 创建user
* 使用routerFunction之后所有的请求处理都是长的一个样,输入一个ServerRequest,返回一个ServerResponse
* @param :ServerRequest
* @return :ServerResponse
*/
public Mono<ServerResponse> createUser(ServerRequest request) {
//获取用户数据
Mono<User> userMono = request.bodyToMono(User.class);
//ok标识返回状态码200
return ServerResponse.ok()
//数据类型
.contentType(MediaType.APPLICATION_JSON_UTF8)
//数据包装:所有数据,数据类型
//由于save不支持输入一个Mono所以调用saveAll
.body(userRepository.saveAll(userMono), User.class);
}
/**
* 根据id删除user
* @param :ServerRequest
* @return :ServerResponse
*/
public Mono<ServerResponse> deleteUserById(ServerRequest request) {
//获取id
String id = request.pathVariable("id");
return userRepository.findById(Long.valueOf(id))
//不为空,删除用户,返回正确状态码
//由于没有实体所以需要builde
.flatMap(u->userRepository.delete(u).then(ServerResponse.ok().build()))
//为空,返回错误状态码
.switchIfEmpty(ServerResponse.notFound().build());
}
/**
* 根据id修改user
* @param :ServerRequest
* @return :ServerResponse
*/
public Mono<ServerResponse> updateUserById(ServerRequest request) {
//获取用户信息
Mono<User> userMono = request.bodyToMono(User.class);
//获取id
long id = userMono.block().getId();
return userRepository.findById(id)
//不为空,修改用户,返回正确状态码
//由于没有实体所以需要builde
.flatMap(u-> userRepository.saveAll(userMono)
.then(ServerResponse.ok().build()))
//为空,返回错误状态码
.switchIfEmpty(ServerResponse.notFound().build());
}
}
/**
* 用于整理所有handler
* 类似于springmvc中的dispatch,总入口
*/
@Configuration
public class AllRouters {
/**
* 将userRouter加入Bean,返回实例
* 注入UserHandler,用注解注入也可以
* @return
*/
@Bean
RouterFunction<ServerResponse> userRouter(UserHandler userHandler) {
//nest:嵌套:嵌套了两个路由在这里
// 需要断言、routerfunction
return RouterFunctions.nest(
// 第一个
//使用工具类,给这个路由加上访问前缀(相当于controller上面的requestMapping),
// 只要访问的是这个地址都会触发这个路由
RequestPredicates.path("/user"),
//第二个
//使用工具类,需要一个断言、handlerFunction
//这里相当于controller方法中的mapping路径
//获取所有user
RouterFunctions.route(RequestPredicates.GET("/"),
userHandler::getAllUsers)
//创建一个user,声明输入数据的类型
.andRoute(RequestPredicates.POST("/")
.and(RequestPredicates.accept(MediaType.APPLICATION_JSON_UTF8)),
userHandler::createUser)
.andRoute(RequestPredicates.DELETE("/{id}"),
userHandler::deleteUserById)
.andRoute(RequestPredicates.PUT("/"),
userHandler::updateUserById)
);
}
}
- 参数校验
/**
* 切面处理与webflux有所不同
* 其他的如错误类,逻辑检查都是一样的
*/
@Component
//由于有很多个异常处理的handl,所以需要将我们这个的优先级调高,不然可能不会被执行,数值越低优先级越高
@Order(-2)
public class UserExceptionHandler implements WebExceptionHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
ServerHttpResponse response = exchange.getResponse();
//设置响应头
response.setStatusCode(HttpStatus.BAD_REQUEST);
//设置返回数据类型:纯文本
response.getHeaders().setContentType(MediaType.TEXT_PLAIN);
//异常信息
String errormsg = toStr(ex);
//数据封装:包装、装换,得到字符串
DataBuffer db = response.bufferFactory().wrap(errormsg.getBytes());
return response.writeWith(Mono.just(db));
}
private String toStr(Throwable ex) {
//已知异常
if (ex instanceof UserNameException){
//强转一下,方便拿信息
UserNameException userNameException = (UserNameException) ex;
return userNameException.getFieldName()+" 出错了:"+userNameException.getFieldValue();
}else{
//未知异常:打印堆栈信息,方便定为错误信息
ex.printStackTrace();
return ex.toString();
}
}
}
五、WebClient框架
框架图
视频讲的有点略,看不下去了,暂时跳过
p39