【学习笔记】WebFlux响应式开发

教学视频:响应式开发教学视频
代码资源: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

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值