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 请求映射原理

springboot 重定向 url_java

FrameworkServlet继承HttpServlet并重写了doGet()方法:

springboot 重定向 url_springboot 重定向 url_02

而doGet()最终还是要执行processRequest()这个方法,而processRequest()内部的核心方法是doService():

springboot 重定向 url_spring boot_03

doService()是一个抽象方法:

protected abstract void doService(HttpServletRequest var1, HttpServletResponse var2) throws Exception;

在DispatcherServlet中有doService()的实现,而doService()中的核心是调用本类的doDispatch()方法,所以研究doDispatch()方法是最终的方法

springboot 重定向 url_web_04

对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

打断点,发送请求并进入这个方法:

springboot 重定向 url_web_05

可以看到,这个方法内部是将所有的handlerMappings用迭代器进行匹配:

springboot 重定向 url_spring boot_06

这些handlerMappings有(index页发送的是Get请求):

springboot 重定向 url_springboot 重定向 url_07

进入第一个mapping,我们可以看到RequestMapping中有我们在控制器写的各种处理请求的方法

springboot 重定向 url_spring boot_08

进入getHandler()方法,可以看到调用了getHandlerInternal()来处理:

springboot 重定向 url_java_09

进入getHandlerInternal()方法,可以看到调用了父类的getHandlerInternal()方法,再次打断点

springboot 重定向 url_springboot 重定向 url_10

在这个方法里再进行找路径以及请求方法:

springboot 重定向 url_session_11

总结:所有的请求映射都在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

矩阵变量:

springboot 重定向 url_spring boot_12

配置:

@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:

springboot 重定向 url_springboot 重定向 url_13

json:

springboot 重定向 url_web_14

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.表达式

springboot 重定向 url_session_15

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 属性优先级

springboot 重定向 url_spring boot_16

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>

效果:

springboot 重定向 url_session_17