欢迎光临
我们一直在努力

[JavaEE] Spring Web MVC入门

目录

1. 什么是Spring Web MVC

2. 学习Spring MVC

2.1 项目准备

2.2 建立连接

2.2.1 @RequestMapping注解介绍

2.3 Postman介绍

2.4 请求

2.4.1 @RequestMapping注解接收特定方法的请求

2.4.2 请求传递参数

2.4.3 请求参数传递对象

2.4.4 后端参数的重命名

2.4.5 传递数组

2.4.6 传递集合

2.4.7 传递JSON数据

2.4.8 获取URL中参数@PathVariable

2.4.9 上传文件@RequestPart

2.4.10 获取Cookie/Session

获取Cookie

获取Session

2.4.11 获取Header

2.5 响应

2.5.1 返回静态页面 和 返回数据@ResponseBody

2.5.2 返回HTML代码片段

2.5.3 返回Json

2.5.4 设置状态码

2.5.5 设置Header

2.5.6 设置Content-Type

3. 综合性练习

3.1 加法计算器

3.1.1 约定前后端交互接口

3.1.2 服务器代码

3.1.3 前端代码

3.1.4 运行测试

3.2 用户登录

3.2.1 约定前后端交互接口

3.2.2 服务器代码

3.2.3 前端代码

3.2.4 运行测试

3.3 留言板

3.3.1 约定前后端交互接口

3.3.2 服务器代码

lombok介绍:

引入依赖的新方式:

服务器代码实现:

3.3.3 前端代码

3.3.4 运行测试

3.4 图书管理系统

3.4.1 生成前端页面的工具

3.4.2 约定前后端交互接口

3.4.3 服务器代码

3.4.4 前端代码

3.4.5 运行测试

4. 应用分层

5. 总结


1. 什么是Spring Web MVC

        Spring Web MVC 是基于 servlet API 构建的原始Web框架,从一开始就包含在spring框架里面,但通常称为:Spring MVC,也就是说 Spring Web MVC 最开始就包含在Spring FrameWork框架里面。

servlet:就是用来处理客户端请求,处理业务逻辑,生成响应内容的。

而Tomcat这样的servlet容器是用来接收客户端请求,管理servlet的执行,并将响应返回给客户端。

Spring Web MVC则是一个Web框架,它是基于servlet实现的一个框架,可以更加高效的处理请求。

MVC:

MVC是一种软件设计的架构模式,把软件系统分为模型,视图,控制器三层:

View(视图)指在应用程序中专门用来与浏览器进行交互,展示数据的资源.。

Model(模型)是应用程序的主体部分,用来处理程序中数据逻辑的部分。

Controller(控制器)可以理解为一个分发器,用来决定对于视图发来的请求,需要用哪一个模型 来处理,以及处理完后需要跳回到哪一个视图。即用来连接视图和模型。

Spring MVC就是一个采用MVC架构的Web框架。

Spring Boot 项目里面包含了Spring MVC框架,使用这个框架来实现Web功能,对Spring MVC框架做出了一些修改,如图所示:

Spring Boot框架相当于集成了很多功能,类似于12306,Spring Framework相当于火车,而Spring MVC相当于售票站。

2. 学习Spring MVC

2.1 项目准备

我们学习Spring MVC 框架主要学习三个方面:

建立连接:将浏览器和服务器建立连接,使得用户能通过浏览器访问服务器的资源。

请求:用户请求时候会有一些参数,服务器能够获取到这些参数。

响应:服务器处理好请求后得到响应,然后将响应发送给浏览器。

我们创建Spring Boot项目时候,框选了Spring Web就相当于创建了Spring MVC项目:

我们需要自己在idea创建一个Spring Boot项目。

2.2 建立连接

先创建一个UserController类,来实现浏览器和服务器的连接,代码如下:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/user")
@RestController
public class UserController {
@RequestMapping("/m1")
public String m1() {
return "m1";
}
}

然后启动程序,访问http://127.0.0.1:8080/user/m1这个网址,就可以在网页上看到返回的数据了。

2.2.1 @RequestMapping注解介绍

        这个注解相当于路径映射,可以修饰类,也可以修饰方法,URL中有资源路径,这个资源路径就是类路径+方法路径。

