Spring MVC入门
做web开发,首先对web有一个大体的了解,下图第一部分是网页的组成成分,我们看到的各式各样的网页就是浏览器根据这些文件渲染出来的,第二部分http协议,是应用层的协议,用来传输上方的这些文件,再下面是传输层和网络层的相关内容,具体的自行学习。
HTTP协议
首先了解http协议,这里提供Mozilla的官方文档进行学习
- HyperText Transfer Protocol
- 用于传输HTML等内容的应用层协议
- 规定了浏览器和服务器之间如何通信,以及通信时的数据格式。
在这里截取一段了解一下
我们在浏览器实际操作来查看一下,以上面推荐的网站为例,用谷歌浏览器打开,在页面上右键-检查,然后刷新一下,就可以看到右边出现了一堆请求,我们随便点一个就可以看到它的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的缓存。
#ThymeleafProperties
spring.thymeleaf.cache=false
那么上面这句话是什么意思呢,ctrl+n我们搜索一下thymeleaf的配置类也就是ThymeleafProperties。我截取了前面的一段代码,可以看到这个类使用了@ConfigurationProperties注解配置了前缀,我们使用这个前缀加上.cache实际是修改了ThymeleafProperties的cache属性,在我截取的代码最后一行可以看到。
@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文档。
# ServerProperties
server.port=8080
案例一
这个案例我们演示如何获取请求和返回响应,具体每一行的意思我在注释里写好。在http方法里面,传入了servlet的request和response,我们对这两个对象进行操作。
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<String> 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("<h1>牛客网</h1>");//利用writer写入html数据
writer.write("<h2>牛客网</h2>");
writer.write("<h3>牛客网</h3>");
} 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参数
这两种方法根据不同的需求,使用相应的方法。
//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。
<!DOCTYPE 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的参数就可以接收到。
//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的值。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Teacher</title>
</head>
<body>
<p th:text="${name}"></p>
<p th:text="${age}"></p>
</body>
</html>
再在controller里写相应方法处理。返回数据类型设为ModelAndView,也就是把模型和视图作为一个对象返回,为了简单直接给了一个固定数据,并设置了模板的路径,demo路径下的view.html,这里就不用写后缀了,在浏览器中访问相应地址就行。
//响应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文件。
还有另外一种方式,效果一样。这个方法是从容器中获取模型的引用,修改模型的值,并直接返回视图的路径。对比来看第二种方式简洁一点。
案例四
还有一种比较常见的情况,我们需要给浏览器返回JSON数据,通常是在一个异步请求中,实际上在前面我们已经使用过了。异步请求是什么呢,比如注册bilibili,当我输入一个昵称时,网页会提醒我昵称被占用,但是网页其它部分时没有刷新变动。
//响应JSON数据(异步请求)
//java对象-> JSPON字符串 ->Js对象
@RequestMapping(path = "/emp",method = RequestMethod.GET)
@ResponseBody
public Map<String,Object> getEmp(){
Map<String,Object> emp=new HashMap<>();
emp.put("name","张三");
emp.put("age","18");
emp.put("salary",8000);
return emp;
}
返回JSON实际就是使用了前面提到的@ResponseBody注解。
同样,也可以返回一组数据。
@RequestMapping(path = "/emps",method = RequestMethod.GET)
@ResponseBody
public List<Map<String,Object>> getEmps(){
List<Map<String,Object>> list=new ArrayList<>();
Map<String,Object> 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;
}