avatar

目录
牛客网后端项目实战(四):Spring MVC入门

Spring MVC入门

做web开发,首先对web有一个大体的了解,下图第一部分是网页的组成成分,我们看到的各式各样的网页就是浏览器根据这些文件渲染出来的,第二部分http协议,是应用层的协议,用来传输上方的这些文件,再下面是传输层和网络层的相关内容,具体的自行学习。

HTTP协议

首先了解http协议,这里提供Mozilla的官方文档进行学习

  • HyperText Transfer Protocol
  • 用于传输HTML等内容的应用层协议
  • 规定了浏览器和服务器之间如何通信,以及通信时的数据格式。

在这里截取一段了解一下

HTTP 流

当客户端想要和服务端进行信息交互时(服务端是指最终服务器,或者是一个中间代理),过程表现为下面几步:

  1. 打开一个TCP连接:TCP连接被用来发送一条或多条请求,以及接受响应消息。客户端可能打开一条新的连接,或重用一个已经存在的连接,或者也可能开几个新的TCP连接连向服务端。

  2. 发送一个HTTP报文:HTTP报文(在HTTP/2之前)是语义可读的。在HTTP/2中,这些简单的消息被封装在了帧中,这使得报文不能被直接读取,但是原理仍是相同的。

    html
    1
    2
    3
    GET / HTTP/1.1
    Host: developer.mozilla.org
    Accept-Language: fr
  3. 读取服务端返回的报文信息:

    html
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    HTTP/1.1 200 OK
    Date: Sat, 09 Oct 2010 14:28:02 GMT
    Server: Apache
    Last-Modified: Tue, 01 Dec 2009 20:18:22 GMT
    ETag: "51142bc1-7449-479b075b2891b"
    Accept-Ranges: bytes
    Content-Length: 29769
    Content-Type: text/html

    html... (here comes the 29769 bytes of the requested web page)
  4. 关闭连接或者为后续请求重用连接。

当HTTP流水线启动时,后续请求都可以不用等待第一个请求的成功响应就被发送。然而HTTP流水线已被证明很难在现有的网络中实现,因为现有网络中有很多老旧的软件与现代版本的软件共存。因此,HTTP流水线已被在有多请求下表现得更稳健的HTTP/2的帧所取代。

我们在浏览器实际操作来查看一下,以上面推荐的网站为例,用谷歌浏览器打开,在页面上右键-检查,然后刷新一下,就可以看到右边出现了一堆请求,我们随便点一个就可以看到它的header,cookies之类的信息。那为什么有这么多请求呢,因为在浏览器解析的过程中,会发现html里面引用了其它资源,那么就会再请求这个资源文件。

Spring MVC

  • 三层架构
    • 表现层、业务层、数据访问层
  • MVC
    • Model:模型层
    • View:视图层
    • Controller:控制层
  • 核心组件
    • 前端控制器:DispatcherServlet

要注意,服务端三层架构和MVC三层不是一一对应关系。可以从上图看到大致的关系,Controller接收请求中的数据,调用业务层去处理,得到的数据封装到model传给视图层,视图层生成html返回给浏览器。在这个过程中,有一个核心组件,前端控制器DispatcherServlet。从spring最新版本的文档找了一个图来解释,大体结构如下。

上面这个图不够具体,在老版的文档找到这个图,可以结合来看,大体流程就是请求来了由前端控制器解析,handlerMapping根据我们的注解找到相应的controller去处理,返回model给前端控制器,前端控制器再调用试图解析器处理,然后然会响应。解释得不够准确,深入了解建议spring官网查看文档

Thymeleaf模板引擎

在前面我们写了简单的示例,都是返回了字符串给浏览器,没有返回过网页,我们需要借用一个工具-模板引擎,那么比较流行的就是thymeleaf。

  • 模板引擎
    • 生成动态的HTML
  • Thymeleaf
    • 倡导自然模板,即以HTML文件为模板
  • 常用语法
    • 标准表达式,判断与循环,模板的布局

案例演示

我们首先在application.properties里面关掉thymeleaf的缓存。

java
1
2
#ThymeleafProperties
spring.thymeleaf.cache=false

