1.请求映射
1.1 rest使用与原理
1.1.1 使用
- Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
- 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
- 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
- 核心Filter;HiddenHttpMethodFilter
- 用法: 表单method=post,隐藏域 _method=put
- SpringBoot中手动开启
spring:
mvc:
hiddenmethod:
filter:
enabled: true
Controller:
@RestController
public class HelloController {
@GetMapping("/user")
public String getUser(){
return "GET-张三";
}
@PostMapping("/user")
public String saveUser(){
return "POST-张三";
}
@PutMapping("/user")
public String putUser(){
return "PUT-张三";
}
@DeleteMapping("/user")
public String deleteUser(){
return "DELETE-张三";
}
}
测试页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
<body>
<h1>测试REST风格</h1>
<form method="post" action="/user"><input type="submit" value="POST"></form>
<form method="get" action="/user"><input type="submit" value="GET"></form>
<form method="post" action="/user">
<input type="hidden" name="_method" value="put">
<input type="submit" value="PUT">
</form>
<form method="post" action="/user">
<input type="hidden" name="_method" value="delete">
<input type="submit" value="DELETE">
</form>
</body>
</html>
自定义把_method 这个名字换成我们自己喜欢的:自定义HiddenHttpMethodFilter
@Configuration
public class WebConfiguration {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
hiddenHttpMethodFilter.setMethodParam("_m");
return hiddenHttpMethodFilter;
}
}
1.1.2 原理
Rest原理(表单提交要使用REST的时候)
- 表单提交会带上**_method=PUT**
- 请求过来被HiddenHttpMethodFilter拦截
- 请求是否正常,并且是POST
- 获取到**_method**的值。
- 兼容以下请求;PUT.DELETE.PATCH
- 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
- 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的.
- 为什么需要手动开启,以及可以自己配置HiddenHttpMethodFilter?
- 为什么拦截的是_method?
- 为什么表单请求方式需要是POST?
Rest使用客户端工具,
- 如PostMan直接发送Put、delete等方式请求,无需Filter
1.2 请求映射原理
FrameworkServlet继承HttpServlet并重写了doGet()方法:
而doGet()最终还是要执行processRequest()这个方法,而processRequest()内部的核心方法是doService():
doService()是一个抽象方法:
protected abstract void doService(HttpServletRequest var1, HttpServletResponse var2) throws Exception;
在DispatcherServlet中有doService()的实现,而doService()中的核心是调用本类的doDispatch()方法,所以研究doDispatch()方法是最终的方法
对Spring MVC功能分析从DispatcherServlet的doDispatch()方法开始:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
//找到当前请求使用哪个Handler(Controller的方法)处理
mappedHandler = this.getHandler(processedRequest);
//HandlerMapping:处理器映射。/xxx->>xxxx
打断点,发送请求并进入这个方法:
可以看到,这个方法内部是将所有的handlerMappings用迭代器进行匹配:
这些handlerMappings有(index页发送的是Get请求):
进入第一个mapping,我们可以看到RequestMapping中有我们在控制器写的各种处理请求的方法
进入getHandler()方法,可以看到调用了getHandlerInternal()来处理:
进入getHandlerInternal()方法,可以看到调用了父类的getHandlerInternal()方法,再次打断点
在这个方法里再进行找路径以及请求方法:
总结:所有的请求映射都在HandlerMapping中。
- SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
- SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
- 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
- 如果有就找到这个请求对应的handler
- 如果没有就是下一个 HandlerMapping
- 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping
2.普通参数与基本注解
2.1 注解
@PathVariable、@RequesHeader
@GetMapping("/car/id/{id}/owner/{owner}")
public Map<String, Object> getCar(@PathVariable("id") Integer id,
@PathVariable("owner") String owner,
@PathVariable Map<String, String> kv,
@RequestHeader("host") String host,
@RequestHeader Map<String, String> headers){
Map map = new HashMap();
map.put("id", id);
map.put("owner", owner);
map.put("kv", kv);
map.put("host", host);
map.put("headers", headers);
return map;
}
//前端页面:<a href="/car/id/11/owner/lisi">测试@PathVariable和@RequestHeader</a><br/>
@RequestParam、@CookieValue
@GetMapping("/person")
public Map<String, Object> getInfo(@RequestParam("id") Integer id, @RequestParam Map<String, String> info,
@CookieValue("_ga") Cookie cookie, @CookieValue("_ga") String _ga){
Map map = new HashMap();
map.put("id", id);
map.put("info", info);
System.out.println(cookie.getName() + "---->");
return map;
}
//<a href="/person?id=12&username=zhangsan&age=18">测试@RequestParam</a><br/>
@RequestBody
post方法请求才有请求体:
@PostMapping("/save")
public Map postMethod(@RequestBody String content){
Map<String,Object> map = new HashMap<>();
map.put("content",content);
return map;
}
<form action="/save" method="post">
姓名: <input type="text" name="username"><br/>
密码: <input type="text" name="password"><br/>
<input type="submit" value="submit">
</form>
@RequestAttribute
获取请求域的值
@Controller
public class RequestTestController {
@GetMapping("/goto")
public String goToPage(HttpServletRequest request){
request.setAttribute("username", "zhangsan");
request.setAttribute("password", 1234567);
return "forward:/success";
}
@ResponseBody
@GetMapping("/success")
public Map success(@RequestAttribute("username") String username,
@RequestAttribute("password") Integer password,
HttpServletRequest request){
Map map = new HashMap();
map.put("username", username);
map.put("password", password);
map.put("username_re", request.getAttribute("username"));
map.put("password_re", request.getAttribute("password"));
return map;
}
}
@MatrixVariable与UrlPathHelper
矩阵变量:
配置:
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
或者:
@Configuration
public class WebConfiguration{
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer(){
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
}
测试:
//1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
//2、SpringBoot默认是禁用了矩阵变量的功能
// 手动开启:原理。对于路径的处理。UrlPathHelper进行解析。
// removeSemicolonContent(移除分号内容)支持矩阵变量的
//3、矩阵变量必须有url路径变量才能被解析
@GetMapping("/cars/{path}/{info}")
public Map carsSell(@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List<String> brand,
@PathVariable("path") String path,
@MatrixVariable("price") Integer price){
Map<String,Object> map = new HashMap<>();
map.put("low",low);
map.put("brand",brand);
map.put("path",path);
map.put("price", price);
return map;
}
// /boss/1;age=20/2;age=10
@GetMapping("/boss/{bossId}/{empId}")
public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
@MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
Map<String,Object> map = new HashMap<>();
map.put("bossAge",bossAge);
map.put("empAge",empAge);
return map;
}
2.2 Servlet API
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
2.3 复杂参数
Map、**Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、**Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
Map<String,Object> map, Model model, HttpServletRequest request 都是可以给request域中放数据,
request.getAttribute();
2.4 自定义参数
可以自动类型转换与格式化,可以级联封装。
/**
* 姓名: <input name="userName"/> <br/>
* 年龄: <input name="age"/> <br/>
* 生日: <input name="birth"/> <br/>
* 宠物姓名:<input name="pet.name"/><br/>
* 宠物年龄:<input name="pet.age"/>
*/
@Data
public class Person {
private String userName;
private Integer age;
private Date birth;
private Pet pet;
}
@Data
public class Pet {
private String name;
private String age;
}
自定义converter
//1、WebMvcConfigurer定制化SpringMVC的功能
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除;后面的内容。矩阵变量功能就可以生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) {
// 啊猫,3
if(!StringUtils.isEmpty(source)){
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
};
}
3.响应处理
3.1 响应json
jackson.jar+@ResponseBody
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
web场景自动引入了json场景
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
给前端自动返回json数据
@ResponseBody
@GetMapping("/pet")
public Pet getPet(Pet pet){
return pet;
}
<form action="/pet" method="get">
姓名:<input type="text" name="name" value="cat"><br/>
年龄:<input type="text" name="age" value="18"><br/>
<input type="submit" value="submit">
</form>
支持的返回值类型
ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;
3.2 内容协商
根据客户端接收能力不同,返回不同媒体类型的数据
3.2.1 引入xml依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
3.2.2 使用postman测试接受不同类型的数据
只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。
xml:
json:
4.模板引擎thymeleaf
4.1 thymeleaf简介
Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text.
现代化、服务端Java模板引擎
4.2 基本语法
1.表达式
2.字面量
- 文本值: ‘one text’ , ‘Another one!’ **,…**数字: 0 , 34 , 3.0 , 12.3 **,…**布尔值: true , false
- 空值: null
- 变量: one,two,… 变量不能有空格
3.文本操作
- 字符串拼接: +
- 变量替换: |The name is ${name}|
4.数学运算
运算符: + , - , * , / , %
5.布尔运算
运算符: and , or
一元运算: ! , not
6.比较运算
比较: > , < , >= , <= ( gt , lt , ge , le **)**等式: == , != ( eq , ne )
7.条件运算
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
8.特殊操作
无操作: _
4.3 设置属性值-th:attr
设置单个值
<form action="subscribe.html" th:attr="action=@{/subscribe}">
<fieldset>
<input type="text" name="email" />
<input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
</fieldset>
</form>
设置多个值
<img src="../../images/gtvglogo.png" th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
替代写法:
<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">
4.4 迭代
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
4.5 条件运算
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>
<div th:switch="${user.role}">
<p th:case="'admin'">User is an administrator</p>
<p th:case="#{roles.manager}">User is a manager</p>
<p th:case="*">User is some other thing</p>
</div>
4.6 属性优先级
4.7 thymeleaf使用
1.引入starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2.自动配好了thymeleaf
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration { }
自动配好的策略
- 1、所有thymeleaf的配置值都在 ThymeleafProperties
- 2、配置好了 SpringTemplateEngine
- 3、配好了 ThymeleafViewResolver
- 4、我们只需要直接开发页面
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html"; //xxx.html
3.页面开发
html引入命名空间: xmlns:th=“http://www.thymeleaf.org”
<!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>
<h2>
<a href="www.atguigu.com" th:href="${link}">去百度</a> <br/>
<a href="www.atguigu.com" th:href="@{link}">去百度2</a>
</h2>
</body>
</html>
4.8 应用
1.路径构建
<form class="form-signin" action="index.html" method="post" th:action="@{/login}">
这是前端登录页面的表格,点击提交后会请求到相应的controller来处理:
@PostMapping("/login")
public String main(User user, HttpSession session, Model model){
String username = user.getUsername();
String password = user.getPassword();
if("deserts".equals(username) && "123".equals(password)){
session.setAttribute("user", user);
return "redirect:/main.html";
}else{
model.addAttribute("msg", "账号或者密码错误");
return "login";
}
}
@GetMapping("main.html")
public String mainPage(HttpSession session, Model model){
Object user = session.getAttribute("user");
if (user != null){
return "main";
}else{
model.addAttribute("msg", "请重新登录");
return "login";
}
}
直接写入标签内的文本:
<a href="#">[[${session.user.username}]]</a>
2.错误回显
前面处理的用户登录的controller在用户登录失败或着没登录直接访问main页面时,应该返回登录页及给出提示信息,通过model设置request域的值,从前端页面取出来,达到回显的效果
<label style="color:green;" th:text="${msg}"/>
3.抽取重复页面
这里主要参照官网,区分三种抽取的方式。抽取的片段:
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer
页面引入抽取片段的三种方式:
<body>
...
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>
</body>
会导致的结果:
<body>
...
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
<div>
© 2011 The Good Thymes Virtual Grocery
</div>
</body>
区别:insert是插入原引入标签的内部,replace是将原标签给替换成碎片部分的标签和内容,include是去掉标签将碎片内容包含在原标签中。
4.数据渲染(遍历)
controller:
@GetMapping("/dynamic_table")
public String dynamic_table(Model model){
List<User> users = Arrays.asList(new User("aaa", "111"), new User("bbb", "222"),
new User("ccc", "333"), new User("ddd", "444"));
model.addAttribute("users", users);
return "table/dynamic_table";
}
页面:
<table class="display table table-bordered" id="hidden-table-info">
<thead>
<tr>
<th class="hidden-phone">#</th>
<th class="hidden-phone">用户名</th>
<th class="hidden-phone">密码</th>
</tr>
</thead>
<tbody>
<tr class="gradeX" th:each="user,status:${users}">
<td class="hidden-phone" th:text="${status.count}"></td>
<td class="center hidden-phone">[[${user.username}]]</td>
<td class="center hidden-phone" th:text="${user.password}"></td>
</tr>
</tbody>
</table>
效果: