HTTP
http协议是做web开发的基础,
- HyperText Transfer Protocol(超文本传输协议)
- 用于传输HTML等内容的应用层协议
- 规定了浏览器和服务器之间如何通信,以及通信时数据的格式
手册:MND Web 文档
查看http相关手册
http概述
主要看这个协议到底规定了什么东西,重点列举以下两点:
-
http流
下图是http协议规定的浏览器和服务器通信的步骤:
HTTP/1.1以及更早的HTTP协议报文都是语义可读的。在HTTP/2中,这些报文被嵌入到了一个新的二进制结构,帧。帧允许实现很多优化,比如报文头部的压缩和复用。即使只有原始HTTP报文的一部分以HTTP/2发送出来,每条报文的语义依旧不变,客户端会重组原始HTTP/1.1请求。因此用HTTP/1.1格式来理解HTTP/2报文仍旧有效。
有 两种 HTTP报文的类型,请求与响应,每种都有其特定的格式。
第一种:浏览器向服务器发送的报文叫做请求
第二种:服务器向浏览器返回的报文叫做响应
详细手册查询见文章:HTTP查阅手册地址
其实我们通过观察浏览器插件或在具体应用过程中也会看到请求或响应相关的数据。打开浏览器的插件去看一下请求和响应的相关数据。
这里以chrom为例,右键--检查(N)--Network
刷新当前网页后,浏览器就访问了服务器,产生了通信,我们就能看到请求数据和响应数据了。如下图:
点击查看第一条数据HTTP
General内容如下:
为什么有那么多次请求呢?
浏览器与服务器之间产生通信,浏览器访问服务器,服务器返回的是一个html,浏览器对html进行解读并渲染相关的内容,因为html可能多处用到css文件或js文件或图片文件或视频文件等,当发现依赖文件时便会向服务器提出请求,服务器便会再返回相应的html依赖的文件。关键在于网页(html文件)的返回是否存在问题。
Spring MVC
我们做服务端开发是有层次的,如果不分层用一个类去写就会使耦合度很高,后期难以维护。服务端的代码分为三层。
-
服务端三层架构
表现层、业务层、数据访问层
浏览器访问服务器首先访问的是表现层,期待表现层给其返回一些数据,表现层会调用业务层去处理业务,业务层在处理业务过程中会调用数据库。最终表现层得到业务层返回的数据后经过加工处理就返回给浏览器,完成整个请求。
-
MVC
MVC是一种设计模式,这种模式的理念是将复杂的代码分为三个层次
M--Model--模型层
V--View--视图层
C--Controller--控制层
MVC主要解决的是服务端表现层的问题,当浏览器发送请求访问服务器的时候,访问Controller控制器,Controller控制器会接收请求中的数据调用业务层处理请求,处理完成后将得到的数据封装到Model中,传给试图层View,视图层利用Model中的数据生成一个html返回给浏览器。浏览器最终通过试图层得到了html。总之,Controller用于处理浏览器的请求、负责调度,View负责渲染、展现,Controller和View之间的纽带是Model。
-
Spring MVC核心组件
前端控制器:DispatcherServlet(实际上是一个类)。M、V、C都是由这个类调度的。下面为Spring MVC的底层原理图,显示了服务器(Tomcat)内,前端控制器对Controller\Model\View的调度。(图片来源:spring.io--Spring Framework Reference Documentation of 4.3.26.RELEASE --Web MVC framework)
总结要掌握的两点:
1.理解MVC模式解决的是哪个层次的问题、是如何解决的;
2.理解Spring MVC核心的组件是什么、是如何调度MVC三层的。
Thymeleaf
给浏览器返回动态网页,需要另外的工具去支持--模板引擎。目前最流行的模板引擎为Thymeleaf。
-
模板引擎
生成动态的HTML
Thymeleaf
倡导自然模板,即以HTML文件为模板。
有的模板引擎以jsp为模板,那不懂JavaScript只懂html的人就很尴尬,Thymeleaf纯html就很友好。常用语法
无论用什么样的模板引擎,重点学的是三方面的内容:
标准表达式(页面那些地方要被动态数据替换)、判断与循环(渲染数据过程中需要判断数据是否为空值、model给我的数据可能是一个集合或数组时就需要循环处理)、模板的布局(很多页面的结构是相似的,尽量将布局一样的区域复用)。Thymeleaf官网:
https://www.thymeleaf.org
案例演示
- 配置Thymeleaf
在application.properties中配置。
Thymeleaf默认启用缓存,需要把其缓存关掉,原因是若不关掉模板缓存即使更改了页面但仍可能看到原来未更改的页面,存在延迟;但当系统上线后,模板的缓存就应该开启了,因为缓存会降低服务器的压力。在application.properties中加入代码:
spring.thymeleaf.cache=false
我们知道,spring boot火热的一个重要的原因是他的@SpringBootApplication注解,是一个复合注解,包含了@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan注解。其中@EnableAutoConfiguration可以根据客户引入的spring boot的库来自动加载配置,但是到底加载哪些配置项呢?这些配置项到底属于哪个类呢?也就是说,我们知道spring的依赖注入的方式加载配置项,每个配置项必然注入某个类的属性,那么我们找到这些类,就能知道有哪些配置项可配了。
关于查阅application proprerties的配置手册:spring.io-->projects-->spring boot(Learn-->Reference Doc. )-->Application Properties-->查找Thymeleaf
由此可知thymeleaf缓存的配置项是spring.thymeleaf.cache。
在application.properties加入的配置其实是给一个配置类注入数据,比如我在aplication.properties中加入的spring.thymeleaf.cache=false
其实是给thymeleaf相关的某个配置类的cache属性赋值为false,那么如何知道我使用的配置项属于哪个类呢?因为spring官网发布的新版本的spring boot的说明文档中的Common Application Properties页面显示的配置项是按照功能模块划分的,并没有给出某配置项具体属于哪个类。如图:
因此查阅旧版本说明文档的Common Application Properties,网址:https://docs.spring.io/spring-boot/docs/2.1.13.RELEASE/reference/html/common-application-properties.html
旧版本中都标明了每个配置项的出处。
比如我只知道配置数据源使用spring.thymeleaf,但我还想知道spring.thymeleaf有哪些配置项,这些配置项注入到哪个底层类中,它告诉你了:
那么我们已经知道thymeleaf配置项源于ThymeleafAutoConfiguration这个类,Idea中快捷键Ctrl+N查看该类。由该类中@EnableConfigurationProperties({ThymeleafProperties.class})
可知详细的参数配置应该是ThymeleafProperties.class,那就进入ThymeleafProperties.class查看。
配置好以后,下面写代码演示spring MVC常用的语法。因为spring MVC解决的是表现层的问题,表现层肯定要先写Controller,Model这个类本来就有就可以直接拿来用,再者就是写模板引擎所需要的模板,模板就放在templates中。项目包中三层架构的分布如下图。
以下所有代码实现都是在controller下的AlphaController中进行的。
演示在spring MVC框架下如何获得请求对象和响应对象。底层层面来讲,请求对象中封装了请求数据,响应对象中封装了响应数据,他们分别能够处理请求和响应。spring MVC本身有更简洁的方式处理问题,但是这里先要理解比较底层的机制,简洁的方式其实是对底层的组件、对象做封装。下面在AlphaController中写一个方法。
@RequestMapping("/http")
//若想获取请求对象、响应对象,只需在这个方法上加以声明,
// 声明了这两个对象以后DispatcherServlet在调用这个方法的时候就会自动将这两个对象传过来
//request对象常用的接口是HttpServletRequest
public void http(HttpServletRequest request, HttpServletResponse response) {//这里没有返回值,原因是通过response对象可以直接向浏览器输出数据而不依赖返回值
//获取请求数据request
System.out.println(request.getMethod());
System.out.println(request.getServletPath());//获取请求路径
//以上两行为请求行数据
Enumeration<String> enumeration = request.getHeaderNames();//得到所有请求行的key,因为有很多数据所以要用到迭代器enumeration
while(enumeration.hasMoreElements()) {
String name = enumeration.nextElement();
String value = request.getHeader(name);
System.out.println(name + ":" + value);
//以上为请求消息头,是若干行的数据。
}
// 除此以外还有请求体,包含业务数据、参数。
// 在访问这个方法时若想传一个参数code进来,如何通过request得到参数?
System.out.println(request.getParameter("code"));
//向浏览器做出响应、返回响应数据response
//首先设置返回的数据类型,是返回html、还是图片、字符串等
//若返回网页
response.setContentType("text/html;charset=utf-8");//text/html表示返回网页类型的文本,charset=utf-8以便于支持中文
//通过封装的输出流向浏览器输出
try(//java7新语法:在try后加一个小括号,writer在小括号内创建,编译时会自动加一个finally,然后再close
PrintWriter writer = response.getWriter();//获取输出流
) {
writer.write("<h1>乐坛网</h1>");//通过writer向浏览器打印一个网页,输出一个一级标题
} catch (IOException e) {
e.printStackTrace();
}
}
选中核心类CommunityApplication运行,打开浏览器访问http://localhost:8080/community/alpha/http
在当前网页右键-->检查,在插件中Network界面-->刷新,发现只返回了一个请求,因为网页上没有依赖任何资源。
回到控制台看日志,发现打印结果与代码对应,如下:
如果我想传一个参数,那么就在原浏览器地址后加
?参数名=参数值
,多个参数就是?参数名1=参数值1&参数名2=参数值2
,如:http://localhost:8080/community/alpha/http?code=123
http://localhost:8080/community/alpha/http?code=123&name=Joy
若输入地址为localhost:8080/community/alpha/http?code=123,回车后相应地控制台原来显示null的一行会变成123。
以上即为通过request对象获取相关数据并响应浏览器的底层原理,下面演示实际开发中处理问题的方法。
处理浏览器的请求分为两个方面:请求处理&返回响应
请求处理方式:
请求方式有很多种,但是我们只需要用到get和post就能解决一切问题。
1.浏览器向服务器获取数据 GET请求
- 方法一:
//第一种方法:
//GET请求,希望向服务器获取数据
//假设我要查询所有学生,查询的路径为/students?current=1&limit=20。
// 查出来的信息可能会很多,分页显示需要带上条件,告诉服务器当前(current)是第几页,每一页最多显示多少条数据(20条)
//下面演示服务器怎样去处理这样的请求
@RequestMapping(path = "/students", method = RequestMethod.GET) //强制只能处理get请求
@ResponseBody
public String getStudents(
//注解@RequestParam对参数的注入作更详尽的声明
//required=false表明不传这个参数也不会出错,如果不传这个参数,则默认值defaultValue是1
@RequestParam(name = "current", required = false, defaultValue = "1") int current,
@RequestParam(name = "limit", required = false, defaultValue = "10") int limit) {
System.out.println(current);
System.out.println(limit);
return "some students";
}
重新编译运行结果为- 方法二:
//第二种方法:
//根据学生编号查询一个学生 /student/123(123为学生编号)
//直接把参数编排到路径当中成为路径的一部分
@RequestMapping(path = "/student/{id}", method = RequestMethod.GET)
@ResponseBody
public String getStudent(@PathVariable("id") int id) {//注解@PathVariable表示路径变量
return "a student";
}
运行结果:
总之,在get请求当中,有两种传参的方式,一种是?参数=参数值
,一种是把某一参数直接加到路径当中。
2.浏览器向服务器提交数据 POST请求
浏览器向服务器提交数据,首先浏览器需要打开一个带有表单的网页,通过表单填写数据以后才能提交给服务器,现在还没有网页,那么我们就在项目中创建一个静态的网页。templates里存放的模板是动态资源,静态的资源应该放在static下。在static下建一个目录html,在static.html目录下创建一个html文件命名为student。
创建html静态页面
<!--新增一个学生发送给浏览器-->
<!--注释-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>增加学生</title>
</head>
<body>
<!--在body之内加一个表单-->
<form method="post" action="/community/alpha/student">
<!--声明该表单用什么样的方式提交(post请求提交数据)、提交给谁(提交路径)-->
<p>
姓名:<input type="text" name="name">
<!--文本框中的数据在填完提交给服务器时需要给这个数据起一个参数名-->
</p>
<p>
年龄:<input type="text" name="age">
</p>
<p>
<input type="submit" value="保存">
<!--提交按钮-->
</p>
</form>
</body>
</html>
重新编译运行,浏览器中访问html页面,因为这个html放在static目录下,static这一级路径不用敲,需要敲入/html目录,因为访问的是静态页面所以与alpha目录没有任何关系。因此访问地址为:localhost:8080/community/html/student.html
访问结果如图
先不着急在表单中填数据,因为还没有在服务端加路径以处理请求。
补充:get请求也可以向服务器端传数据,那为什么不用get请求去传?因为get请求在传参数是,参数会直接显示在路径中,其次路径的长度是有限制的,若参数有很多项时get请求很可能传不下。因此在提交数据时一般不用get请求。
下面写方法来处理post请求
//POST请求
@RequestMapping(path ="/student", method = RequestMethod.POST)
@ResponseBody
public String saveStudent(String name, int age) {
//直接声明参数,参数名字与表单中数据名一致就会自动把数据传过来
System.out.println(name);
System.out.println(age);
return "success";
}
重新编译运行,并刷新刚才的静态表单页面,在表单中填写数据并提交。
以上请求处理方式就演示完成了。接下来演示如何向浏览器返回响应数据。
返回响应方式:
之前我们都是响应字符串这种简单的内容,那么下面演示如何向浏览器响应动态的html。
- 方法一:
//响应HTML数据
//假设浏览器查询一个老师,那么服务器就帮助其查询到了该老师相关的数据,并将数据以网页的形式响应给浏览器
@RequestMapping(path ="/teacher", method = RequestMethod.GET)
//不加注解@ResponseBody就默认返回html
public ModelAndView getTeacher() { //返回的数据类型是model和view两份数据,是提供给DispatcherServlet的
ModelAndView mav = new ModelAndView();//先实例化
mav.addObject("name", "张三"); //传入动态值
mav.addObject("age", "30");
mav.setViewName("/demo/view");
//设置模板的路径和名字,模板都放在templates目录下,我用到的模板会放在templates的demo目录下,且目录后要跟上模板的文件名
//因为thymeleaf引擎默认的模板就是html文件,因此不再需要写后缀,只需写文件名如:view.html只用写view
return mav;
}
代码中getTeacher()返回的数据类型是ModelAndView,这里就需要再复习一下spring MVC的原理:所有的组件都是由DispatherServlet去调度的,DispatherServlet会调用Controller的某个方法,这个方法需要给其返回model数据、视图相关数据,然后再由DispatherServlet把model和view提交给模板引擎,由模板引擎进行渲染生成动态html。
还缺templates.demo下的view.html模板,创建vie.html,代码如下:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!--声明当前文件是一个模板而不是一个普通的html文件,这个模板的语法来源于thymeleaf官网-->
<!-- -->
<head>
<meta charset="UTF-8">
<title>Teacher</title>
</head>
<body>
<p th:text="${name}"></p>
<p th:text="${age}"></p>
<!-- 第一段落:姓名,第二段落。因为姓名和年龄都是动态的值,我们需要利用模板引擎的语法去解决 -->
<!--th:text="${name}"是thymeleaf的语法-->
</body>
</html>
- 方法二:
上面的方法一更直观,是把model和view都装到了一个对象里,而此方法是把model数据装到参数中,把view视图直接返回,返回的值给了DispatcherServlet,同时DispatcherServlet也持有model的引用,因此DispatcherServlet依旧能得到model和view。
//查询学校
@RequestMapping(path ="/school", method = RequestMethod.GET)
public String getSchool(Model model) {
//这个model对象不是我们自己创建的,而是DispatcherServlet在调用方法时,当检索到有model对象时,自动实例化了model对象
//model是一个bean,DispatcherServlet持有对bean的引用,在方法内部往bean中存数据也能够实现的
model.addAttribute("name", "北京大学");
model.addAttribute("age", "100");
return "/demo/view";//返回view的路径
//因为返回的类型是string,model数据如何传呢?加类型为model的参数
}
对比来看方式二更加简洁,所以最好掌握这种方法。
- json响应
单个json对象
除了能响应html以外,服务器还能向浏览器响应任何数据,常见响应JSON数据,通常是在异步(当前网页不刷新但是悄悄访问了服务器一次,得到了一个结果,结果不是html只是一个判断结果)请求当中。比如注册b站账号,填写用户名完成时光标切换,就会被判断出用户名是否可用。现在重点关注如何向浏览器响应这种数据。
JSON数据是什么数据?有怎样的价值?java是面向对象的语言,得到的是java对象,将java对象数据返回给浏览器;浏览器解析对象用的是js,js也是面向对象语言,js希望得到js对象,但是Java对象不可能直接转成js对象,那么通过JSON就可以实现两者的兼容,JSON实际上是具有特定格式的字符串,Java对象可以转成字符串传给浏览器,浏览器可以将字符串转成js对象。因为任何语言都有字符串类型,字符串的格式是比较通用的格式,任何语言都能进行解析,都能将字符串转成对象,所以JSON起到了衔接的作用。因此在跨语言的情况下,json是常见的一种字符串形式,尤其是在异步请求当中,客户端需要返回一个局部验证的结果时,json响应就很方便。下面演示如何向浏览器响应json。
//响应JSON数据(异步请求)
@RequestMapping(path = "/emp", method = RequestMethod.GET)
@ResponseBody
public Map<String, Object> getEmp() { //map用于封装
Map<String, Object> emp = new HashMap<>();//实例化
emp.put("name", "张三");
emp.put("age", "23");
emp.put("salary", "8000");
return emp;
DispatcherServlet在调用getEmp()方法时,看到加了@ResponseBody
注解以及返回的类型,就会自动把map转成字符串并发送给浏览器。
多个json对象
@RequestMapping(path = "/emps", method = RequestMethod.GET)
@ResponseBody
public List<Map<String, Object>> getEmps() { //map用于封装
List<Map<String, Object>> list = new ArrayList<>(); //创建一个list(集合)
Map<String, Object> emp = new HashMap<>();//实例化
emp.put("name", "张三");
emp.put("age", "23");
emp.put("salary", "8000.00");
list.add(emp);//向集合内装数据
emp = new HashMap<>();
emp.put("name", "李四");
emp.put("age", "24");
emp.put("salary", "9000.00");
list.add(emp);//向集合内装数据
emp = new HashMap<>();
emp.put("name", "王五");
emp.put("age", "25");
emp.put("salary", "10000.00");
list.add(emp);//向集合内装数据
return list;
}
以上就是在Spring MVC当中处理请求、处理响应的最常用的几种方式,后面项目中会经常反复用到。
End