那么上面这句话是什么意思呢,ctrl+n我们搜索一下thymeleaf的配置类也就是ThymeleafProperties。我截取了前面的一段代码,可以看到这个类使用了@ConfigurationProperties注解配置了前缀,我们使用这个前缀加上.cache实际是修改了ThymeleafProperties的cache属性,在我截取的代码最后一行可以看到。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ConfigurationProperties(
prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING;
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
private boolean checkTemplate = true;
private boolean checkTemplateLocation = true;
private String prefix = "classpath:/templates/";
private String suffix = ".html";
private String mode = "HTML";
private Charset encoding;
private boolean cache;

同理,我们修改服务器启动端口,也可以查看对应的配置类,还可以修改其它参数,这里不做演示,可以查阅spring文档。

java
1
2
# ServerProperties
server.port=8080

案例一

这个案例我们演示如何获取请求和返回响应,具体每一行的意思我在注释里写好。在http方法里面,传入了servlet的request和response,我们对这两个对象进行操作。

java
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
package com.neu.langsam.nowcoder.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

@Controller
@RequestMapping("/alpha")
public class AlphaController {

@RequestMapping("http")
public void http(HttpServletRequest request, HttpServletResponse response){
//获取请求数据
System.out.println(request.getMethod());//打印请求方法
System.out.println(request.getServletPath());//打印请求路径
Enumeration enumeration=request.getHeaderNames();//古老的迭代器获取请求头
while (enumeration.hasMoreElements()){
String name=enumeration.nextElement();
String value=request.getHeader(name);
System.out.println(name+":"+value);
}

System.out.println(request.getParameter("code"));//打印请求参数

//返回响应数据
response.setContentType("text/html;charset=utf-8");//设置返回数据类型
try (
PrintWriter writer= response.getWriter();
)
{
writer.write("

牛客网

"
);//利用writer写入html数据

writer.write("

牛客网

"
);

writer.write("

牛客网

"
);

} catch (IOException e) {
e.printStackTrace();
}
}
}

启动项目,在浏览器中访问,可以看到页面显示了我们写入的html,同样可以打开检查,查看我们这一次的请求情况,查看idea控制台,可以看到输出了我们想要获取的数据,还可以在链接后接问号传参,传参的不做演示。

http://localhost:8080/alpha/http?code=test

案例二

案例一演示了比较基础的方法去操作,但实际上已经封装好了简单的方法,接下来演示如何更加简单的去处理。现在假设查询学生数据,学生比较多采用分页显示,那么就需要传入current参数告诉后端现在需要第几页以及limit参数规定每页多少条数据。那么怎么处理这样的请求呢。

GET 方法

  • 首先还是使用注解进行配置
    • @RequestMapping配置了访问路径和访问方法,这里我们规定了只接受GET方法,相应的还有POST,DELETE等方法自行了解。GET方法通常用来查询数据,POST通常添加或者修改数据。
    • @ResponseBody注解表示将方法返回的java对象转换为json或者xml格式的数据,直接写入响应的body区而不走视图解析器。
  • 在第一个getStudents方法中,我们使用了@RequestParam注解规定了请求参数,对于current参数,我们规定了不是必须的,默认值设置为1。我们就可以在地址栏传入参数,/students?current=2&limit=20
  • 在第二个getStudent方法中,使用了@PathVariable注解,同时路径中使用了{id},这样就可以直接/student/112传入id参数

这两种方法根据不同的需求,使用相应的方法。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//GET请求
// /students?current=1&limit=20
@RequestMapping(path = "/students",method = RequestMethod.GET)
@ResponseBody
public String getStudents(
@RequestParam(name = "current",required = false,defaultValue = "1") int current,
@RequestParam(name = "limit",required = false,defaultValue = "10") int limit){
System.out.println(current+" "+limit);
return "some students";
}

// /student/123
@RequestMapping(path = "/student/{id}",method = RequestMethod.GET)
@ResponseBody
public String getStudent(@PathVariable("id") int id){
System.out.println(id);
return "a student";
}

POST方法

上面说到post方法通常用于增加或者修改数据,那么我们就需要有一个表单去填写数据。在static目录下新建html目录,在html目录下新建一个html文件。这是一个简单的网页,两个输入框加上一个提交按钮,规定了提交的路径是/alpha/student。

html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>增加学生title>
head>
<body>
<form method="post" action="/alpha/student">
<p>
姓名:<input type="text" name="name">

p>
<p>
年龄:<input type="text" name="age">
p>
<p>
<input type="submit" value="保存">
p>
form>
body>
html>

然后我们在controller里写处理这个访问路径的方法,这里需要注意一点,前面我们写了/student/{id}路径,比我们这里访问的/student多了一层,所以要另外写一个处理/student的方法。要获取浏览器提交的参数,我们只需要传入和浏览器表单里每一项一样名字的参数就行,spring会帮我们自动匹配。比如html里input年龄的参数名我们规定为age,那么处理请求时传入一个名为age的参数就可以接收到。

java
1
2
3
4
5
6
7
//POST请求
@RequestMapping(path = "/student",method = RequestMethod.POST)
@ResponseBody
public String saveStudent(String name,int age){
System.out.println(name+"+"+age);
return "success";
}

案例三

演示了这么多,thymeleaf模板引擎怎么用呢。首先创建一个模板,在templates下创建demo目录,然后创建一个view.html,那可能会问和前面的静态html有啥区别呢。在html标签里使用xmlns规定了模板的来源,在p标签里使用thymeleaf的语法,意思是让页面的文字等于我们传入的那么和age的值。

html
1
2
3
4
5
6
7
8
9
10
11
html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Teachertitle>
head>
<body>
<p th:text="${name}">p>
<p th:text="${age}">p>
body>
html>

再在controller里写相应方法处理。返回数据类型设为ModelAndView,也就是把模型和视图作为一个对象返回,为了简单直接给了一个固定数据,并设置了模板的路径,demo路径下的view.html,这里就不用写后缀了,在浏览器中访问相应地址就行。

java
1
2
3
4
5
6
7
8
9
//响应HTML数据
@RequestMapping(path = "/teacher",method = RequestMethod.GET)
public ModelAndView getTeacher(){
ModelAndView modelAndView=new ModelAndView();
modelAndView.addObject("name","张三");//添加模型
modelAndView.addObject("age","18");
modelAndView.setViewName("/demo/view");//设置视图
return modelAndView;
}

同样,我们在检查里查看这次请求,可以看到响应的内容是一个html文件。

还有另外一种方式,效果一样。这个方法是从容器中获取模型的引用,修改模型的值,并直接返回视图的路径。对比来看第二种方式简洁一点。

java
1
2
3
4
5
6
@RequestMapping(path = "/school",method = RequestMethod.GET)
public String getSchool(Model model){
model.addAttribute("name","东北大学");
model.addAttribute("age",97);
return "/demo/view";
}

案例四

还有一种比较常见的情况,我们需要给浏览器返回JSON数据,通常是在一个异步请求中,实际上在前面我们已经使用过了。异步请求是什么呢,比如注册bilibili,当我输入一个昵称时,网页会提醒我昵称被占用,但是网页其它部分时没有刷新变动。

java
1
2
3
4
5
6
7
8
9
10
11
//响应JSON数据(异步请求)
//java对象-> JSPON字符串 ->Js对象
@RequestMapping(path = "/emp",method = RequestMethod.GET)
@ResponseBody
public Map getEmp(){
Map emp=new HashMap<>();
emp.put("name","张三");
emp.put("age","18");
emp.put("salary",8000);
return emp;
}

返回JSON实际就是使用了前面提到的@ResponseBody注解。

同样,也可以返回一组数据。

java
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
@RequestMapping(path = "/emps",method = RequestMethod.GET)
@ResponseBody
public List> getEmps(){
List> list=new ArrayList<>();
Map emp=new HashMap<>();
emp.put("name","张三");
emp.put("age","18");
emp.put("salary",1000);
list.add(emp);
emp=new HashMap<>();
emp.put("name","李四");
emp.put("age","18");
emp.put("salary",2000);
list.add(emp);
emp=new HashMap<>();
emp.put("name","王五");
emp.put("age","18");
emp.put("salary",3000);
list.add(emp);
emp=new HashMap<>();
emp.put("name","帅哥");
emp.put("age","18");
emp.put("salary",4000);
list.add(emp);
return list;
}

文章作者: langsam
文章链接: https://langsam1998.github.io/2020/03/09/20200307-%E7%89%9B%E5%AE%A2%E7%BD%91%E5%90%8E%E7%AB%AF%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98%EF%BC%88%E5%9B%9B%EF%BC%89%EF%BC%9ASpring-MVC%E5%85%A5%E9%97%A8/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 平儿的博客
打赏
  • 微信
    微信
  • 支付寶
    支付寶

评论