SpringMVC
SpringMVC是Web层,是代替servlet的(接受请求、处理请求、做出响应)
SpringMVC就是下面五个方面:
- 做出响应
- 获取请求
- 统一异常处理
- 参数校验
- 拦截器—登录验证:通过放行,不通过不放行。
优化了之前哪些?作用:
通过 :1. 做出响应2. 获取请求—我们能够实现获取前端的数据并可以对浏览器做出响应。
3.统一异常处理:之前是直接写在controller层里面的,现在将异常做统一处理,这样代码能够简洁明了。
4. 参数校验:以前需要在服务器端进行写代码进行参数检验,但是如果参数超级多,就会比较麻烦。所以这块实现不写代码的情况下实现参数检验。
34部分就是让controller层更加简单明了。让controller只有接受请求、处理请求、做出响应。
关注:注解有哪些?各自的作用
- 要会读会写相关注解
- 会使用注解
- 能用自己的话说出相关注解的作用
一、起步案例
通过浏览器访问Controller,跳转到一个新的页面。只需要写Controller和页面即可
- 首先创建一个静态页面:静态页面要放在static目录下
- Controller类-控制器-Java类
package com.stedu.controller;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller//放在类上面,表明这是一个控制器类
@RequestMapping("/test")//可以放在类和方法上面,用来设置路径。即类的 路径是:test
public class TestController {
//光有路径是没用的,要创建方法:方法的路径是类的路径+方法的路径
@RequestMapping("/hello")
public String hello(){
//请求转发--视图位置:static是项目的根路径
return "/test.html";//请求转发的实现直接return一个字符串就行,返回视图的位置
}
}
二、SpringMVC执行流程
SpringMVC属于web层,就是负责和浏览器进行交互的
前端控制器是核心,所有的组件都是和前端控制器交互的。前端控制器的本质就是servlet,内置了servlet。前端控制器是springmvc官方写的servlet。
- 用户发送请求(只有用户这个部分是前端,其他都是后端)至前端控制器DispatcherServlet;
- DispatcherServlet收到请求调HandlerMapping处理器映射器;
- 处理器映射器(这个处理器映射器就是能够根据用户的请求地址找到具体的controller,但是不会执行方法)找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给前端控制器DispatcherServlet;
- DispatcherServlet调用HandlerAdapter处理器适配器;
- HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器);(调用执行方法)
- Controller执行完成返回ModelAndView;
- HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet;
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器;
- ViewReslover解析后返回具体View;
- DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中);
- DispatcherServlet响应用户。
即用户先请求前端控制器 ,前端控制器根据用户的请求再去请求处理器映射器 ,处理器映射器根据用户的请求地址会返回一个执行器链,然后前端控制器拿到执行器链后调用处理器适配器,处理器适配器会调用具体的控制器(handler)。调用完具体的控制器(handler)后具体的控制器就会被返回。一个返回的是视图,一个返回的是模型。返回给了处理器适配器,处理器适配器将二者返回给前端控制器,前端控制器根据视图和模型调用视图解析器,视图解析器对视图进行解析,最后将视图返回给用户 。
三、SpringMVC注解解析
3.1、@RequestMapping
作用:就是给类里面的方法配一个地址。设置一个路径。
方法上面不设置路径能够编译通过,这个方法就是普通方法,但是如果想让方法能够被外界访问,就必须要配置路径。关于方法的路径:
属性:
value:
- 用于指定类或者方法请求的地址,可以配置多个值(数组的方式配置)
- @RequestMapping如果没有指定属性,那么()中的值就是value的值
method:用于指定方法能够接收请求的方式;不指定表示任何请求都能接收
@RequestMapping(value = "/hello", method = RequestMethod.POST)
public String hello() {
return "/jsp/success.jsp";
}
如果限制请求的方式,可以使用如下注解进行地址映射:
@GetMapping(地址)
,限制只接收get请求;
@PostMapping(地址)
,限制只接收post请求。
@PutMapping(地址)
,限制只接收put请求。
@DeleteMapping(地址)
,限制只接收delete请求。
3.2、@Controller
指定这是一个控制器类,这个注解的作用和@Component作用是一样的,用来配置bean让Spring容器进行管理。配置了@Controller注解之后,需要在SpringMVC配置文件中配置包扫描:<context:component-scan />
@Controller:表明这个类是控制器类,和@Service
、@Component
、@Repository
一样,加在哪个类上面,就能实例化哪个类的对象。不需要自己创建对象了
四、转发和重定向
我们在Controller中写的方法,默认以转发的方式转发到相应名称的试图,如果希望明确配置转发和重定向,在视图名称前加上forward:或redirect:。
/**
* 转发:地址栏不会改变
*/
@RequestMapping("/hello1")
public String hello1() {
return "forward:/jsp/success.jsp";
}
/**
* 重定向:地址栏改变
*/
@RequestMapping("/hello2")
public String hello2() {
return "redirect:/jsp/success.jsp";
}
五、配置视图解析器
什么是前缀和后缀?
如果不想写前缀和后缀,那么我们可以:
配置视图解析器:
在配置文件里面:
#配置视图解析器:
#前缀:
spring.mvc.view.prefix=前缀
spring.mvc.view.suffix=后缀
这样在控制器层的代码请求转发可以省略前缀和后缀,但是关于重定向不能省略前缀和后缀
package com.stedu.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller//放在类上面,表明这是一个控制器类
@RequestMapping("/test1")//可以放在类和方法上面,用来设置路径。即类的 路径是:test
public class Test1Controller {
//光有路径是没用的,要创建方法:方法的路径是类的路径+方法的路径
@RequestMapping("/hello")
public String hello(){
//请求转发--视图位置:static是项目的根路径
//配置了视图解析器可以省略前后缀
return "test";//请求转发的实现直接return一个字符串就行,返回视图的位置
}
@RequestMapping("/hello1")
public String hello2(){
//重定向:地址栏改变:不能省略前后缀
return "redirect:/a/test1/html";
}
}
六、Spring MVC的响应和请求
1.数据响应:返回JSON类型的数据
方式:添加注解:@ResponseBody
。添加上这个注解,如果返回的是复杂类型,那么就会把复杂类型转成JSON类型的字符串返回给前端。
举例:
实体类:
package com.stedu.bean;
import lombok.*;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Student {
private String name;
private Integer age;
private Integer id;
private String addr;
}
Controller层:
package com.stedu.controller;
import com.stedu.bean.Student;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class Test3Controller {
@ResponseBody//返回的是复杂类型,那么就会把复杂类型转成JSON类型的字符串返回给前端
@RequestMapping("/test3")
public Student hello3(){
Student student = new Student();
student.setAddr("qd");
student.setId(1);
student.setAge(20);
student.setName("zhangsan");
return student;
}
}
上面是加上注解返回的JSON格式:键值对格式
如果不加注解:
所以将一个对象转成JSON返回给前端只需要加个@ResponseBody
注解即可。
那如何将多个对象转成JSON格式响应给前端呢?
解决方案:集合
//将多个JSON对象转成JSON返回给前端
@RequestMapping("/test333")
@ResponseBody
public ArrayList<Object> test3(){
ArrayList<Object> list = new ArrayList<>();
Student student1 = new Student("lisi1", 21, 1, "sd");
Student student2 = new Student("lisi2",22,2,"sd");
Student student3 = new Student("lisi3",23,3,"sd");
Student student4 = new Student("lisi4",24,4,"sd");
//添加到集合里面:
list.add(student1);
list.add(student2);
list.add(student3);
list.add(student4);
return list;//响应数据给前端,因为加上了注解@ResponseBody,所以直接返回了
}
补充对@RestController
的使用说明
总结:
响应:
- 可以请求转发/重定向
- 给页面返回JSON:返回一个Java的对象,然后在类上面加上注解:
@ResponseBody
2. 获取请求参数
获取请求参数主要是对应:
1、获得基本类型参数
Controller中的业务方法的参数名称要与请求参数的name一致,参数值会自动映射匹配;并且能自动做类型转换(String向其他类型转换)。
/**
* 获取基本数据类型参数
* 请求参数名和方法参数相同,会进行自动映射,方法参数和请求参数值相同
*/d
@RequestMapping("/test1")
public String test1(String username, int age) {
System.out.println(username);
System.out.println(age);
return "success";
}
发送请求测试:
http://localhost:8080/test1?username=Tom&password=123456
总结:浏览器发送的参数名字和Controller方法参数的名字要保持一致:
1.form表单name属性值和Controller方法参数的名字要保持一致
2.key和Controller方法参数的名字要保持一致。
如果不一致,方法参数的值是null。
2、获得JSON类型数据
浏览器 发送给服务器的请求不是json,虽然使用了AJAX请求,但是发送的数据只是模拟form表单提交数据,数据类型不是Json
如果想发送的数据是JSON类型,那么需要设置发送数据类型:
contextType:"application/json;charset=utf-8"
如果没有写这个,那就是对form表单做的模拟。
下面具体说前端如果发送的是JSON,那后端我们如何获取?
加注解:@RequestBody
3.前后端参数名不一样—参数绑定注解@RequestParam
当前端请求的参数名称与Controller的业务方法参数****名称不一致时,就需要通过 @RequestParam 注解进行绑定
@RequestMapping("/test7")
public String test7(@RequestParam(name="name", required=false, defaultValue="Jim") String username) {
System.out.println(username);
return "success";
}
4.Restful风格参数获取@PathVariable(以及@GetMapping、@PostMapping)
Restful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。
Restful风格的请求是使用url+请求方式表示一次请求目的的,HTTP协议里面四个表示操作方式的动词如下:
GET:用于获取资源
POST:用于新建资源
PUT:用于更新资源
DELETE:用于删除资源
其实就是不完全是GetMapping和PostMapping了,新增修改删除对应上面的@…Mapping
例如:
路径 请求方式 功能
/user/1 GET 得到id=1的user
/user/1 DELETE 删除id=1的user
/user/1 PUT 更新id=1的user
/user POST 新增user
上述url地址/user/1中的1就是要获得的请求参数,在SpringMVC中可以使用占位符进行参数绑定。地址/user/1可以写成/user/{id},占位符{id}对应的就是1的值。在业务方法中我们可以使用@PathVariable注解进行占位符的匹配获取工作。
/**
* Restful风格的请求参数
* url+请求类型
*/
@GetMapping("/test8/{username}")
public String test8(@PathVariable("username") String username) {
System.out.println(username);
return "success";
}
请求地址:
http://localhost:8080/test1/test8/Bob
七、异常处理—自定义异常处理器并使用
以前我们异常处理是在各层里面直接处理的,看起来代码很混乱。我们希望的是各层就做好自己的就可以。
我们想要做全局统一处理。
方式:
自定义异常处理器并使用:
(1)控制器内的异常处理-----只能处理某个Controller自己的异常。
(2)全局异常处理(更常用)
1、异常处理思路
系统的dao、service、controller出现都通过throws Exception向上抛出,最后由springmvc前端控制器交由异常处理器进行异常处理。
2、控制器异常处理方式
配置控制器异常处理,使用@Controller+@ExceptionHandler
。
编写控制器类
package com.stedu.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
//异常处理器:
@Controller//控制器类:
@RequestMapping("/test9")//路径
public class Test9Controller {
@GetMapping("hello1")//给这个方法配置路径,这个方法可以接收请求了
public String hello1(){
//人为的创造异常:
String crr = null;
System.out.println(crr.length());//空指针异常,如果没有处理,就会程序崩溃,显示500页面。
return "/a/success.html";//跳转
}
}
如上,我们自动的创建了异常,如果我们不对其进行处理,那么用户访问的时候会出现错误:
一般我们接触到的异常异常处理就是出现异常的时候跳转到一个单独的出错页面:比如:出错啦之类的。跳转到专门的出错页面。
err.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>出错啦</h1>
</body>
</html>
所以需要自定义异常捕获:
package com.stedu.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
//异常处理器:
@Controller//控制器类:
@RequestMapping("/test9")//路径
public class Test9Controller {
//自定义异常处理器:控制器内部的---只能捕获NullPointerException异常
@ExceptionHandler(NullPointerException.class)
public String exceptionHandler(NullPointerException e){
return "err";
}
@GetMapping("/hello1")//给这个方法配置路径,这个方法可以接收请求了
public String hello1(){
//人为的创造异常:
String crr = null;
System.out.println(crr.length());//空指针异常,如果没有处理,就会程序崩溃,显示500页面。
return "success";//跳转
}
@GetMapping("hello2")//给这个方法配置路径,这个方法可以接收请求了
public String hello2(){
//人为的创造异常:
int a= 10;
a=a/0;//会崩溃,但是不会去err页面,还是会出现500,因为写的这个只能捕获空指针异常,
//这个处理方式可以处理改成Exception异常,
// 但是如果很多的话,也会很麻烦,因为要在每个里面都添加这样的代码处理,会造成代码冗余
// 所以全局异常处理更合适
System.out.println(crr.length());//空指针异常,如果没有处理,就会程序崩溃,显示500页面。
return "success";//跳转
}
}
这个处理方式可以处理改成Exception异常,但是如果很多的话,也会很麻烦,因为要在每个里面都添加这样的代码处理,会造成代码冗余,所以全局异常处理更合适
3、全局异常处理
控制器的异常处理只能处理控制器内部的异常,如果希望处理控制器抛出的所有异常而不希望在控制器内部处理,这就需要配置全局异常处理。配置全局异常处理,使用@ControllerAdvice+@ExceptionHandler
。
其实就是将单独写的异常处理复制一份,放到全局处理的类中。
3.1、在控制器中添加方法
不需要额外添加注解,正常写就行了
@RequestMapping("/test2")
public String test2(int a) throws Exception {
if(a == 100) {
throw new Exception("出错了");
}
return "success";
}
3.2、编写全局异常处理类—不需要写try-catch了
package com.stedu.advice;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
@ControllerAdvice//1统一异常处理注解:
public class WebExceptionHandler {
//2统一异常处理:在方法上加上@ExceptionHandler
@ExceptionHandler(NullPointerException.class)
public String exceptionHandler(NullPointerException e){
return "err";
}
@ExceptionHandler(NullPointerException.class)
public String exceptionHandler(Exception e){
return "err";
}
}
如果控制器内部能处理,就走内部的处理,如果内部不能处理,就会走全局异常处理。其他层或者类(比如控制器层/业务层)不需要添加额外的注解或者代码
4、异常处理返回JSON数据
4.1、修改全局异常处理类
@ControllerAdvice
public class WebExceptionHandler {
/**
* 全局异常处理,处理Exception
* 返回JSON
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public String handleRuntimeException(Exception e) {
return "{\"msg\":\"出错了\"}";
}
}
4.2、发送请求的页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Index</title>
<script src="${pageContext.request.contextPath}/js/jquery-3.3.1.js"></script>
<script>
$(function () {
$("#btn").click(function () {
//发送ajax请求
$.ajax({
url: "${pageContext.request.contextPath}/test/test2",
data: {a:100},
dataType: "JSON",
type: "POST",
success: function(data) {
console.log(data);
}
});
});
});
</script>
</head>
<body>
<p>
<a href="${pageContext.request.contextPath}/test/test1">测试控制器异常处理</a>
</p>
<p>
<a href="${pageContext.request.contextPath}/test/test2?a=100">测试控制器全局处理</a>
</p>
<p>
<button id="btn" type="button">测试全局异常处理,返回JSON</button>
</p>
</body>
</html>
总结上述内容:
八、参数校验—服务器端参数校验
1.引入依赖
如果Springboot版本小于2.3.x,spring-boot-starter-web会自动传入hibernate-validator依赖。如果Springboot版本大于2.3.x,则需要手动引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2、参数校验未通过的统一异常处理
如果校验不通过会报MethodArgumentNotValidException或者ConstraintViolationException。
@RestControllerAdvice
public class ParamExceptionHandler {
@ExceptionHandler({BindException.class})
public RespBean handleMethodArgumentNotValidException(BindException e) {
BindingResult bindingResult = e.getBindingResult();
StringBuilder sbf = new StringBuilder("校验失败 ");
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sbf.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
}
String msg = sbf.toString();
int i = msg.lastIndexOf(", ");
msg = msg.substring(0, i);
return RespBean.error(msg);
}
@ExceptionHandler({ConstraintViolationException.class})
public RespBean handleConstraintViolationException(ConstraintViolationException e) {
return RespBean.error(e.getMessage());
}
}
3.RequestBody和form表单参数校验
我们通常使用@RequestBody和实体类来接收参数。此时只需要给参数上面加上@Validated注解就能实现自动参数校验。
即:在实体类的属性上面添加规则,在方法接收参数位置加上:@Validated注解
实体类代码
@Data
public class User {
private Long id;
@NotBlank(message = "用户账号不能为空")
@Length(min = 4, max = 10,message = "账号长度为4-10位")
private String userName;
@NotBlank(message = "密码不能为空")
@Length(min = 6,max = 12,message = "密码长度为6-12位")
private String password;
@NotNull(message = "邮箱不能为空")
@Email(message = "邮箱格式错误")
private String email;
/**
* 性别 1为男生 2为女生
*/
@Min(value = 1,message = "最小值为1")
@Max(value = 2,message = "最大值为2")
private Integer sex;
}
Controller代码
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping
public RespBean addUser(@RequestBody @Validated User user){
//忽略service处理相关业务
return RespBean.ok();
}
@PostMapping("/add1")
public RespBean addUser1(@Validated User user){
//忽略service处理相关业务
return RespBean.ok();
}
}