• 52、基于函数式方式开发 Spring WebFlux 应用


    ★ Spring WebFlux的两种开发方式

    1. 采用类似于Spring MVC的注解的方式来开发。
       此时开发时感觉Spring MVC差异不大,但底层依然是反应式API。
    
    2. 使用函数式编程来开发
    
    • 1
    • 2
    • 3
    • 4

    ★ 使用函数式方式开发Web Flux

    使用函数式开发WebFlux时需要开发两个组件:

    ▲ Handler:作用:该处理器组件相当于控制器,它负责处理客户端的请求、并对客户端生成响应。
    
           该Handler组件的每个方法都只带一个ServerRequest参数(不是Servlet API)——代表客户端请求对象,
    
           且每个方法的返回值类型都是Mono,代表作为服务器响应的消息发布者。
           
           mono 代表一个消息发布者
    
    ▲ Router:作用:该组件通过函数式的编程方式来定义URL与Handler处理方法之间的映射关系。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    ★ WebFlux通过ServerRequest获取请求数据的两种方式:

    这两种方式并不是可以自由选择的,而是根据数据的来源不同,需要采用对应的获取策略。
    
     - 对于以请求体提交的数据,通常会通过formData()(表单数据)或bodyToFlux()或bodyToMono()(RESTful)方法来获取,
    
       由于这种方式都需要通过网络IO读取数据,可能会造成阻塞,
    
       因此它们都采用了订阅-发布的异步方式,这三个方法的返回值都是Mono或Flux(消息发布者)。
    
     - 对于URL中的数据(包括传统请求参数和路径参数),由于它们只要直接解析URL字符串即可读取数据,
    
       不会造成阻塞,因此没有采用订阅-发布的异步方式。直接用pathVariable()或queryParam()方法即可读取数据。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    ★ Handler方法的返回值

    Handler作用: 该处理器组件相当于控制器,它负责处理客户端的请求、并对客户端生成响应。

    Handler处理方法的返回值类型是Mono,
    调用ServerResponse的ok()(相当于将响应状态码设为200)、
    contentType()方法返回ServerResponse.BodyBuilder对象。
    有了ServerResponse.BodyBuilder对象之后,根据响应类型不同,
    可调用如下两个方法来生成Mono作为返回值:
    
    ▲ render(String name, Map model):使用模板引擎来生成响应,
    
       其中第一个参数代表逻辑视图名,第二个参数代表传给模板的model数据。render()方法还有其他重载形式,功能类似。
    
    ▲ body(P publisher, Class elementClass):直接设置响应体类生成响应,同样用于生成RESTful响应。
    
       body()方法还有其他重载形式,功能类似。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ★ 使用Router定义URL与Handler方法的对应关系

    Router作用: 该组件通过函数式的编程方式来定义URL与Handler处理方法之间的映射关系。

     ▲ Router就是容器中RouterFunctions类型的Bean。
        ——通常来说,就是使用@Configuration修饰的配置类来配置该Bean即可。
    
     return RouterFunctions
       // 定义映射地址和处理器方法之间的对应关系
       .route(RequestPredicates.POST("/login")
          .and(RequestPredicates.accept(MediaType.TEXT_HTML)), handler::login)
       .andRoute(RequestPredicates.GET("/viewBook/{id}")
          .and(RequestPredicates.accept(MediaType.TEXT_HTML)), handler::viewBook);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    代码演示:

    同个请求,演示跟 spring mvc 不同的实现方法。

    请求的数据是简单的url数据,就是前端传来的数据(id)是写在url 的。

    总结:通过添加 Handler 类,相当于之前的controller ,然后创建一个 Router 配置类,通过在配置类 配置 Router Bean 这个bean,来实现对客户端请求来的URL 与 Handler处理方法之间的映射关系。最终响应回json格式的数据或者 html 页面。

    Handler:该处理器组件相当于控制器,它负责处理客户端的请求、并对客户端生成响应,这个类就是handler组件

    现在弄一个 Handler 类,用来处理客户端的请求,是一个处理数据的类,相当于controller

    这个方法是生成 RESTful 响应的,就是 Json 响应
    在这里插入图片描述
    这个方法是生成 HTML 响应的
    在这里插入图片描述

    Router:作用:该组件通过函数式的编程方式来定义URL与Handler处理方法之间的映射关系。

    配置 Router Bean ,负责完成请求 URL 和 Handler 处理方法之间的映射。

    设置方法的请求路径是 “/viewBookabc/{id}” ,走这个路径就会访问这个 handler::viewBook 方法。
    而 handler::viewBooks 是 lambda 中的方法引用 ,会找到 BookHandler 类中的 viewBook 方法

    bean 方法里面的参数是 BookHandler,所以可以用 lambda 的方法引用功能 来引用该类的viewBook方法。

    负责完成 【请求URL】 和 【Handler处理方法】 之间的映射。

    Handler处理方法:就是 BookHandler 的 viewBook 方法。
    在这里插入图片描述

    返回响应给html的页面
    在这里插入图片描述

    这个bean在项目启动的时候就会被加载。
    在这里插入图片描述

    调用方法看看流程:
    访问方法,就会走 BookHandler 的 这个方法。
    在这里插入图片描述

    测试结果:

    在这里插入图片描述

    完整代码:

    BookHandler

    // Handler:该处理器组件相当于控制器,它负责处理客户端的请求、并对客户端生成响应,这个类就是handler组件
    @Component
    public class BookHandler
    {
        private BookService bookService;
    
        //有参构造器完成依赖注入
        public BookHandler(BookService bookService)
        {
            this.bookService = bookService;
        }
    
    
        // Handler:该处理器组件相当于控制器,它负责处理客户端的请求、并对客户端生成响应,这个类就是handler组件
    
        //这个方法是生成 RESTful 响应的
        public Mono<ServerResponse> viewBook(ServerRequest request)
        {
            //如果请求参数是通过 URL 字符串即可解析,可用 pathVariable()或queryParam()方法获取参数
            Integer id = Integer.parseInt(request.pathVariable("id"));
            Book book = bookService.getBook(id);
            //ok()  表示服务器响应正常
            Mono<ServerResponse> body = ServerResponse.ok()
                    //选择生成 JSON 响应类型
                    .contentType(MediaType.APPLICATION_JSON)
                    //如果要生成 JSON 响应,直接用 body 方法
                    //参数1:代表数据发布者(Publisher),参数2:指定 Mono 中每个数据项的类型
                    //Mono 的 justOrEmpty 将单个及可能为null的数据包装成 Mono
                    //如果是设计良好的应用(就是底层数据库的访问也是用 reactor api ,
                    // 这样此处从数据库返回的数据就是 Mono 或者 Flux,根本不需要包装)
                    .body(Mono.justOrEmpty(book), Book.class);
            return body;
        }
    
        //这个方法是生成 HTML 响应的
        public Mono<ServerResponse> viewBookHtml(ServerRequest request)
        {
            //如果请求参数是通过 URL 字符串即可解析,可用 pathVariable()或queryParam()方法获取参数
            Integer id = Integer.parseInt(request.pathVariable("id"));
            Book book = bookService.getBook(id);
            //ok()  表示服务器响应正常
            Mono<ServerResponse> render = ServerResponse.ok()
                    //选择生成 HTML 响应类型
                    .contentType(MediaType.TEXT_HTML)
                    //参数1:逻辑视图名   参数2:相当于 spring mvc 的 model,用于向视图页面传输数据
                    .render("viewBook", Map.of("book", book));
            return render;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    RouterConfig

    package cn.ljh.FunctionalFlux.router;
    
    import cn.ljh.FunctionalFlux.handler.BookHandler;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.MediaType;
    import org.springframework.web.reactive.function.server.RequestPredicates;
    import org.springframework.web.reactive.function.server.RouterFunction;
    import org.springframework.web.reactive.function.server.RouterFunctions;
    import org.springframework.web.reactive.function.server.ServerResponse;
    
    @Configuration //配置类
    public class RouterConfig
    {
        //配置 Router Bean ,负责完成请求 URL 和 Handler 处理方法之间的映射。
    
        @Bean
        public RouterFunction<ServerResponse> routerFunctions(BookHandler handler)
        {
            //MediaType.APPLICATION_JSON 设置响应类型 ,  handler::viewBooks  是 lambda 中的方法引用
            RouterFunction<ServerResponse> route =
                    RouterFunctions
                            //这里就映射到 BookHandler 类里面的 viewBook 方法,/viewBookabc/{id}这个是我们这边给的访问路径
                            .route(RequestPredicates.GET("/viewBookabc/{id}")
                                    .and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), handler::viewBook)
    
                            //这里就映射到 BookHandler 类里面的 viewBookHtml 方法,/viewBookHtml/{id}这个是我们这边给的访问路径
                            .andRoute(RequestPredicates.GET("/viewBookHtml/{id}")
                                    .and(RequestPredicates.accept(MediaType.TEXT_HTML)), handler::viewBookHtml);
            return route;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    上面的代码演示,请求的数据是简单的url数据,就是前端传来的数据(id)是写在url 的。

    这次演示的是前端以 表单 的方式 或 restful 方式提交数据。

    演示:以 RESTful 方式提交的数据的处理

    这边接收前端传来的数据并进行处理,相当于controller
    在这里插入图片描述

    这里的bean就是处理 请求url 和 handler处理方法 之间的映射关系
    在这里插入图片描述

    测试结果:
    成功处理添加书本的方法,添加的书本的数据在postman中实现。
    在这里插入图片描述

    演示:通过表单页面提交请求

    写一个简单的表单页面
    在这里插入图片描述

    前端通过表单页面提交请求
    在这里插入图片描述

    添加 请求 URL 和 Handler 处理方法之间的映射
    在这里插入图片描述

    测试结果:

    注意:发现因为 handler处理方法那里,因为使用了map ,把源 Mono 转成新的 Mono,当时转换的结果没去用它,所以出现添加不成功的问题。

    如图:
    如果不需要使用 Mono 转换之后的结果,此时就不需要使用 map() 方法
    map() 方法就是负责将 源Mono 转换成新的 Mono
    如果只是希望用到 Mono 中的数据,此时成为消费数据,
    就是把这条消息消费掉就行,因为不需要把 Mono 的结果返回到视图页面,所以不需要用map方法进行转换。
    在这里插入图片描述

    测试成功:
    成功通过表单页面提交请求
    在这里插入图片描述

    前端注意小知识:

    在 templates 路径下的静态页面是不能直接访问的,得通过控制器的处理方法进行转发才能访问到。
    或者直接把页面放在静态资源目录(static、public),才能直接访问。
    注意:页面得是静态页面,不能有动态内容,不能是动态页面。

    在这里插入图片描述

    完整代码:

    domain
    在这里插入图片描述

    处理类:BookHandler,类似于controller

    package cn.ljh.FunctionalFlux.handler;
    
    
    import cn.ljh.FunctionalFlux.domain.Book;
    import cn.ljh.FunctionalFlux.service.BookService;
    import org.springframework.http.MediaType;
    import org.springframework.stereotype.Component;
    import org.springframework.util.MultiValueMap;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.reactive.function.server.ServerRequest;
    import org.springframework.web.reactive.function.server.ServerResponse;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    import java.time.Duration;
    import java.util.Collection;
    import java.util.Map;
    
    
    // Handler:该处理器组件相当于控制器,它负责处理客户端的请求、并对客户端生成响应,这个类就是handler组件
    @Component
    public class BookHandler
    {
        private BookService bookService;
    
        //有参构造器完成依赖注入
        public BookHandler(BookService bookService)
        {
            this.bookService = bookService;
        }
    
    
        // Handler:该处理器组件相当于控制器,它负责处理客户端的请求、并对客户端生成响应,这个类就是handler组件
    
        //这个方法是生成 RESTful 响应的 ,就是 Json 响应
        public Mono<ServerResponse> viewBook(ServerRequest request)
        {
            //如果请求参数是通过 URL 字符串即可解析,可用 pathVariable()或queryParam()方法获取参数
            Integer id = Integer.parseInt(request.pathVariable("id"));
            Book book = bookService.getBook(id);
            //ok()  表示服务器响应正常
            Mono<ServerResponse> body = ServerResponse.ok()
                    //选择生成 JSON 响应类型
                    .contentType(MediaType.APPLICATION_JSON)
                    //如果要生成 JSON 响应,直接用 body 方法
                    //参数1:代表数据发布者(Publisher),参数2:指定 Mono 中每个数据项的类型
                    //Mono 的 justOrEmpty 将单个及可能为null的数据包装成 Mono
                    //如果是设计良好的应用(就是底层数据库的访问也是用 reactor api ,
                    // 这样此处从数据库返回的数据就是 Mono 或者 Flux,根本不需要包装)
                    .body(Mono.justOrEmpty(book), Book.class);
            return body;
        }
    
        //这个方法是生成 HTML 响应的
        public Mono<ServerResponse> viewBookHtml(ServerRequest request)
        {
            //如果请求参数是通过 URL 字符串即可解析,可用 pathVariable()或queryParam()方法获取参数
            Integer id = Integer.parseInt(request.pathVariable("id"));
            Book book = bookService.getBook(id);
            //ok()  表示服务器响应正常
            Mono<ServerResponse> render = ServerResponse.ok()
                    //选择生成 HTML 响应类型
                    .contentType(MediaType.TEXT_HTML)
                    //参数1:逻辑视图名   参数2:相当于 spring mvc 的 model,用于向视图页面传输数据
                    .render("viewBook", Map.of("book", book));
            return render;
        }
    
    
        //以 RESTful 方式提交的数据的处理
        public Mono<ServerResponse> addBook(ServerRequest request)
        {
            //假设数据来自 RESTful 的 POST 请求,此时用 bodyToMono() 或 bodyToFlux() 来获取数据
            //bodyToFlux():如果请求的数据中包含多个数据,就用这个。
            //bodyToMono():如果请求的数据只有一个数据,那就用这个
            //这两个方法参数指定了 Mono 或 Flux 中数据的类型
    
            // 添加一本图书,只是一个对象,所以用.bodyToMono() ,
            // 如果是一个集合,就应该使用 .bodyToFlux()
    
            Mono<Book> bookMono = request.bodyToMono(Book.class);
            //map() 负责将 Mono 或者 Flux 中的元素,转换成新的 Mono 或 Flux 中的元素
            Mono<Book> resultMono = bookMono.map(book ->
            {
                //添加 Book 对象
                bookService.addBook(book);
                return book;
            });
            Mono<ServerResponse> body = ServerResponse.ok()
                    //选择生成 JSON 响应类型
                    .contentType(MediaType.APPLICATION_JSON)
                    //如果要生成 JSON 响应,直接用 body 方法
                    //参数1:代表数据发布者(Publisher),参数2:指定 Mono 中每个数据项的类型
                    //Mono 的 justOrEmpty 将单个及可能为null的数据包装成 Mono
                    //如果是设计良好的应用(就是底层数据库的访问也是用 reactor api ,
                    // 这样此处从数据库返回的数据就是 Mono 或者 Flux,根本不需要包装)
                    .body(resultMono, Book.class);
            return body;
        }
    
    
        //通过表单页面提交请求
        public Mono<ServerResponse> addBookHtml(ServerRequest request)
        {
            //假设数据来自 表单页面 的 POST 请求,通过 formData() 获取表单的数据
            Mono<MultiValueMap<String, String>> formData = request.formData();
    
            /*
             * 如果不需要使用 Mono 转换之后的结果,此时就不需要使用 map() 方法
             * map() 方法就是负责将 源Mono 转换成新的 Mono
             * 如果只是希望用到 Mono 中的数据,此时成为消费数据
             */
            formData.subscribe(map ->
            {
                String name = map.get("name").get(0);
                String price = map.get("price").get(0);
                String author = map.get("author").get(0);
                Book book = new Book(null, name, Double.parseDouble(price), author);
                bookService.addBook(book);
            });
            Mono<ServerResponse> render = ServerResponse.ok()
                    //选择生成 JSON 响应类型
                    .contentType(MediaType.TEXT_HTML)
                    .render("addBookResult", Map.of("tip", "添加书籍成功"));
            return render;
        }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129

    配置类:RouterConfig,添加个bean处理url和handler类中的方法的映射关系

    package cn.ljh.FunctionalFlux.router;
    
    import cn.ljh.FunctionalFlux.handler.BookHandler;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.MediaType;
    import org.springframework.web.reactive.function.server.RequestPredicates;
    import org.springframework.web.reactive.function.server.RouterFunction;
    import org.springframework.web.reactive.function.server.RouterFunctions;
    import org.springframework.web.reactive.function.server.ServerResponse;
    
    @Configuration //配置类
    public class RouterConfig
    {
        //配置 Router Bean ,负责完成请求 URL 和 Handler 处理方法之间的映射。
        @Bean
        public RouterFunction<ServerResponse> routerFunctions(BookHandler handler)
        {
            //MediaType.APPLICATION_JSON 设置响应类型 ,  handler::viewBooks  是 lambda 中的方法引用
            RouterFunction<ServerResponse> route =
                    RouterFunctions
                            //这里就映射到 BookHandler 类里面的 viewBook 方法,/viewBookabc/{id}这个是我们这边给的访问路径
                            .route(RequestPredicates.GET("/viewBookabc/{id}")
                                    .and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), handler::viewBook)
    
                            //这里就映射到 BookHandler 类里面的 viewBookHtml 方法,/viewBookHtml/{id}这个是我们这边给的访问路径
                            .andRoute(RequestPredicates.GET("/viewBookHtml/{id}")
                                    .and(RequestPredicates.accept(MediaType.TEXT_HTML)), handler::viewBookHtml)
    
                            //这里就映射到 BookHandler 类里面的 addBook 方法,/addBook 这个是我们这边给的访问路径
                            .andRoute(RequestPredicates.POST("/addBook")
                                    .and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), handler::addBook)
    
                            //这里就映射到 BookHandler 类里面的 addBookHtml 方法,/addBookHtml/{id}这个是我们这边给的访问路径
                            .andRoute(RequestPredicates.POST("/addBookHtml")
                                    .and(RequestPredicates.accept(MediaType.TEXT_HTML)), handler::addBookHtml);
            return route;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    BookService

    package cn.ljh.FunctionalFlux.service;
    
    
    import cn.ljh.FunctionalFlux.domain.Book;
    import java.util.Collection;
    
    public interface BookService
    {
        Book getBook(Integer id);
    
        Integer addBook(Book book);
    
        Collection<Book> getAllBooks();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    BookServiceImpl

    package cn.ljh.FunctionalFlux.service.impl;
    
    
    import cn.ljh.FunctionalFlux.domain.Book;
    import cn.ljh.FunctionalFlux.service.BookService;
    import org.springframework.stereotype.Service;
    
    import java.util.*;
    
    //添加这个@Service注解,springboot就可以自动扫描这个Service组件的实现类,然后把这个类部署成容器中的bean。
    @Service
    public class BookServiceImpl implements BookService
    {
        //添加一个 Map 集合,假设为数据库
        public static final Map<Integer, Book> bookDB = new LinkedHashMap<>();
    
        //创建一个自增id
        static int nextId = 4;
    
        //初始化这个数据库
        static
        {
            bookDB.put(1, new Book(1, "火影忍者", 100.0, "岸本"));
            bookDB.put(2, new Book(2, "家庭教师", 110.0, "天野明"));
            bookDB.put(3, new Book(3, "七龙珠Z", 120.0, "鸟山明"));
        }
    
    
        //查看图书
        @Override
        public Book getBook(Integer id)
        {
            Book book = bookDB.get(id);
            if (book == null){
                throw new RuntimeException("没有此图书信息!");
            }
            return book;
        }
    
        //添加图书
        @Override
        public Integer addBook(Book book)
        {
            book.setId(nextId);
            bookDB.put(nextId,book);
            //返回id,先返回在自增。
            return nextId++;
        }
    
        //查看所有的图书
        @Override
        public Collection<Book> getAllBooks()
        {
            //获取集合中的所有元素
            Collection<Book> values = bookDB.values();
            return values;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    添加图书页面:addBook.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>添加图书页面</title>
    </head>
    <body>
    <h2>添加图书页面</h2>
    
    <form method="post" action="/addBookHtml">
        书名:<input name="name"  id="name" type="text"><br>
        价格:<input name="price"  id="price" type="text"><br>
        作者:<input name="author"  id="author" type="text"><br>
        <input type="submit" value="提交"/>
        <input type="reset" value="重设"/>
    </form>
    </body>
    </html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    添加图书结果页面:addBookResult.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>添加图书结果</title>
    </head>
    <body>
    <h2>添加图书结果</h2>
    <div th:text="${tip}">
    </div>
    </body>
    </html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    根据id查询图书:viewBook.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>查看图书</title>
    </head>
    <body>
    <h2>查看图书</h2>
    <div th:text="${book.name}"></div>
    <div th:text="${book.price}"></div>
    <div th:text="${book.author}"></div>
    
    </div>
    </body>
    </html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    Vue中如何进行图像识别与人脸对比(如百度AI、腾讯AI)
    【重拾C语言】十、递归程序设计
    电子邮件营销初学者指南(三):8个成功策略
    洛谷 P1548 [NOIP1997 普及组] 棋盘问题
    虚拟现实(VR)的应用场景
    C++ | 大小端模式的概念、检测与影响
    程序员基础能力系列(2)——vscode快捷键总结
    基于Java实现的禁忌搜索算法
    springboot使用小工具:Lombok、devtools、Spring Initailizr
    [MySQL]数据库的约束与表的设计
  • 原文地址:https://blog.csdn.net/weixin_44411039/article/details/132686771