1. Spring MVC概述
1.1 Spring MVC是什么
SpringMVC是Spring的一个模块,是一个基于MVC设计模式的web框架。
1.2 Spring MVC执行流程。
1.3 组件分析
-
前端控制器(默认配置)Dispatcher Servlet 作用:只负责分发请求。可以很好的对其它组件进行解耦合。
-
处理器映射器(默认配置)HandlerMapping
作用:将前端的url和处理器方法建立映射关系
-
处理器适配器(默认配置)HandlerAdapter
适配并调用具体的处理器方法执行
-
处理器 controller
(需要程序员开发)
-
视图解析器 ViewResolver
根据逻辑视图查找映射物理视图
简化的MVC执行流程:
前后分离的执行流程:
2. Handler
引入依赖
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.1 参数
Handler的参数列表,功能非常强大,主要功能有以下两点:
-
接收请求参数
-
获取servletAPI
java
@Controller
@RestController
public class Login {
}
2.1.1接收请求参数
-
简单类型的参数,Spring MVC可以直接帮我们封装成参数列表中声明的类型,比如String、int、double......
java//http://localhost:8080/login?username=petrel&password=123 @RequestMapping("/login") public void login(String username, String password){ System.out.println(username); System.out.println(password); }
-
或者也可以直接接收一个Java Bean
java//http://localhost:8080/login?name=petrel&age=23 @RequestMapping("/login") public void login(Person person) { System.out.println(person.getName()); System.out.println(person.getAge()); }
-
如果请求参数是一个日期,Spring MVC并不能直接封装到Date中,需要设置一下日期格式。
java//http://localhost:8080/login?name=petrel&age=23&birthday=2020-02-02 public class User { private String username; private String password; @DateTimeFormat(pattern = "yyyy-MM-dd")//前端到后端 可以把前端string类型转换到后端date类型 //@JsonFormat(pattern = "yyyy-MM-dd")//后端到前端 可以把后端date类型转换到前端string private Date birthday; // getters & setters... } // SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd"); // String date = sim.format(user.getBirthday()); 将Date处理成字符串
-
如果请求参数是一个数组类型,我们可以直接通过数组接收。
javahttp://localhost:8080/delSel?ids=1&ids=2 请求参数ids为方法的参数 @RequestMapping("/delSel") public String delSel(Integer[] ids) { System.out.println(Arrays.toString(ids)); return "index"; }
-
如果想要用集合类型来接收数组参数呢?下面的写法可以么?
java//http://localhost:8080/delSel?ids=1&ids=2 @RequestMapping("/delSel") public String delSel(List<Integer> ids) { System.out.println(ids); return "index"; } //不可以,因为方法参数中的数据类型 必须是可实例化的,得有构造方法,List是个接口,没办法构造。但是可以使用ArrayList
java//http://localhost:8080/delSel?ids=1&ids=2 @RequestMapping("/delSel") public String delSel(@RequestParam ArrayList<Integer> ids) { System.out.println(ids); return "index"; } //但是必须在参数前面加@RequestParam才可以拿到请求参数
2.1.2 获取servletAPI
可以在Handler的形参中直接使用以下类型:
-
HttpServletRequest
通过request对象获取请求信息 -
HttpServletResponse
通过response处理响应信息 -
HttpSession
通过session对象得到session中存放的对象
java
@ResponseBody
@RequestMapping("/login") // 获取前端请求资源路径
public String login(String username, Integer password, HttpServletRequest request) {
HttpSession session = request.getSession();
session.setAttribute("username", username);
return "index";
}
@ResponseBody
@RequestMapping("/test") // 获取前端请求资源路径
public String test(HttpServletRequest request) {
HttpSession session = request.getSession();
Object username = session.getAttribute("username");
String uname = (String) username;
return uname;
}
@ResponseBody
@RequestMapping("/login") // 获取前端请求资源路径
public String login(String username, Integer password, HttpSession session) {
session.setAttribute("username", username);
return "index";
}
@ResponseBody
@RequestMapping("/test") // 获取前端请求资源路径
public String test(HttpSession session) {
Object username = session.getAttribute("username");
String uname = (String) username;
return uname;
}
2.2 返回值(了解)
可以为Handler指定两种种返回值类型:
-
void
如果返回值为
void
的时候,可以在Handler形参上定义request
和response,使用
request或
response指定响应结果javaresponse.getWriter().println();
-
String
逻辑视图名称
返回的字符串就是逻辑视图。
javareturn "index.jsp";
请求转发与重定向
java@RequestMapping("/login") // @ResponseBody 注释,否则返回json格式 public String login(String username, Integer password, HttpSession session) { session.setAttribute("username", username); // return "forward:/test"; // 请求转发 return "redirect:/test"; // 重定向 } @RequestMapping("/test") @ResponseBody // 这里需要返回json public String test(HttpSession session) { Object username = session.getAttribute("username"); String uname = (String) username; return uname; }
2.3 注解
-
@RequestMapping
-
声明在方法上:
java//@RequestMapping("/login") //@RequestMapping(value = "/login") //@RequestMapping(path = "/login") //@RequestMapping(value = {"/login1", "/login2"}) @Controller public class TestController { @ResponseBody @RequestMapping(value = {"/login1", "/login2"}, method = RequestMethod.GET) // 只有get请求才可以 post请求进不来 public String login(String username) { System.out.println(username); return username; } }
-
通过
value
属性配置该方法的访问路径 -
通过method属性指定该方法允许的访问方式,默认情况get,post都支持
-
java// 静态文件目录,resources/static/login.html 访问方式 http://localhost:8080/login.html <form action="/login1" method="get"> <input type="text" name="username"> <button>提交</button> </form>
-
声明在类上:窄化请求
⚠️:我们的controller中是不允许有相同的资源路径的
假设我们的EmpController和DeptController都需要将资源路径成定义"/findAll"必然是不合法的
我们可以将资源路径分别定义成"emp/findAll"和"dept/findAll",这种方式叫做窄化请求
窄化请求,可以对请求URL进行分类管理,例如:
/person/add
、/person/list
......java// http://localhost:5555/v1/findAll?username=petrel @RestController @RequestMapping("/v1") public class TestController { @RequestMapping("/findAll") public String findAll(String username) { System.out.println(username); return username; } } // 2合1注解 : @RestController = @Controller + @ResponseBody
-
-
@RequestParam
该注解用来标注一个请求参数:
在方法的形参前,可以加可以不加,加了就有特殊含义了
java@RequestMapping(value = "/login1") public String login1(String name) { System.out.println(name); return "index"; } //上述方式在请求 login1 可以不强制传递请求参数,那打印name结果是null @RequestMapping(value = "/login2") public String login2(@RequestParam String name) { System.out.println(name); return "index"; } //上述方式在请求 login2 强制必须传递请求参数,那打印name结果就是请求参数传的值 //http://localhost:8080/login3?name=tom @RequestMapping(value = "/login3") public String login3(@RequestParam("name") String username) { System.out.println(username); return "index"; } //上述方式在请求 login3 的请求参数是name 指定请求参数的名字,用于处理前后端参数不一致 //@RequestParam(value = "name") 和 @RequestParam(name = "name") 等同于 @RequestParam("name") //http://localhost:8080/login4 @RequestMapping(value = "/login4") public String login4(@RequestParam(name = "name", required = false) String username) { System.out.println(username); return "index"; } // required = false,默认是true 代表着必须传递参数,如果设置false代表可以不传递参数,不传为null @RequestMapping(value = "/login5") public String login5(@RequestParam(name = "name",defaultValue = "zs") String username) { System.out.println(username); return "index"; } //设置默认值,这样required就不需要在写了。
-
value
:@RequestParam(value="username")
等同于@RequestParam("username")
,对应请求参数的键 -
required
:参数是否必填 -
defaultValue
:设置默认值
-
-
@PathVariable
将路径的一部分作为请求参数,RESTful的基础。
java//http://localhost:8080/findById/1 @RequestMapping("/findById/{id}") public String findById(@PathVariable Integer id) { System.out.println(id); return "index"; } //请求参数就不可以是?号。参数必须是目录形式。 @RestController @RequestMapping("/person") public class PersonController { @GetMapping("/findById/{id}") public String findById(@PathVariable Integer id) { System.out.println(id); return null; } @PutMapping("/updateById/{id}") public String updateById(@PathVariable Integer id) { System.out.println(id); return null; } @DeleteMapping("/deleteById/{id}") public String deleteById(@PathVariable Integer id) { System.out.println(id); return null; } @GetMapping("/findByIdOrName/{id}/{name}") public String findByIdOrName(@PathVariable Integer id, @PathVariable String name) { System.out.println(id); System.out.println(name); return null; } }
RESTful
RESTful就是一个url的编写风格。使用RESTful就不需要用❓问号分割请求参数了。
一个严格的RESTful中是不可能存在❓的。
一个URl对应一个资源,请求方式确定一个动作。
ps:有一张person表id有1 2 3 ,name有zs,lisi,wangwu。
POST请求:就是向数据库中添加数据。/person,代表把person对象添加到表中
DELETE请求:就是向数据库中删除一条数据。 /person/1 ,代表把person表中id为1的 删除
PUT请求:就是向数据库修改一条数据。/person/1,代表把person表中id为1的 修改
GET请求:就是向数据库表查询数据。/person/3, 代表把person表中id为3的 查询出来
3. 静态资源访问
springboot中放置静态资源的目录,常用有static和templates目录,只要把静态资源放到这几个目录下,就能直接访问到
XML
http://localhost:8080/login.html
XML
http://localhost:8080/login1.html
static目录下静态资源默认是可以被访问到的,但是templates目录下资源默认是访问不到的,需要做配置:
java
spring.web.resources.static-locations=classpath:/templates/,classpath:static/
也可以直接给所有静态资源添加一个前缀,既可统一拦截,又可统一放开
java
spring.mvc.static-path-pattern=/res/**
java
http://localhost:8080/res/login1.html
http://localhost:8080/res/login.html
4. Spring MVC对JSON的支持
Spring MVC对JSON的⽀持⾮常友好,主要⽤到两个注解:@RequestBody,@ResponseBody
也可以配置为其它的JSON⼯具,⽐如fastjson,⾃学如何配置。
为什么学习Jackson,因为它是SpringMVC默认的,方便配置使用。
@RequestBody
java
public class User {
private Integer id;
private String username;
private String password;
getter&& setter
}
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
</head>
<body>
<button id="btn1">前端发送json到后端</button>
<button id="btn2">接收后端数据</button>
<script>
$("#btn1").click(function () {
$.ajax({
type: "post",
url: "http://localhost:8080/sendJson",
data: '{"username":"zs","password":"123456","id":20}',
contentType: 'application/json' //告诉服务器 前端发的数据是json格式
})
})
$("#btn2").click(function () {
$.ajax({
type: "get",
url: "http://localhost:8080/receiveJson",
// url: "http://localhost:8080/receiveJsonData",
success(res) {
console.log(res);
}
})
})
</script>
</body>
</html>
Handler部分
java
package com.whitecamellia.springboot01.controller;
import com.whitecamellia.springboot01.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Petrel
* @create 2022-08-04 6:59 上午
*/
@Controller
public class JsonController {
@PostMapping("/sendJson")
public String jsonTest1(@RequestBody User user) {
//@RequestBody 前端来的json字符串在请求体中
System.out.println(user);
return "index";
}
}
测试:查看控制台是否接受到前端发送过来的json数据。
@ResponseBody
JavaScript部分
javascript
<script>
$("#btn2").click(function () {
$.ajax({
type: "get",
url: "http://localhost:8080/receiveJson",
// url: "http://localhost:8080/receiveJsonData",
success(res) {
console.log(res);
}
})
</script>
Handler部分
java
// login.html
@GetMapping("/receiveJson")
@ResponseBody //代表这得到的返回值要作为响应体 返回给前端,就不会走试图解析器
public List<User> jsonTest2() {
User user = new User();
user.setUsername("zs");
user.setPassword("123456");
user.setId(20);
List<User> list = new ArrayList<>();
list.add(user);
return list;
}
@GetMapping("/receiveJsonData")
@ResponseBody
public Map<String, Object> jsonTest3() {
User user = new User();
user.setUsername("zs");
user.setPassword("123456");
user.setId(20);
List<User> list = new ArrayList<>();
list.add(user);
Map<String, Object> map = new HashMap<>();
map.put("code", 200);
map.put("message", "数据请求成功!");
map.put("data", list);
return map;
}
测试:查看前端页面console页面,是否拿到后端响应过来的数据。
注意:@ResponseBody 注解一定要放在方法上面,标注,不要放到方法返回值处标注。
@ResponseBody 注解也可以放到类上,这样该类内部每个方法都会隐式被标注为@ResponseBody。
Fastjson
XML
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.10</version>
</dependency>
5. 拦截器
登录需求
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
</head>
<body>
<div>
账号:<input type="text" id="username">
</div>
<div>
密码:<input type="password" id="password">
</div>
<div>
<button id="btn">登录</button>
</div>
<script>
$("#btn").click(() => {
$.ajax({
url: "http://localhost:8080/login",
method: "post",
data: {
username: $("#username").val(),
password: $("#password").val()
},
success(res) {
console.log(res)
if (res.code == 200) {
location.href = "index.html"
} else {
alert("登录失败")
}
}
})
})
</script>
</body>
</html>
html
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
</head>
<body>
<h1>Home主页</h1>
<h3 id="info">
</h3>
<script>
$.ajax({
url: "http://localhost:8080/findAll",
method: "get",
success(res) {
$("#info").html(res.data)
}
})
</script>
</body>
</html>
java
package com.whitecamellia.uploaddemo.controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
/**
* @author Petrel
* @create 2022-08-04 10:05 下午
*/
@RestController
public class LoginController {
@PostMapping("/login")
public Map<String, Object> login(String username, String password, HttpSession session) {
session.setAttribute("username", username);
Map<String, Object> map = new HashMap();
if ("admin".equals(username) && "123456".equals(password)) {
map.put("code", 200);
map.put("success", "登录成功");
return map;
} else {
map.put("code", 200);
map.put("success", "登录失败");
return map;
}
}
}
java
package com.whitecamellia.uploaddemo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Petrel
* @create 2022-08-04 3:52 下午
*/
@RestController
public class TestController {
@GetMapping("/findAll")
public Map<String, Object> findAll(HttpSession session) {
Object username = session.getAttribute("username");
Map<String, Object> map = new HashMap<>();
List<String> list = new ArrayList();
if (username == null) {
list.add("用户未登录,不能获取数据");
map.put("code", 403);
map.put("message", "用户未登录");
map.put("data", list);
}
list.add("用户已登录,可以获取数据");
map.put("code", 200);
map.put("message", "用户已登录");
map.put("data", list);
return map;
}
}
创建拦截器
实现HandlerInterceptor
接口
java
创建interceptor package
/**
* 创建拦截器
*/
package com.whitecamellia.uploaddemo.controller.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
Object username = session.getAttribute("username");
if (username != null) {
return true; // 放行
} else {
Map<String, Object> map = new HashMap<>();
response.setHeader("content-type", "application/json");
map.put("code", 403);
map.put("message", "No Access");
// java对象转成json字符串
String str = JSONArray.toJSONString(map);
// ObjectMapper objectMapper = new ObjectMapper();
// String s = objectMapper.writeValueAsString(map);
// response.addHeader("charset", "utf8");
PrintWriter writer = response.getWriter();
writer.write(str);
// json字符串 转成 java对象
// Map map1 = JSONObject.toJavaObject((JSON) o, Map.class);
System.out.println("拦截器工作了");
return false; // 拦截
}
}
}
配置拦截器
java
创建config package
package com.whitecamellia.uploaddemo.controller.config;
import com.whitecamellia.uploaddemo.controller.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
// 将配置注入容器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/*.html")
.excludePathPatterns("/login");
}
}
跨域配置
java
package com.whitecamellia.uploaddemo.controller.config;
import com.whitecamellia.uploaddemo.controller.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author Petrel
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/*.html")
.excludePathPatterns("/login");
}
/**
* 允许跨域调用的过滤器
*/
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
//允许所有域名进行跨域调用
config.addAllowedOriginPattern("*");
//允许跨越发送cookie
config.setAllowCredentials(true);
//放行全部原始头信息
config.addAllowedHeader("*");
//允许所有请求方法跨域调用
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}