使用注意:

  • 我们在使用这个注解时候,路径里面的/可以省略,Spring会帮我们检查。
  • 注解里面可以是多层路径,第一个路径可以省略/,后面的不能省略。
  • 类路径和方法路径可以重复,需要保证资源路径是唯一的,但是平常使用都不会重名。
  •         这时候用户输入一个URL后,服务器能通过这个URL访问到具体的某个类某个方法,但是Spring是如果找到这个具体的类或者方法呢?

            Spring通过扫描所有加了@RestController注解的类,查看里面有没有@RequestMapping注解,从而找到对应的方法,没有加@RestController注解的类不会扫描。

      @RequestMapping注解既可以接收get请求又可以接收post请求。

    浏览器一般发送的都是get请求,我们如何去构造一个post请求呢?

    2.3 Postman介绍

            我们可以使用Postman这个软件来构造不同方法的请求,测试客户端和服务器的连接,我们使用postman发送的请求给客户端 和 浏览器给客户端发送的请求是一样的效果的。

    下载地址:https://www.postman.com/downloads/,可能需要注册一个账号,才能使用。

    创建请求:

    我们就可以使用Postman这个软件来构造请求,发送给服务器,比如我们构造一个post请求,使用fiddler抓包看是不是一个post请求:

    我们使用postman构造一个post请求,发送给服务器,返回了m1。

    使用fiddler抓包到请求的信息:

    POST http://127.0.0.1:8080/user/m1 HTTP/1.1 User-Agent: PostmanRuntime/7.49.1 Accept: */* Postman-Token: 47008be0-e53d-4db9-8fe4-10c06dfff6fa Host: 127.0.0.1:8080 Accept-Encoding: gzip, deflate, br Connection: keep-alive Content-Length: 0

    显示是一个post请求。

    2.4 请求

    2.4.1 @RequestMapping注解接收特定方法的请求

    此时的@RuquestMapping注解既支持get请求,也支持post请求,其它请求也支持。

    如果想让@RequestMapping注解只支持某一种请求,该如何去编写代码:

    @RequestMapping("/user")
    @RestController
    public class UserController {
    //get和post请求都支持
    @RequestMapping("/m1")
    public String m1() {
    return "m1";
    }
    //只支持get请请求
    @RequestMapping(value = "/m2",method = RequestMethod.GET)
    public String m2() {
    return "m2";
    }
    //只支持post请求
    @RequestMapping(value = "/m3",method = RequestMethod.POST)
    public String m3() {
    return "m3";
    }
    }

    这里访问http://127.0.0.1:8080/user/m2,这个路径只支持get请求,其他请求会报405错误。

    访问http://127.0.0.1:8080/user/m3,这个路径只支持post请求。

    上面的代码还有另外一种写法:

    //只支持get请求
    @GetMapping("/m4")
    public String m4() {
    return "m4";
    }
    //只支持post请求
    @PostMapping("/m5")
    public String m5() {
    return "m5";
    }

            这里的m4只支持get请求,m5只支持post请求,使用这种写法的话注解里面的字符串可以相同,这里就会根据资源路径+请求的方法来区分那个方法。

    这里的method也可以支持多个请求:

    //支持get和post请求
    @RequestMapping(value = "/m6",method = {RequestMethod.POST, RequestMethod.GET})
    public String m6() {
    return "m6";
    }

    get和post请求都支持。

    2.4.2 请求传递参数

    我们使用postman构造请求时候,是可以进行传参的。

    参数介绍:

    Params是传普通参数,通过查询字符串的键值对的形式传递。

    Headers:请求头,键值对的形式可以自己设置。

    Body:正文,可以自己添加内容。

    from-data是通常用于提交图片或者文件。

    x-www-form-urlencoded是对应的form表单。

    raw可以上传text,json,html,xml等。

    我们创建一个新的类来演示RequestController类:

    @RequestMapping("/request")
    @RestController
    public class RequestController {
    //传递一个参数
    @RequestMapping("/r1")
    public String r1(String keyword) {
    return "接受的参数:" + keyword;
    }
    //传递多个参数
    @RequestMapping("r2")
    public String r2(String userName, String password) {
    return "账号:" + userName + " " + "密码:" + password;
    }
    }

    传递一个参数的请求:

    传递多个参数的请求,通过查询字符串传递数据:

    传递多个请求,通过正文来传递数据:

    fiddler抓包结果:

    POST http://127.0.0.1:8080/request/r2 HTTP/1.1 User-Agent: PostmanRuntime/7.49.1 Accept: */* Postman-Token: 6eabf928-ee24-493a-a2c6-bc60c40e135b Host: 127.0.0.1:8080 Accept-Encoding: gzip, deflate, br Connection: keep-alive Content-Type: application/x-www-form-urlencoded Content-Length: 30

    userName=admin&password=123456

    这里面的用户名和密码数据就通过正文传递了。

    传递参数是整型和基本类型呢?

    //传递包装类型整型
    @RequestMapping("/r3")
    public String r3(Integer number) {
    return "接收参数:" + number;
    }
    //传递基本类型整型
    @RequestMapping("/r4")
    public String r4(int number) {
    return "接收参数:" + number;
    }

    传递包装类型和基本类型都能传递成功,显示200。

    传递包装类型和基本类型,传递字母,会显示400,客户端错误:

            如果包装类型不传参数,此时就是null,但是基本类型不传参数,就会报错,500,服务器错误,基本类型不能为null。

    2.4.3 请求参数传递对象

    如果传的参数过多的情况,我们接收时候可以使用一个对象来接收:

    定义一个UserInfo类:

    package com.sias.demo.controller;

    public class UserInfo {
    private String name;
    private int gender;
    private int age;

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public int getGender() {
    return gender;
    }

    public void setGender(int gender) {
    this.gender = gender;
    }

    public int getAge() {
    return age;
    }

    public void setAge(int age) {
    this.age = age;
    }

    @Override
    public String toString() {
    return "UserInfo{" +
    "name='" + name + '\\'' +
    ", gender=" + gender +
    ", age=" + age +
    '}';
    }
    }

    利用对象来接收请求的参数:

    //使用对象来接收参数
    @RequestMapping("r5")
    public String r5(UserInfo userInfo) {
    return "接收参数userInfo = " + userInfo.toString();
    }

    2.4.4 后端参数的重命名

    required属性是设置该参数是否必须传参。

    //接收到参数a,把a的值给keyword,使用这个注解的参数,默认是必须传参,否则报错
    @RequestMapping("r6")
    public String r6(@RequestParam("a") String keyword) {
    return "接收参数:" + keyword;
    }
    //设置非必须传参
    @RequestMapping("r7")
    public String r7(@RequestParam(value = "a", required = false) String keyword) {
    return "接收参数:" + keyword;
    }

    通过postman发送请求:

    强制传参:

    非强制传参:

    2.4.5 传递数组

    //传递数组
    @RequestMapping("r8")
    public String r8(String[] arr) {
    return "接收参数:" + Arrays.toString(arr);
    }

    利用postman构造请求:

    方法一:

    方法二:

    2.4.6 传递集合

            默认情况下,传递同一个参数的多个值,是会放在数组里面的,如果要存到集合里面,需要使用@RequestParam注解来绑定参数关系。        

    错误代码:

    //传递集合
    @RequestMapping("r9")
    public String r9(List<Integer> list) {
    return "接收参数:" + list;
    }

    利用上面代码接受请求,结果会报错,500:

    利用@RequestParam注解绑定关系后:

    //传递集合
    @RequestMapping("r9")
    public String r9(@RequestParam List<Integer> list) {
    return "接收参数:" + list;
    }

    这里跟数组一样,两种传参方式都可以:

    2.4.7 传递JSON数据

    JSON概念:

            JSON就是一种数据格式,有着自己的规则来组织数据,本质是一个字符串,是客户端和服务端进行交互的一种数据格式。

    JSON语法:

    • 数据在键值对中
    • 数据之间用 , 分隔
    • 对象用 { } 表示
    • 数组用 [ ] 表示
    • 值可以为对象,也可以为数组,数组中可以包含多个对象。

    示例代码:

    {
    "squadName": "Super hero squad",
    "homeTown": "Metro City",
    "formed": 2016,
    "secretBase": "Super tower",
    "active": true,
    "members": [{
    "name": "Molecule Man",
    "age": 29,
    "secretIdentity": "Dan Jukes",
    "powers": ["Radiation resistance", "Turning tiny", "Radiation
    blast"]
    }, {
    "name": "Madame Uppercut",
    "age": 39,
    "secretIdentity": "Jane Wilson",
    "powers": ["Million tonne punch", "Damage resistance", "Superhuman
    reflexes"]
    }, {
    "name": "Eternal Flame",
    "age": 1000000,
    "secretIdentity": "Unknown",
    "powers": ["Immortality", "Heat Immunity", "Inferno",
    "Teleportation", "Interdimensional travel"]
    }]
    }

    压缩版本:

    { "squadName": "Super hero squad", "homeTown": "Metro City", "formed": 2016, "secretBase": "Super tower", "active": true, "members": [{ "name": "Molecule Man", "age": 29, "secretIdentity": "Dan Jukes", "powers": ["Radiation resistance", "Turning tiny", "Radiation blast"] }, { "name": "Madame Uppercut", "age": 39, "secretIdentity": "Jane Wilson", "powers": ["Million tonne punch", "Damage resistance", "Superhuman reflexes"] }, { "name": "Eternal Flame", "age": 1000000, "secretIdentity": "Unknown", "powers": ["Immortality", "Heat Immunity", "Inferno", "Teleportation", "Interdimensional travel"] }] }

    我们可以通过一些格式化工具将上面的压缩版本格式化:

    https://www.json.cn/

    JSON字符串和Java对象转换:

    常用的转换工具:gson,fastjson,jackson。

    spring boot项目自带的是jackson,我们可以直接去使用。

    我们在test目录下,创建一个JsonTest类,来演示Jackson的使用:

    package com.sias.demo;

    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.sias.demo.controller.UserInfo;
    import org.junit.jupiter.api.Test;

    public class JsonTest {
    //对象转Json
    @Test
    void testObjectToJson() throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    //构造一个对象
    UserInfo userInfo = new UserInfo();
    userInfo.setName("张三");
    userInfo.setGender(1);
    userInfo.setAge(18);
    //对象转JSON
    String s = objectMapper.writeValueAsString(userInfo);
    System.out.println(s);
    }

    //Json转对象
    @Test
    void testJsonToObject() throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    //构造一个Json
    String s = "{\\"name\\":\\"张三\\",\\"gender\\":1,\\"age\\":18}";
    //Json转对象
    UserInfo userInfo = objectMapper.readValue(s, UserInfo.class);
    System.out.println(userInfo.toString());
    }
    }

            我们使用了Jackson工具里面的ObjectMapper类的writeValueAsString()方法和readValue()方法来进行转换的。

            这里我使用了一个@Test注解,这个注解类似于main方法,可以运行测试方法,这个注解在一个类里面可以有多个,一个类里面只能有一个main方法。

            我们使用writeValueAsString()方法将对象转换成JSON,利用readValue()方法将JSON转换为对象,该方法参数里面要标明转成那个对象。

    JSON的优点:

  • 语法简单,便于阅读理解,能够快速的进行数据转换。
  • JSON可以被多种语言解析,跨平台性好。
  • JSON的数据格式消耗的带宽小,可以提高传输效率。
  • JSON数据格式是纯文本格式,不是包含什么可执行代码,比较安全。
  • JSON数据格式容易拓展,嵌套数组和对象等复杂类型。
  • 传递JSON数据:

            这里使用了@RequestBody注解,作用是将请求中的JSON数据转换成参数里面的对象类型,代码如下:

    //传递JSON数据
    @RequestMapping("r10")
    public String r10(@RequestBody UserInfo userInfo) {
    return "接收的参数:" + userInfo.toString();
    }

    利用postman构造请求,包含JSON的数据:

    注意事项:

            我们在test里面的代码,将JSON对象转为对象使用的是readValue()方法,这个代码工作原理是:利用对象对应的类里面的空的构造方法,先new一个对象,然后使用这个对象赋值类里面的属性,如果我们在类里面重写了带有参数的构造方法,没有空的构造方法,此时下面代码执行就会报错,不能实现JSON转对象的过程。

    //Json转对象
    @Test
    void testJsonToObject() throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    //构造一个Json
    String s = "{\\"name\\":\\"张三\\",\\"gender\\":1,\\"age\\":18}";
    //Json转对象
    UserInfo userInfo = objectMapper.readValue(s, UserInfo.class);
    System.out.println(userInfo.toString());
    }

    2.4.8 获取URL中参数@PathVariable

    我们打开一个今日头条网站,然后随便找两篇文章,我们可以看到这两篇文章的URL如下:

    https://www.toutiao.com/trending/7576914740409139263/?rank=1&log_from=b9be4e9e1f37d_1764233947866

    https://www.toutiao.com/trending/7576991131788247049/?rank=2&log_from=d5f8b1470f48c8_1764233959884

            我们可以看到这两篇文章的资源路径中存在两个不同的数字,我们可以猜测后端接收到请求,是根据这两个数组来查询是哪一篇文章,把内容返回给浏览器。

    那后端如何获取到URL里面的参数呢?

            这里要用到@PathVariable注解来获取,这里URL的名字要和获取的参数名字相同,参数名字也可以修改成其他名字,这里的获取的参数要使用{ }来包含。

    //获取URL的参数
    @RequestMapping("/article/{articleId}")
    public String r11(@PathVariable Integer articleId) {
    return "获取到的参数:" + articleId;
    }
    //获取多个参数
    @RequestMapping("/article/{type}/{articleId}")
    public String r12(@PathVariable Integer articleId, @PathVariable("type") String articleType) {
    return "获取到的参数:" + articleId + " " + articleType;
    }

    使用postman构造请求:

    2.4.9 上传文件@RequestPart

    这里使用MultipartFile类型来接收文件,使用transferTo将获取到的文件保存在本地路径下。

    @RequestPart注解还可以用来对参数重命名的。

    //获取文件并存储本地
    @RequestMapping("/r13")
    public String r13(@RequestPart("file11") MultipartFile file) throws IOException {
    System.out.println(file.getOriginalFilename());
    //将文件保存到本地
    file.transferTo(new File("D:\\\\" + file.getOriginalFilename()));
    return "文件获取成功,并上传到本地";
    }

    2.4.10 获取Cookie/Session

    什么是Cookie:

            cookie是客户端浏览器端存储的小型文本数据(键值对的形式),负责被动接收服务器发来的数据,然后存储到本地,等到发出请求时,自动将cookie中的数据存储在请求中。

    http协议是一种无状态协议,就是说客户端和服务器的这次会话和下一次会话之间没有关系。

            但是在实际开发中,会存在一些需要关联起来不同的会话。比如我们在网页登录自己的账号时候,下次进入网页就是直接登录好的页面。

    会话:就是客户端和服务器建立的一次连接,就是一个会话,相当于打电话也是一次会话。

            当一个客户端访问服务器时候,而这时候服务器就会生成一个身份令牌sessionId,将这个sessionId存储在响应里面的set-cookie字段中传给客户端,此时客户端的cookie就保存sessionId这个属性,下次请求时候将sessionId存储在请求的cookie字段中,发送给服务器,服务器就可以通过sessionId来确定是哪个客户端访问的服务器。

    什么是Session:

            在实际开发中,一个服务器需要处理很多个客户端的请求,也就会产生很多会话,那么服务器是如何区分那个请求是那个客户端发送来的呢?

            这里服务器就会对每个客户端创建一个session对象,里面包括sessionId,以及客户端的信息等内容,session本质是一个哈希表,sessionId是key值,value是客户端的信息。

            当一个客户端首次访问服务器,此时服务器就会创建一个session对象,生成sessionId,然后将这个sessionId放在响应的set-cookie字段中,返回给客户端,客户端的cookie中就会存储sessionId这个数据,等到下一次客户端发送请求,就会将这个sessionId数据放在请求的cookie字段发送给服务器,服务器就可以根据sessionId找到对应的客户端数据。

    图示:

    Cookie和Session的区别:

    Cookie是客户端保存用户信息的一种机制,Session是服务器保护用户信息的一种机制。

    Cookie和Session之间通过SessionId关联起来的。

    Cookie和Session经常一起配合使用,但是也可以不一起使用,Cookie也可以保存一些其他数据在客户端,Session中的SessionId不一定非要Cookie/set-Cookie字段来传递,URL也可以。

    获取Cookie

    传统获取方法:

    //传统获取Cookie的方法
    @RequestMapping("r14")
    public String r14(HttpServletRequest request, HttpServletResponse response) {
    Cookie[] cookies = request.getCookies();
    if(cookies != null) {
    for(Cookie cookie : cookies) {
    System.out.println(cookie.getName() + ":" + cookie.getValue());
    }
    }
    return "获取cookie成功";
    }

    HttpServletRequest类是http请求的所有信息,HttpServletResponse类包含http响应的所有内容。

            我们通过postman来设置cookie的值,然后发送请求,通过上面代码获取到cookie的键值对,然后返回字符串获取cookie成功。

    简单获取cookie方法:

    //通过注解拿到cookie的值
    @RequestMapping("/r15")
    public String r15(@CookieValue("name") String name) {
    return "从cookie中获取的值" + name;
    }

    这里使用了@CookieValue注解来获取cookie的值。

    获取Session

    存储session:

    //存储session
    @RequestMapping("/setSession")
    public String setSession(HttpServletRequest request) {
    //先从request里面获取sessionId,然后根据sessionId找到session对象。
    //这里默认为true,表示没有session就创建个session。
    //参数为false,表示没有session就返回null。
    HttpSession session = request.getSession(true);
    //设置用户名和年龄
    session.setAttribute("name", "lisi");
    session.setAttribute("age", 20);
    return "存储session成功";
    }

    这里使用HttpServletRequest来获取到session,使用setAttribute方法来设置session的值。

    获取session:

    //获取session
    @RequestMapping("/getSession1")
    public String getSession1(HttpServletRequest request) {
    HttpSession session = request.getSession(false);
    if(session == null) {
    return "没有session对象";
    }else {
    String userName = (String)session.getAttribute("name");
    return "用户名是:" + userName;
    }
    }

    //获取session
    @RequestMapping("/getSession2")
    public String getSession2(HttpSession session) {
    String userName = (String)session.getAttribute("name");
    return "用户名是:" + userName;
    }

    //获取session
    @RequestMapping("/getSession3")
    public String getSession3(@SessionAttribute("name") String userName) {
    return "用户名是:" + userName;
    }

    有三种方法获取session,第三种使用了@SessionAttribute注解来获取session的值。

    2.4.11 获取Header

    //获取Header
    @RequestMapping("/getHeader1")
    public String getHeader1(HttpServletRequest request) {
    String userAgent = request.getHeader("User-Agent");
    return userAgent;
    }

    //获取Header
    @RequestMapping("/getHeader2")
    public String getHeader2(@RequestHeader("User-Agent") String userAgent) {
    return userAgent;
    }

            这里获取Header中的键值对的属性值,存在两种方法,第二种使用了@RequestHeader注解来获取。

    2.5 响应

    2.5.1 返回静态页面 和 返回数据@ResponseBody

    @RequestMapping("/resp")
    @Controller
    public class RespController {
    //返回静态页面
    @RequestMapping("/r1")
    public String returnPage() {
    return "/index.html";
    }
    //返回数据
    @RequestMapping("/r2")
    @ResponseBody
    public String returnData() {
    return "返回的数据";
    }

    }

    这里我们使用了@Controller注解。

    @Controller注解 和 @RestController注解的区别:

    @RestController = @Controller + @ResponseBody,被@Controller注解修饰的类,会被扫描的。程序运行时候默认扫描的是启动类所在的目录下的类。

    @Conteoller注解是该类返回的都是页面时候使用的。

    @RestController注解是该类返回的都是数据的时候使用的。

    如果一个类既有页面又有数据,我们可以使用@Controller注解,然后返回数据的方法上使用@ResponseBody注解,该注解既是类注解,也是方法注解。

    2.5.2 返回HTML代码片段

    //返回HTML代码片段
    @RequestMapping("r3")
    @ResponseBody
    public String returnHtml() {
    return "<h1>一级标题<h1>";
    }

    //返回HTML代码
    @RequestMapping(value = "r4", produces = "text/plain")
    @ResponseBody
    public String returnText() {
    return "<h1>一级标题<h1>";
    }

            我们如果返回的是HTML的代码,浏览器会自动转换为HTML格式,我们可以通过设置Content-Type键值对的值来改变在浏览器显示的类型,使用produces参数来修改。

    2.5.3 返回Json

    //返回Json
    @RequestMapping("/r5")
    @ResponseBody
    public UserInfo returnJson() {
    UserInfo userInfo = new UserInfo("zhangsan",1,2);
    return userInfo;
    }

    这里的返回结果为对象。

    2.5.4 设置状态码

    //设置响应的状态码
    @RequestMapping("/r6")
    @ResponseBody
    public UserInfo setStatus(HttpServletResponse response) {
    response.setStatus(404);
    UserInfo userInfo = new UserInfo("zhangsan",1,2);
    return userInfo;
    }

    此时看到状态码被设置成404。

    2.5.5 设置Header

    //设置Header
    @RequestMapping("/r7")
    @ResponseBody
    public String setHeader(HttpServletResponse response) {
    response.setHeader("one","one");
    return "设置header成功";
    }

    此时看到请求头多了一个设置的键值对。

    2.5.6 设置Content-Type

    //返回HTML代码
    @RequestMapping(value = "/r4", produces = "text/plain")
    @ResponseBody
    public String returnText() {
    return "<h1>一级标题<h1>";
    }

    使用produces属性来设置。

    3. 综合性练习

    3.1 加法计算器

    3.1.1 约定前后端交互接口

    接口定义:

    请求参数:

    响应数据:

     

    3.1.2 服务器代码

    后端接收到两个参数后,返回数据显示在新页面里。

    @RequestMapping("/calc")
    @RestController
    public class CalcController {
    @RequestMapping("/sum")
    public String sum(Integer num1, Integer num2) {
    if(num1 == null || num2 == null) {
    return "输入的参数不合法,请重新输入";
    }
    Integer sum = num1 + num2;
    return "<h1>计算机计算结果:" + sum + "</h1>";
    }
    }

    3.1.3 前端代码

            这里使用的from标签来发送请求,action是请求的URL,method是请求的方式,input标签的name属性是key的值,输入的是value的值,type=“submit”是提交按钮,点击后将from标签域内的数据提交给服务器。

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    </head>
    <body>
    <!– 使用from标签来向服务器发送请求 –>
    <form action="calc/sum" method="post">
    <h1>计算器</h1>
    数字1:<input name="num1" type="text"><br>
    数字2:<input name="num2" type="text"><br>
    <input type="submit" value=" 点击相加 ">
    </form>
    </body>

    </html>

    3.1.4 运行测试

    3.2 用户登录

    3.2.1 约定前后端交互接口

    用户登录接口:

    请求参数:

    响应数据:

    Content-Type : text/html

    响应内容:

    true //账号密码验证成功

    false //账号密码验证失败

    第二个接口:

    请求数据:

    请求路径:/user/getLoginUser

    请求方式:GET

    接口描述:查询当前登录的用户

    请求参数:无

    响应数据:

    Content-Type:text.html

    响应内容:zhangsan

    3.2.2 服务器代码

            服务器代码实现了两个接口,一个是登录页面,接收账号和密码,并且将账号的信息存储在session中,如果账号密码正确返回true,其余情况返回false。

    一个是显示登录用户名字的页面,根据session查询登录用户的名字返回给前端。

    @RequestMapping("/userL")
    @RestController
    public class UserLoginController {
    @RequestMapping("/login")
    public boolean login(String userName, String password, HttpSession session) {
    //参数的校验
    //方法1:
    // if(userName == null || userName == "" || password == null || password == "") {
    // return false;
    // }
    //方法2:
    if(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {
    return false;
    }
    //这里使用固定的账号密码
    if("admin".equals(userName) && "123456".equals(password)) {
    //将账号信息存储在session中
    session.setAttribute("userName", userName);
    return true;
    }
    return false;
    }
    @RequestMapping("/getLoginUser")
    public String getLoginUser(HttpSession session) {
    String userName = (String)session.getAttribute("userName");
    return userName;
    }
    }

    3.2.3 前端代码

    登录界面:

            这里使用了Ajax技术,作用是可以保证页面不重新加载页面,跟服务器交换数据,更新局部页面,下面使用的jQuery语法来创建Ajax请求。

    <body>
    <h1>用户登录</h1>
    用户名:<input name="userName" type="text" id="userName"><br>
    密码:<input name="password" type="password" id="password"><br>
    <input type="button" value="登录" onclick="login()">

    <script src="js/jquery-3.7.1.min.js"></script>
    <script>
    // 创建login函数
    function login() {
    // 使用jQuery来创建Ajax请求
    $.ajax({
    url: "/userL/login",
    type: "post",
    //发送到服务端的数据
    data: {
    userName: $("#userName").val(),
    password: $("#password").val()
    },
    //http请求成功
    success: function(response) {
    if(response) {
    //跳转到新的页面
    location.href = "index.html";
    }else {
    alert("密码错误,请重新输入");
    }
    }
    });
    }
    </script>
    </body>

    用户登录成功首页:

    这里也是用了Ajax技术来实现的。

    <body>
    登录人: <span id="loginUser"></span>

    <script src="js/jquery-3.7.1.min.js"></script>
    <script>
    $.ajax({
    url: "userL/getLoginUser",
    type: "get",
    success: function(userName) {
    $("#loginUser").text(userName);
    }
    });
    </script>
    </body>

    3.2.4 运行测试

    3.3 留言板

    3.3.1 约定前后端交互接口

    获取全部留言:

    请求:GET 

    url:message/getList

    响应:JSON格式

    [

            {

            "from": "A",

            "to": "B",

            "message": "C"

            },{     

                    "from": "D",

                    "to": "E",

                    "message": "F"

            }

    ]

    发表新的留言:

    请求: POST JSON格式

    url:/message/publish

    {

            "from": "A",

            "to": "B",

            "message": "C"

    }

    响应:JSON格式

    {

            ok: 1

    }

    3.3.2 服务器代码

    lombok介绍:

    Lombok是一个工具库,通过添加注解的方式,来简化Java的开发。

    我们使用时候,需要导入依赖:

    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>

    lombok库里面包含很多注解:

    @Data注解是直接包含很多的方法,在编译时候就会加到类里面的,此时注解会消失。

    下面这个类,可以换成添加注解的形式:

    public class UserInfo {
    private String name;
    private int gender;
    private int age;

    public UserInfo() {

    }
    public UserInfo(String name, int gender, int age) {
    this.name = name;
    this.gender = gender;
    this.age = age;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public int getGender() {
    return gender;
    }

    public void setGender(int gender) {
    this.gender = gender;
    }

    public int getAge() {
    return age;
    }

    public void setAge(int age) {
    this.age = age;
    }

    @Override
    public String toString() {
    return "UserInfo{" +
    "name='" + name + '\\'' +
    ", gender=" + gender +
    ", age=" + age +
    '}';
    }
    }

    下面代码在编译之后,就会包含上面的各种方法。

    @Data
    public class UserInfo {
    private String name;
    private int gender;
    private int age;
    }

    引入依赖的新方式:

    使用EditStarter插件。

    我们就可以通过上面页面添加依赖了。

    服务器代码实现:

    先定义一个类来表示一条留言对象。

    @Data
    public class MessageInfo {
    private String from;
    private String to;
    private String message;
    }

            设置两个接口,一个是接收留言数据,显示在下面,另一个是保存留言数据在顺序表中,保证留言信息不会刷新掉。

    @RequestMapping("/message")
    @RestController
    public class MessageController {
    private List<MessageInfo> messageInfoList = new ArrayList<>();

    @PostMapping("/publish")
    public String publish(@RequestBody MessageInfo messageInfo) {
    //使用@RequestBody注解,将JSON字符串转成对象
    //检查参数规范
    if(!StringUtils.hasLength(messageInfo.getFrom()) ||
    !StringUtils.hasLength(messageInfo.getTo()) ||
    !StringUtils.hasLength(messageInfo.getMessage())) {
    return "{\\"ok\\": 0}";
    }
    //存储留言
    messageInfoList.add(messageInfo);
    return "{\\"ok\\": 1}";
    }

    @GetMapping("/getList")
    public List<MessageInfo> getList() {
    return messageInfoList;
    }
    }

    3.3.3 前端代码

            前端使用JSON.stringify()方法将对象转为JSON字符串,使用JSON.parse()方法将JSON字符串转为对象。

    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>留言板</title>
    <style>
    .container {
    width: 350px;
    height: 300px;
    margin: 0 auto;
    /* border: 1px black solid; */
    text-align: center;
    }

    .grey {
    color: grey;
    }

    .container .row {
    width: 350px;
    height: 40px;

    display: flex;
    justify-content: space-between;
    align-items: center;
    }

    .container .row input {
    width: 260px;
    height: 30px;
    }

    #submit {
    width: 350px;
    height: 40px;
    background-color: orange;
    color: white;
    border: none;
    margin: 10px;
    border-radius: 5px;
    font-size: 20px;
    }
    </style>
    </head>

    <body>
    <div class="container">
    <h1>留言板</h1>
    <p class="grey">输入后点击提交, 会将信息显示下方空白处</p>
    <div class="row">
    <span>谁:</span> <input type="text" name="" id="from">
    </div>
    <div class="row">
    <span>对谁:</span> <input type="text" name="" id="to">
    </div>
    <div class="row">
    <span>说什么:</span> <input type="text" name="" id="say">
    </div>
    <input type="button" value="提交" id="submit" onclick="submit()">
    <!– <div>A 对 B 说: hello</div> –>
    </div>

    <script src="js/jquery-3.7.1.min.js"></script>
    <script>
    load();
    //显示所有留言
    function load() {
    $.ajax({
    type: "get",
    url: "/message/getList",
    success: function(messages) {
    if(messages != null && messages.length > 0) {
    var finalHtml = "";
    for(var m of messages) {
    finalHtml += "<div>" + m.from + "对" + m.to + "说" + m.message + "</div>";
    }
    $(".container").append(finalHtml);
    }
    }
    });
    }
    function submit(){
    //1. 获取留言的内容
    var from = $('#from').val();
    var to = $('#to').val();
    var say = $('#say').val();
    if (from== '' || to == '' || say == '') {
    return;
    }
    var data = {
    from: from,
    to: to,
    message: say
    }
    //使用Ajax向后端发送请求
    $.ajax({
    type: "post",
    url: "/message/publish",
    contentType: "application/json",
    //stringify方法将对象转为JSON字符串
    data: JSON.stringify(data),

    success: function (result) {
    //使用parse方法将JSON字符串转成对象
    var str = JSON.parse(result);
    if(str.ok == 1) {
    //成功
    var divE = "<div>" + from + "对" + to + "说" + say + "</div>";
    //将内容添加到留言板下面
    $(".container").append(divE);
    //清空输入内容
    $("#from").val("");
    $("#to").val("");
    $("#say").val("");
    }else {
    //失败
    alert("留言发布失败");
    }
    }
    });
    }
    </script>
    </body>
    </html>

    3.3.4 运行测试

    3.4 图书管理系统

    3.4.1 生成前端页面的工具

    Bootstarp网站,这里面可以有很多模版和样式,可以直接使用。

    https://www.bootcss.com/

    3.4.2 约定前后端交互接口

    登录接口:

    url    /user/login

    post

    请求参数:name=admin&password=admin

    响应:

    true //账号密码正确

    false //账号密码错误

    图书列表展示:

    url   /book/getList

    post

    请求参数:无

    响应:

    返回图书列表

    [

            {               

                    "id": 1,

                    "bookName": "活着",

                    "author": "余华",

                    "count": 270,

                    "price": 20,

                    "publish": "北京⽂艺出版社",17 "status": 1,

                    "statusCN": "可借阅"

            },

            ……..

    ]

    字段说明:

    3.4.3 服务器代码

    用户登录接口:

    @RequestMapping("/user")
    @RestController
    public class UserController {
    @RequestMapping("/login")
    public Boolean login(String name, String password, HttpSession session) {
    if(!StringUtils.hasLength(name) && !StringUtils.hasLength(password)) {
    return false;
    }
    //未使用数据库
    if("admin".equals(name) && "123456".equals(password)) {
    session.setAttribute("userName", name);
    return true;
    }
    return false;
    }
    }

    图书列表接口:

    @RequestMapping("/book")
    @RestController
    public class BookController {
    @RequestMapping("/getList")
    public List<BookInfo> getList() {
    //这里没有使用数据库,先mock数据(自己构造虚假数据)
    List<BookInfo> bookInfos = mockData();
    //这里返回给前端的图书借阅状态设置成文字
    for(BookInfo book : bookInfos) {
    if(book.getStatus() == 1) {
    book.setStatusCN("可借阅");
    }else {
    book.setStatusCN("不可借阅");
    }
    }
    return bookInfos;
    }

    //该方法从mock的数据中查数据
    public List<BookInfo> mockData() {
    List<BookInfo> bookInfos = new ArrayList<>();
    for (int i = 1; i <= 15 ; i++) {
    BookInfo book = new BookInfo();
    book.setId(i);
    book.setBookName("图书" + i);
    book.setAuthor("作者" + i);
    book.setCount(new Random().nextInt(80) + 21); //[20,100]
    book.setPrice(new BigDecimal(new Random().nextInt(80) + 21));
    book.setPublish("图书出版社" + i);
    book.setStatus(i % 5 == 0 ? 2 : 1); //1-可借阅 2-不可借阅
    bookInfos.add(book);
    }
    return bookInfos;
    }
    }

    3.4.4 前端代码

    登录页:

    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="css/bootstrap.min.css">
    <link rel="stylesheet" href="css/login.css">
    <script type="text/javascript" src="js/jquery.min.js"></script>
    </head>

    <body>
    <div class="container-login">
    <div class="container-pic">
    <img src="pic/computer.png" width="350px">
    </div>
    <div class="login-dialog">
    <h3>登陆</h3>
    <div class="row">
    <span>用户名</span>
    <input type="text" name="userName" id="userName" class="form-control">
    </div>
    <div class="row">
    <span>密码</span>
    <input type="password" name="password" id="password" class="form-control">
    </div>
    <div class="row">
    <button type="button" class="btn btn-info btn-lg" onclick="login()">登录</button>
    </div>
    </div>
    </div>
    <script src="js/jquery.min.js"></script>
    <script>
    function login() {
    $.ajax({
    type: "post",
    url: "/user/login",
    data: {
    name: $("#userName").val(),
    password: $("#password").val()
    },
    success: function (result) {
    if(result) {
    //账号密码正确
    location.href = "book_list.html";
    }else {
    alert("账号或密码错误,请重新输入");
    }
    }
    });
    }
    </script>
    </body>

    </html>

    图书列表页:

    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图书列表展示</title>
    <link rel="stylesheet" href="css/bootstrap.min.css">

    <link rel="stylesheet" href="css/list.css">
    <script type="text/javascript" src="js/jquery.min.js"></script>
    <script type="text/javascript" src="js/bootstrap.min.js"></script>
    <script src="js/jq-paginator.js"></script>

    </head>

    <body>
    <div class="bookContainer">
    <h2>图书列表展示</h2>
    <div class="navbar-justify-between">
    <div>
    <button class="btn btn-outline-info" type="button" onclick="location.href='book_add.html'">添加图书</button>
    <button class="btn btn-outline-info" type="button" onclick="batchDelete()">批量删除</button>
    </div>
    </div>

    <table>
    <thead>
    <tr>
    <td>选择</td>
    <td class="width100">图书ID</td>
    <td>书名</td>
    <td>作者</td>
    <td>数量</td>
    <td>定价</td>
    <td>出版社</td>
    <td>状态</td>
    <td class="width200">操作</td>
    </tr>
    </thead>
    <tbody>
    </tbody>
    </table>

    <div class="demo">
    <ul id="pageContainer" class="pagination justify-content-center"></ul>
    </div>
    <script>

    getBookList();
    function getBookList() {
    $.ajax({
    type: "post",
    url: "/book/getList",
    success: function (books) {
    var finalHtml = "";
    for(var book of books) {
    finalHtml += '<tr>';
    finalHtml += '<td><input type="checkbox" name="selectBook" value="' + book.id + '" id="selectBook" class="book-select"></td>';
    finalHtml += '<td>' + book.id + '</td>';
    finalHtml += '<td>' + book.bookName + '</td>';
    finalHtml += '<td>' + book.author + '</td>';
    finalHtml += '<td>' + book.count + '</td>';
    finalHtml += '<td>' + book.price + '</td>';
    finalHtml += '<td>' + book.publish + '</td>';
    finalHtml += '<td>' + book.statusCN + '</td>';
    finalHtml += '<td><div class="op">';
    finalHtml += '<a href="book_update.html?bookId=' + book.id + '">修改</a>';
    finalHtml += '<a href="javascript:void(0)" onclick="deleteBook(' + book.id + ')">删除</a>';
    finalHtml += '</div></td></tr>';
    }
    $("tbody").html(finalHtml);
    }
    });
    }

    //翻页信息
    $("#pageContainer").jqPaginator({
    totalCounts: 100, //总记录数
    pageSize: 10, //每页的个数
    visiblePages: 5, //可视页数
    currentPage: 1, //当前页码
    first: '<li class="page-item"><a class="page-link">首页</a></li>',
    prev: '<li class="page-item"><a class="page-link" href="javascript:void(0);">上一页<\\/a><\\/li>',
    next: '<li class="page-item"><a class="page-link" href="javascript:void(0);">下一页<\\/a><\\/li>',
    last: '<li class="page-item"><a class="page-link" href="javascript:void(0);">最后一页<\\/a><\\/li>',
    page: '<li class="page-item"><a class="page-link" href="javascript:void(0);">{{page}}<\\/a><\\/li>',
    //页面初始化和页码点击时都会执行
    onPageChange: function (page, type) {
    console.log("第"+page+"页, 类型:"+type);
    }
    });
    function deleteBook(id) {
    var isDelete = confirm("确认删除?");
    if (isDelete) {
    //删除图书
    alert("删除成功");
    }
    }
    function batchDelete() {
    var isDelete = confirm("确认批量删除?");
    if (isDelete) {
    //获取复选框的id
    var ids = [];
    $("input:checkbox[name='selectBook']:checked").each(function () {
    ids.push($(this).val());
    });
    console.log(ids);
    alert("批量删除成功");
    }
    }

    </script>
    </div>
    </body>

    </html>

    3.4.5 运行测试

    4. 应用分层

    为什么要应用分层?

            我们前面写的代码创建了很多类,这些类混在一起就会难以阅读和区分,不方便别人阅读自己的代码,此时就需要一种规范来将代码进行分类,类似于spring MVC框架将系统分为Model(模型),View(视图),Controller(控制器)三个层次,将用户视图和业务处理完全分开,通过控制器来进行连接。

    现在主流的开发模式是“前后端分离”,前端和后端各自进行开发,又在Java后端产生了新的三层架构:

    表现层:用来接受请求和返回响应的,表现层一般使用Controller来表示。

    业务逻辑层:用来处理具体的业务的,业务逻辑层一般使用Sevice来表示。

    数据层:用来管理和存储数据的,数据层一般使用Dao来表示。

    实体类可以使用 model , entity , pojo 这些来表示。

    MVC模式和三层架构的联系:

    MVC模型主要是把数据和视图分离,将数据展示和数据处理分开,通过控制器来建立联系。

    三层架构是从不同的维度处理数据的高内聚和低耦合,将交互逻辑,业务处理,数据库管理分开。

    5. 总结

    本文介绍了很多注解:

    @RequestMapping:路由映射,用来表示URL的。

    @RequestParam:对前端传过来的参数名字进行重命名。

    @RequestBody:接收前端传来的JSON类型的参数的。

    @PathVariable:接受路径中的参数的。

    @RequestPart:接收前端传来的文件的。

    @ResponseBody:返回数据的。

    @Controller:定义一个控制器,spring框架启动时加载该类,把这个对象交给spring管理,默认该类返回视图。

    @RestController:@ResponseBody + @Controller 返回数据。

    @CookieValue:从Cookie中获取值。

    @SessionAttribute:从Session中获取值。

    @RequestHeader:从Header中获取值。

            Spring MVC内置的HttpServletRequest 和 HttpServletResponse两个对象,可以获得请求和响应中的所有参数,Cookie和session可以从HttpServletRequest中获取,可以使用HttpServletResponse来设置状态码。

    赞(0)
    未经允许不得转载:171主机测评 » [JavaEE] Spring Web MVC入门
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址