1 数据响应
响应页面是指发送一个请求,跳转指定页面。响应页面是来开发一些单体项目(即前后端都在一个开发工具中);响应数据是用来开发前后端分离的项目,前端发送过来请求后端响应相应的数据。
1.1 响应数据——JSON
如果想让SpringMVC响应返回一个JSON类型的数据,首先需要在项目的pom.xml文件中导入web场景的启动器spring-boot-starter-web,spring-boot-starter-web底层引入了JSON场景spring-boot-starter-json。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
然后在controller的方法上中加入@ResponseBody注解,这样的话就是响应数据而不是页面跳转,或者可以把controller类上的@Controller注解换成@RestController,相当于这个controller类下的所有方法都加上了@ResponseBody注解。
1.2 响应数据——内容协商
1.2.1 内容协商介绍
内容协商是指前端发送请求的时候请求头携带Accept字段,用于向服务器声明自己(客户端)能够接收的数据类型,服务器就会根据客户端接收能力的不同,返回不同媒体类型的数据。
原理:首先判断当前响应头中是否已经有之前处理时确定的媒体类型,如果有就用之前确定的媒体类型,如果没有的话就是第一次处理,需要确定处理的媒体类型,通过请求头Accept字段获取客户端(PostMan、浏览器)支持接收的内容类型。遍历循环所有的MessageConverter看谁支持操作这个对象(Person),找到支持操作Person的converter之后把它支持的媒体类型统计出来。如此操作我们就得到了客户端支持接受的类型和服务端能够返回的类型,再通过嵌套循环客户端支持接收的类型和服务端能够返回的类型获得内容协商的最佳匹配媒体类型,最后遍历所有的MessageConverter看谁支持将对象转为最佳匹配媒体类型,用该converter完成转换。
1.2.2 基于请求参数的内容协商
postman可以直接更改请求体Accept字段的内容,而浏览器无法更改,为了方便浏览器的内容协商,可以开启基于请求参数的内容协商功能。
步骤如下:首先在application.yml中写spring.mvc.contentnegotiation.favor-parameter: true,然后发送请求时携带fromat字段。
请求:http://localhost:8080/user?format=json
@RequestMapping(value = "/user")
public String getUser() {
return "get user";
}
1.2.3 数据响应的原理小结
1、标注了@ResponBody或@RestController表示数据是响应出去,就会调用RequestResponseBodyMethodProcessor处理;
2、RequestResponseBodyMethodProcessor通过调用MessageConvert进行数据的转换;
3、所有的MessageConvert合起来可以支持各自媒体类型数据的读和写;
4、通过内容协商找到最终处理的MessageConvert。
2 视图解析与模板引擎
2.1 视图解析
视图解析:springboot默认的打包方式是jar包方式,但是JSP不支持在jar包(一种压缩包)中编译,因此springboot默认不支持JSP,需要引入第三方的模板引擎技术实现页面的渲染。
2.2 模板引擎
Thymeleaf的使用:首先在pom.xml文件里引入场景启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
引入Thymeleaf的场景启动器后,springboot会在ThymeleafAutoConfiguration自动配置类中配置好所有的相关组件,并将相关配置项与ThymeleafProperties.class绑定,同时配置了SpringTemplateEngine和ThymeleafViewResolver,设置了默认页面跳转的前缀和后缀(即默认页面存放的位置是templates文件夹和页面的文件后缀必须是.html),我们可以直接开发页面即可。
案例:
(1) templates文件夹下新建html文件(2)标签引入templates命名空间,可以在编写页面时有提示。下面代码中${}是直接获取link的值作为链接地址,@{}是拼装项目的访问路径+符号里的值,即http://localhost:8080/link,如果在application.yml中加了访问前缀,那么@{}拼装时也会自动加上访问前缀,@{}也可以直接写网页绝对路径如https://www.baidu.com/
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 th:text="${msg}">哈哈</h1><br>
<h1>
<a th:href="${link}">百度一下</a><br>
<a th:href="@{link}">百度一下</a>
</h1>
</body>
</html>
(3)编写controller用于页面跳转
@Controller
public class ViewTestController {
@RequestMapping("/success")
public String getSuccess(Model model) {
//model的数据会被放到在request域中
model.addAttribute("msg","你好,噪声");
model.addAttribute("link","http://www.baidu.com");
return "success";
}
}
开发小技巧:重定向可以防止表单重复提交
2.3 拦截器
拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并在Controller方法执行前后加入某些特殊处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等,以判断用户是否登录为例,拦截除了登录和静态资源的所有请求进行登录判断,已经登录则放行,未登录则返回登录页面。下面介绍拦截器的具体实现步骤:
首先自定义拦截器实现HandlerInterceptor接口,重写相应的方法。
public class LoginInterceptor implements HandlerInterceptor {
// 目标方法执行之前执行的方法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 实现登录检查的逻辑
HttpSession session = request.getSession();
Object user = session.getAttribute("loginUser");
if (user != null) {
// 如果session域中存在user说明已经登录可以放行
return true;
}
// 拦截住,未登录,转发到登录页面
request.setAttribute("msg", "请先登录");
request.getRequestDispatcher("/").forward(request,response);
return false;
}
// 目标方法执行完成以后执行的方法
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
// 页面渲染以后执行的方法
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
然后自定义配置类实现WebMvcConfigurer接口,重写addInterceptors方法将拦截器注册到容器,并指定拦截哪些请求。
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
// 拦截所有的请求
.addPathPatterns("/**") // /**是所有请求都会拦截包括静态资源
// 直接放行的请求
.excludePathPatterns("/", "/login", "/css/**", "/fonts/**", "/js/**", "/images/**");
}
}
拦截器执行流程:按图中顺序执行,任何步骤代码出现异常都会直接从已经执行过的拦截器倒叙执行afterCompletion。
2.4 文件上传
前端写一个form表单上传文件,后端写一个controller获取文件并处理。表单的method是post,enctype是multipart/form-data,多文件上传在input中要加multiple。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
<!--email邮箱-->
<div class="form-group">
<label for="exampleInputEmail1">邮箱</label>
<input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
</div>
<!--userName用户名-->
<div class="form-group">
<label for="exampleInputPassword1">用户名</label>
<input type="text" name="userName" class="form-control" id="exampleInputPassword1" placeholder="Password">
</div>
<!--单文件上传 头像-->
<div class="form-group">
<label for="exampleInputFile">头像</label>
<input type="file" name="headerImg" id="exampleInputFile">
</div>
<!--多文件上传 生活照-->
<div class="form-group">
<label for="exampleInputFile">生活照</label>
<input type="file" name="photos" multiple>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</body>
</html>
@Controller
public class formController {
@RequestMapping("/form")
public String form() {
return "form";
}
@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
@RequestParam("userName") String userName,
@RequestPart("headerImg") MultipartFile headerImg,
@RequestPart("photos") MultipartFile[] photos) throws IOException {
//MultipartFile会自动封装上传过来的文件
if (!headerImg.isEmpty()) {
//获得文件的原始文件名
String originalFilename = headerImg.getOriginalFilename();
//把头像保存到D盘的headerImg文件夹下
headerImg.transferTo(new File("D:\\headerImg\\" + originalFilename));
}
if(photos.length != 0) {
for (MultipartFile photo : photos) {
if(!photo.isEmpty()) {
String originalFilename = photo.getOriginalFilename();
photo.transferTo(new File("D:/photos/" + originalFilename));
}
}
}
//跳转到另一个请求用forward或redirect
return "forward:/success";
}
}
可以在application.properties或application.yml中自定义配置文件上传的属性
#单个文件的最大大小
spring.servlet.multipart.max-file-size=10MB
#总文件最大大小
spring.servlet.multipart.max-request-size=100MB