SpringMVC入门

SpringMVC

第1章 SpringMVC概述

未解决问题:

1-2-4:关于指定配置配置文件的问题(已解决)

解决:out\artifacts\springMVC_02_hello_war_exploded\WEB-INF\web.xml(idea中out路径下的预编译文件问题),因为该路径下存在的web中包含之前运行没有注释的SpringMVC配置文件的位置。

1-1 概述MVC模型

image-20220516141021919

image-20220516141039864

image-20220516141103013

image-20220516141114475

1-2 springMVC-01-helloword

1-2-1 导包

image-20220516143509194

image-20220516143551886

1-3 springMVC-02-hello

1-3-1 代码

image-20220516190242165

1-3-2 运行流程:

image-20220516191102352

1-3-3简单了解RequestMapping(可以使用到类上,也可以使用到方法上)

image-20220516191538905

1-3-4 不指定配置文件位置的默认文件(问题:我将springMVC注解了,但是没有报FileNotFoundException异常,此问题已解决)

image-20220517065504854

image-20220517065137796

image-20220517065657584

问题解决:

image-20220517073848191

总结:

将配置文件位置注释,Tomcat会默认到/WEB-INF/中找 前端控制器名(springMVC)-servlet.xml.有以下2种方法解决该异常:

法1:在/WEB-INF路径下配置springMVC-servlet.xml

法2:将图中注释去掉。

1-3-5:url-pattern细节

1.不要使用/*

image-20220517080242723

2.关于前端控制器中的url-pattern的/,覆盖DefaultServlet中的/

image-20220517082140522

image-20220517082118093

image-20220517082300236

image-20220517082324469

image-20220517082624424

image-20220517083321336

补充:解决办法后面讲

image-20220517083330656

3.为什么可以访问jsp页面呢?

image-20220517083742806

也就是说没有覆盖该url-pattern

image-20220517083834638

1-3-6 @RequestMapping详解

1.@RequestMapping标注在类上

注意:两个方法不能同时处理同一个请求

为当前类的所有的方法的请求地址指定一个基准路径

image-20220517091625116

image-20220517091805339

2.@RequesstMapping应用到方法上

image-20220517092658193

1.method属性限定请求方式

image-20220518102327879

image-20220517093403648

改正:使用a标签发送请求默认是get请求

image-20220517093422693

解决:

image-20220517094534191

image-20220517094558733

2.params--限定发送过来的请求参数

image-20220517095921581

发送请求访问结果:

image-20220517100033962

image-20220517100217653

image-20220517101047495

image-20220517101013389

image-20220517101128536

3.从请求头限定访问的请求

image-20220517112140135

image-20220517112225099

image-20220517112523251

代码参考:

/**
 *
 * @Description
 * 从请求头限定访问的请求
 * 只允许opera访问,不允许edge,google浏览器访问
 *
 * edg浏览器请求头User-Agent信息:
 * User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
 * (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.47
 *
 * google浏览器请求头User-Agent信息:
 *User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N)
 * AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54
 * Mobile Safari/537.36
 *
 * opera浏览器请求头User-Agent信息:
 * User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36
 * (KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36 OPR/85.0.4341.75
 *
 * @Date 2022/5/17 10:15
 *
 */
@RequestMapping(value = "/handle04",headers = {"User-Agent=Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36 OPR/85.0.4341.75"})
public String handle04() {
    System.out.println("RequestMappingTestController...handle04");
    return "success";
}
4.补充

image-20220517154524232

3.@RequestMapping支持Ant风格的URL

image-20220517154607522

1.?(只匹配一个字符)

image-20220517160411427

image-20220517160447410

image-20220517160629395

image-20220517160656674

2.*(匹配多个字符)

image-20220517161204066

image-20220517161236176

image-20220517161428705

3.**(可以表示多层路径)

image-20220517161941588

image-20220517162056253

image-20220517162023057

用**匹配多次路径

image-20220517162246692

image-20220517162317875

代码参考:

package com.xu1.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @auther xu
 * @Description
 * @RequestMapping模糊匹配功能
 * @date 2022/5/17 - 15:48
 * @Modified By:
 */
@RequestMapping("/antTest")
@Controller
public class RequestMappingTest {

    @RequestMapping("/antTest01")
    public String antTest01() {
        System.out.println("antTest01...");
        return "success";
    }

    /**
     *
     * @Description
     * 匹配任意一个字符(0个或多个都不行)
     * @Date 2022/5/17 16:09
     *
     */
    @RequestMapping("/antTest0?")
    public String antTest02() {
        System.out.println("antTest02...");
        return "success";
    }

    /**
     *
     * @Description
     * 匹配任意多个字符(包括0个字符)
     * @Date 2022/5/17 16:08
     */
    @RequestMapping("/antTest0*")
    public String antTest03() {
        System.out.println("antTest03...");
        return "success";
    }

    /**
     *
     * @Description
     * 匹配多层路径
     * @Date 2022/5/17 16:08
     */
    @RequestMapping("/a/**/antTest01")
    public String antTest04() {
        System.out.println("antTest04...");
        return "success";
    }
}
4.@PathVariable--获取路径上的占位符

image-20220517163900695

image-20220517164041868

image-20220517164104385

代码参考:

/**
 *
 * @Description
 * 区别于Ant,我可以获取占位符的值
 * 注意一个占位符也只能代表一层路径。
 * @Date 2022/5/17 16:30
 *
 */
@RequestMapping("/user/{id}")
public String pathVariableTest(@PathVariable("id")String id) {
    System.out.println("路径上的占位符为:"+id);
    return "success";
}

1-3-7 Rest风格

1.概念

image-20220517165249754

image-20220517165050870

image-20220517165202106

2.环境搭建

image-20220518103818185

image-20220518111733868

3.Rest风格的增删改

image-20220520092002319

使用步骤:

image-20220520095700051

image-20220520095821846

结果:

Tomcat8.0以上服务器,导致的jsp页面的错误以及解决:

image-20220520094452439

image-20220520094648483

代码参考:

image-20220520100501448

BookController.java

package com.xu1;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * @auther xu
 * @Description
 * @date 2022/5/18 - 11:02
 * @Modified By:
 */
@Controller
public class BookController {

    /**
     *
     * @Description
     * 查询图书
     * @Date 2022/5/18 11:12
     *
     */
    @RequestMapping(value = "/book/{bookId}",method = RequestMethod.GET)
    public String getBook(@PathVariable("bookId")Integer id) {
        System.out.println("查询到了"+id+"号图书");
        return "success";
    }
    /**
     *
     * @Description
     * 删除图书
     * @Date 2022/5/18 11:13
     *
     */
    @RequestMapping(value = "/book/{bookId}",method = RequestMethod.DELETE)
    public String deleteBook(@PathVariable("bookId")Integer id) {
        System.out.println("删除了"+id+"号图书");
        return "success";
    }

    /**
     *
     * @Description
     * 图书的更新
     * @Date 2022/5/18 11:08
     *
     */
    @RequestMapping(value = "/book/{bookId}",method = RequestMethod.PUT)
    public String updateBook(@PathVariable("bookId")Integer id) {
        System.out.println("更新了"+id+"号图书");
        return "success";
    }

    /**
     *
     * @Description
     * 添加图书不需要携带bookId
     * @Date 2022/5/18 11:11
     *
     */
    @RequestMapping(value = "/book",method = RequestMethod.POST)
    public String addBook() {
        System.out.println("添加了新图书");
        return "success";
    }

}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <!--        指定SpringMVC配置文件的位置-->
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springMVC.xml</param-value>
        </init-param>
        <!--
        servlet启动加载,servlet原本是第一次访问创建对象
        load-on-startup:服务器启动时创建对象;值越小优先级越高,越先创建对象;
        -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>

        <!--        如果是/*,也会拦截jsp页面-->
        <!--        <url-pattern>/*</url-pattern>-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>hiddenHttpMethodFilter</filter-name>
<!--        拦截所有请求-->
        <url-pattern>/*</url-pattern>
    </filter-mapping>


</web-app>

index.jsp:

<%--
  Created by IntelliJ IDEA.
  User: 许荣
  Date: 2022/5/17
  Time: 17:03
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
  <title>$Title$</title>
</head>

<body>
<h3>Rest风格探索</h3>

<%--
页面如何发其他方式的请求?(除了get/post)
按照如下要求:
1.创建一个post类型的表单
2.表单项中携带一个_method的参数
3.这个_method可以指定其他方式发送请求(eg:delete)
--%>
<a href="book/1">查询图书</a><br/>

<%--可以发送delete请求--%>
<form action="book/1" method="post">
  <input name="_method" value="delete">
  <input type="submit" value="删除1号图书">
</form>
<%--可以发送put请求--%>
<form action="book/1" method="post">
  <input name="_method" value="put">
  <input type="submit" value="更新1号图书">
</form>


<form action="book" method="post">
  <input type="submit" value="添加1号图书"/>
</form><br/>



</body>
</html>

success.jsp:

<%--
  Created by IntelliJ IDEA.
  User: 许荣
  Date: 2022/5/18
  Time: 10:56
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>成功跳转页面!</h1>
</body>
</html>

1-4 获取请求参数的值--@RequestParam(project4)

image-20220520142710969

image-20220520140128682

image-20220520140526686

image-20220520140926392

image-20220520141129831

image-20220520141426117

image-20220520141715431

1-5 获取请求头中的参数值

image-20220520142731489

image-20220520142406482

image-20220520143122188

1-6 获取某一个cookid的值

image-20220520143410322

image-20220520143751257

image-20220520144305886

image-20220520144908149

1-7 SpringMVC-POJO(Plain Ordinary Java Object)--简单的java对象

image-20220520151545666

代码:

address.java
package com.xu1.address;

/**
 * @auther xu
 * @Description
 * @date 2022/5/20 - 15:06
 * @Modified By:
 */
public class Address {
    private String province;
    private String city;
    private String villige;

    public Address() {
    }

    public Address(String province, String city, String villige) {
        this.province = province;
        this.city = city;
        this.villige = villige;
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getVillige() {
        return villige;
    }

    public void setVillige(String villige) {
        this.villige = villige;
    }

    @Override
    public String toString() {
        return "Address{" +
                "province='" + province + '\'' +
                ", city='" + city + '\'' +
                ", villige='" + villige + '\'' +
                '}';
    }
}
Book.java:
package com.xu1.book;

import com.xu1.address.Address;

/**
 * @auther xu
 * @Description
 *     书名:<input type="text" name="bookName"/><br/>
 *     作者:<input type="text" name="author"/><br/>
 *     库存:<input type="text" name="stock"/><br/>
 *     销量:<input type="text" name="sale"/><br/>
 * @date 2022/5/20 - 14:54
 * @Modified By:
 */
public class Book {
    private String bookName;
    private String author;
    private Double price;
    private Integer stock;
    private Integer sale;
    private Address address;

    public Book() {
    }

    public Book(String bookName, String author, Double price, Integer stock, Integer sale,Address address) {
        this.bookName = bookName;
        this.author = author;
        this.price = price;
        this.stock = stock;
        this.sale = sale;
        this.address = address;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public Integer getStock() {
        return stock;
    }

    public void setStock(Integer stock) {
        this.stock = stock;
    }

    public Integer getSale() {
        return sale;
    }

    public void setSale(Integer sale) {
        this.sale = sale;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Book{" +
                "bookName='" + bookName + '\'' +
                ", author='" + author + '\'' +
                ", price=" + price +
                ", stock=" + stock +
                ", sale=" + sale +
                ", address=" + address +
                '}';
    }
}
HelloRequest控制器:
package com.xu1.request;

import com.xu1.book.Book;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * @auther xu
 * @Description
 * @date 2022/5/20 - 11:27
 * @Modified By:
 */
@Controller
public class HelloRequest {

    @RequestMapping(value = "/hello")
    public String handle1(@RequestParam(value = "user",required = false) String username,
                          @RequestHeader(value = "hhaha",required = false) String userAgent,
                          @CookieValue("JSESSIONID") String cookValue) {
        System.out.println("请求参数的值为:"+username);
        System.out.println("获取浏览器请求头中的User-Agent值为:"+userAgent);
        System.out.println("JSESSIONID所对应的cook值为:"+cookValue);
        return "success";
    }
    @RequestMapping("/book")
    public String getBook(Book book) {
        System.out.println("我保存的图书为:"+book);
        return "success";
    }

   
}

index.jsp:
<%--
  Created by IntelliJ IDEA.
  User: 许荣
  Date: 2022/5/20
  Time: 11:23
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
  <title>$Title$</title>
</head>
<body>
  <a href="hello">测试?username=Tom</a>
  <form action="book" method="post">
    书名:<input type="text" name="bookName"/><br/>
    作者:<input type="text" name="author"/><br/>
    价格:<input type="text" name="price"><br/>
    库存:<input type="text" name="stock"/><br/>
    销量:<input type="text" name="sale"/><br/>
    <hr/>
    作者位置
    省:<input type="text" name="address.province">
    市:<input type="text" name="address.city">
    村:<input type="text" name="address.villige">

    <input type="submit" value="提交数据">

  </form>
</body>
</html>

结果:

image-20220520152330236

image-20220520152836073

1-8 springMVC可以直接在参数上写原生的API(application programmer interface)

image-20220520170654300

image-20220520170944007

1-9 乱码问题的解决

1.用以解决get请求导致的乱码问题

image-20220520171603642

image-20220520172353107

2.解决post乱码问题

image-20220520174151215

更正:图中的"相应"改为"响应"

image-20220520174811196

结果:

image-20220520174901148

image-20220520174722491

总结:

image-20220520172954193

10:字符编码过滤器必须放到所有过滤器的前面

image-20220520180528859

image-20220520180756198

第2章:SpringMVC将数据转发给其他页面

2-1 SpringMVC将数据转发给其他页面

1.方式1:方法处传入Map,或者Model或者ModelMap

可以在方法处传入Map,或者Model或者ModelMap。这些参数里面保存的所有数据都会放在request域中。可以页面获取.
Map,Model,ModelMap:最终都是BindingAwareModelMap

image-20220523064019538

结果:

image-20220523064134308

image-20220523064224658

image-20220523064329327

源码探究:

image-20220523064851034

image-20220523064915234

image-20220523065232421

image-20220523065910088

代码参考:

package com.xu1.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Map;

/**
 * @auther xu
 * @Description
 * SpringMVC除了在方法上使用原生API(request,session)外,还有什么方式可以进行页面间的数据传递
 * 方式1:
 * 可以在方法处传入Map,或者Model或者ModelMap。这些参数里面保存的所有数据都会放在域中。可以页面获取.
 * Map,Model,ModelMap:最终都是BindingAwareModelMap
 *
 *
 * @date 2022/5/21 - 19:26
 * @Modified By:
 */
@Controller
public class OutputController {
    @RequestMapping("hello01")
    public String handle01(Map<String,Object> map){
        map.put("username","小明");
        System.out.println("map接口的实现类是:"+map.getClass());
        System.out.println("handle01...");
        return "success";
    }
    @RequestMapping("hello02")
    public String handle02(Model model){
        model.addAttribute("username","小华");
        System.out.println("map接口的实现类是:"+model.getClass());
        System.out.println("handle02...");
        return "success";
    }
    @RequestMapping("hello03")
    public String handle03(ModelMap modelMap){
        m的类是:"+modelMap.getClass());
        System.out.println("handle03...");
        return "success";
    }
}

2.方式2:方法返回值为ModelAndView

image-20220523094446063

image-20220523094534648

image-20220523094635722

3.方式3:SessionAttributes(给session中暂存一些值)

image-20220523095721626

image-20220523100518007

image-20220523100607432

image-20220523100653938

2-2 ModelAttribute解决全字段更新问题(有了mybatis,该注解使用较少)

1.全字段更新问题:实际中,更新数据只需要更新一部分,但是全字段更新会导致所有数据更新,有些原来不变的数据会由于全字段更新导致赋值为空

image-20220523115738291

image-20220523111346504

2.解决全字段更新问题:使用@ModelAttribute注解

image-20220523120630649

image-20220523120425150

代码参考:

package com.xu1.controller;

import com.xu1.book.Book;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Map;


/**
 * @auther xu
 * @Description
 * @date 2022/5/23 - 11:04
 * @Modified By:
 */
@Controller
public class ModelAttributeController {

    @RequestMapping("/modelAttribute")
    public String updateBook(@ModelAttribute("haha") Book book) {
        System.out.println("页面要提交过来的图书信息为:"+book);
        return "success";
    }

    @ModelAttribute
    public void hahaMyModelAttribute(Map<String,Object> map) {
        //模拟从数据库总查到的数据并封装为对象
        Book  book = new Book("西游记","吴承恩",89.99,100,50);
        System.out.println("模拟数据库中查到的图书信息是:"+book);
        map.put("haha",book);
        System.out.println("modelAttribute方法...查询了图书并给你保存起来了");
    }
}
<form action="modelAttribute" method="post">
  书名:西游记<br/>
  作者:吴承恩<br/>
  价格:<input type="text" name="price"><br/>
  库存:<input type="text" name="stock"><br/>
  销量:<input type="text" name="sale"><br/>
  <input type="submit" value="提交数据">
</form>

3.ModelAttribute原理分析

image-20220523121514917

验证是否为同一个map:true

image-20220523122411245

验证是否为同一个book对象:true

image-20220523122716115

代码参考:

package com.xu1.controller;

import com.xu1.book.Book;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Map;


/**
 * @auther xu
 * @Description
 * @date 2022/5/23 - 11:04
 * @Modified By:
 */
@Controller
public class ModelAttributeController {

    private Map<String,Object> map1;
    private Map<String,Object> map2;

    @RequestMapping("/modelAttribute")
    public String updateBook(@ModelAttribute("haha") Book book,Map<String,Object> map) {
        map2 = map;
        System.out.println("map1域map2是否为同一个map:"+(map1 == map2));
        System.out.println("验证是否为同一个实例对象:"+(book == map.get("haha")));
        System.out.println("页面要提交过来的图书信息为:"+book);
        return "success";
    }
    @ModelAttribute
    public void hahaMyModelAttribute(Map<String,Object> map) {
        //模拟从数据库总查到的数据并封装为对象
        Book  book = new Book("西游记","吴承恩",89.99,100,50);
        System.out.println("模拟数据库中查到的图书信息是:"+book);
        map.put("haha",book);

        map1  = map;
        System.out.println("modelAttribute方法...查询了图书并给你保存起来了");
    }
}

2-3 SpringMVC源码分析()

2-3-1 DispatcherServlet结构分析

结构参考:

DispatcherServlet

找到doGet与doPost方法:

image-20220523142536925

image-20220523142633765

image-20220523142749516

image-20220523142932224

image-20220523143020072

2-3-2 doDispatch源码分析

/**
 * Process the actual dispatching to the handler.
 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   HttpServletRequest processedRequest = request;
   HandlerExecutionChain mappedHandler = null;
   boolean multipartRequestParsed = false;

   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

   try {
      ModelAndView mv = null;
      Exception dispatchException = null;

      try {
           //1.检查是否为文件上传请求
         processedRequest = checkMultipart(request);
         multipartRequestParsed = processedRequest != request;

         // Determine handler for the current request.
          //2.检查哪一个类(控制器)用来处理请求
         mappedHandler = getHandler(processedRequest);
           //3.如果没有哪一个处理器能够处理该请求,将抛出异常或者404
         if (mappedHandler == null || mappedHandler.getHandler() == null) {
            noHandlerFound(processedRequest, response);
            return;
         }

         // Determine handler adapter for the current request.
         //4.能拿到执行这个类的所有方法的适配器;(反射工具:AnnotationMethodHandlerAdater)
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // Process last-modified header, if supported by the handler.
         String method = request.getMethod();
         boolean isGet = "GET".equals(method);
         if (isGet || "HEAD".equals(method)) {
            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
            if (logger.isDebugEnabled()) {
               String requestUri = urlPathHelper.getRequestUri(request);
               logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
            }
            if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
               return;
            }
         }

         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }

         try {
            // Actually invoke the handler.
             //5.适配器来执行目标方法:将目标方法执行完后的返回值作为视图名,设置保存到ModelAndView中
          //目标方法无论怎么写,最终适配器执行完成之后都会将执行后的信息封装成ModelAndView
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
         }
         finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
               return;
            }
         }

         applyDefaultViewName(request, mv);//如果目标方法没有视图名,设置一个默认的视图名
          //6.根据方法最终执行完成后封装的ModelAndView:转发到对应的页面,而且ModelAndView中的数据可以从请求域中获取
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
         dispatchException = ex;
      }
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   catch (Exception ex) {
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
   }
   catch (Error err) {
      triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
   }
   finally {
      if (asyncManager.isConcurrentHandlingStarted()) {
         // Instead of postHandle and afterCompletion
         mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
         return;
      }
      // Clean up any resources used by a multipart request.
      if (multipartRequestParsed) {
         cleanupMultipart(processedRequest);
      }
   }
}

image-20220526205340594

image-20220526210147559

image-20220526210602458

老师的代码参考:

image-20220523153134102

image-20220523153406221

image-20220523155745208

2-3-3 :mappedHandler = getHandler(processedRequest);

image-20220527070309676

image-20220523164050405

image-20220527071317688

image-20220527072128724

小结一波:

handlerMap中的值从何得来?

HandlerMapping:处理器映射;他里面保存了每一个处理器能处理那些请求的映射信息:

handlerMap:Ioc容器启动创建Controller对象的时候扫描每一个处理器(每一个方法)都能处理什么请求,保存在HandlerMapping的handlerMap属性中;下一次请求过来,就来看哪一个handlerMapping中的这个请求映射值就行了;

源码参考:

	/**
	 * Return the HandlerExecutionChain for this request.
	 * <p>Tries all handler mappings in order.
	 * @param request current HTTP request
	 * @return the HandlerExecutionChain, or {@code null} if no handler could be found
	 */
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		for (HandlerMapping hm : this.handlerMappings) {
			if (logger.isTraceEnabled()) {
				logger.trace(
						"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
			}
			HandlerExecutionChain handler = hm.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
		return null;
	}

2-3-4 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

image-20220523204529186

image-20220527081214820

image-20220527081518634

image-20220527081742661

image-20220527082445456

源码参考:

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		for (HandlerAdapter ha : this.handlerAdapters) {
			if (logger.isTraceEnabled()) {
				logger.trace("Testing handler adapter [" + ha + "]");
			}
			if (ha.supports(handler)) {
				return ha;
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}

2-3-5 SpringMVC9大组件

image-20220524084617126

/** MultipartResolver used by this servlet */
//文件上传解析器
@Nullable
private MultipartResolver multipartResolver;

/** LocaleResolver used by this servlet */
//区域信息解析器:与国际化有关
@Nullable
private LocaleResolver localeResolver;

/** ThemeResolver used by this servlet */
//主题解析器:强大的主题效果更换
@Nullable
private ThemeResolver themeResolver;

/** List of HandlerMappings used by this servlet */
//handler映射信息
@Nullable
private List<HandlerMapping> handlerMappings;

/** List of HandlerAdapters used by this servlet */
//handler的适配器
@Nullable
private List<HandlerAdapter> handlerAdapters;

/** List of HandlerExceptionResolvers used by this servlet */
//SpringMVC强大的异常解析功能:异常解析器
@Nullable
private List<HandlerExceptionResolver> handlerExceptionResolvers;

/** RequestToViewNameTranslator used by this servlet */
@Nullable
private RequestToViewNameTranslator viewNameTranslator;

/** FlashMapManager used by this servlet */
//FlashMapManager:SpringMVC中运行重定向携带数据功能
@Nullable
private FlashMapManager flashMapManager;

/** List of ViewResolvers used by this servlet */
//视图解析器
@Nullable
private List<ViewResolver> viewResolvers;

image-20220528084045126

1.initStrategies--九大组件初始化的地方

image-20220527135808693

用一个例子来说明--initHandlerMappings:

/**
	 * Initialize the HandlerMappings used by this class.
	 * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
	 * we default to BeanNameUrlHandlerMapping.
	 */
	private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;

		if (this.detectAllHandlerMappings) {
			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				OrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerMapping later.
			}
		}

		// Ensure we have at least one HandlerMapping, by registering
		// a default HandlerMapping if no other mappings are found.
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
			}
		}
	}

image-20220527140311263

image-20220527141702275

image-20220527141834371

image-20220527142933859

2-3-6 ha.handle:使用反射执行目标方法(难)

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
@RequestMapping("/modelAttribute")
public String updateBook(
        @RequestParam("author") String author,
        @ModelAttribute("haha") Book book, Map<String,Object> map) {
    map2 = map;
    System.out.println("map1域map2是否为同一个map:"+(map1 == map2));
    System.out.println("验证是否为同一个实例对象:"+(book == map.get("haha")));
    System.out.println("页面要提交过来的图书信息为:"+book);
    return "success";
}

image-20220527143837205

1.分析invokeHandlerMethod(request,response,handler):

image-20220527144920203

image-20220527150331183

(提前执行)

2.分析:invokeHandlerMethod(..):
public final Object invokeHandlerMethod(Method handlerMethod, Object handler,
      NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {

   Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod);
   try {
      boolean debug = logger.isDebugEnabled();
      for (String attrName : this.methodResolver.getActualSessionAttributeNames()) {
         Object attrValue = this.sessionAttributeStore.retrieveAttribute(webRequest, attrName);
         if (attrValue != null) {
            implicitModel.addAttribute(attrName, attrValue);
         }
      }
      for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) {
         Method attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod);
         Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel);
         if (debug) {
            logger.debug("Invoking model attribute method: " + attributeMethodToInvoke);
         }
         String attrName = AnnotationUtils.findAnnotation(attributeMethod, ModelAttribute.class).value();
         if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) {
            continue;
         }
         ReflectionUtils.makeAccessible(attributeMethodToInvoke);
          //提前运行ModelAttribute
         Object attrValue = attributeMethodToInvoke.invoke(handler, args);
         if ("".equals(attrName)) {
            Class<?> resolvedType = GenericTypeResolver.resolveReturnType(attributeMethodToInvoke, handler.getClass());
            attrName = Conventions.getVariableNameForReturnType(attributeMethodToInvoke, resolvedType, attrValue);
         }
         if (!implicitModel.containsAttribute(attrName)) {
             //把提前运行的ModelAttribute方法的返回值也放到隐含模型中
            implicitModel.addAttribute(attrName, attrValue);
         }
      }
       //再一次解析目标方法的参数值是哪些
      Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel);
      if (debug) {
         logger.debug("Invoking request handler method: " + handlerMethodToInvoke);
      }
      ReflectionUtils.makeAccessible(handlerMethodToInvoke);
      return handlerMethodToInvoke.invoke(handler, args);
   }
   catch (IllegalStateException ex) {
      // Internal assertion failed (e.g. invalid signature):
      // throw exception with full handler method context...
      throw new HandlerMethodInvocationException(handlerMethodToInvoke, ex);
   }
   catch (InvocationTargetException ex) {
      // User-defined @ModelAttribute/@InitBinder/@RequestMapping method threw an exception...
      ReflectionUtils.rethrowException(ex.getTargetException());
      return null;
   }
}

image-20220527151739314

image-20220527152159145

2-1 如下方法确定目标方法运行时使用的每一个参数的值(先执行的是--hahaMyModelAttribute(Map<String,Object> map)):

image-20220531092841778

private Object[] resolveHandlerArguments(Method handlerMethod, Object handler,
      NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {

   Class<?>[] paramTypes = handlerMethod.getParameterTypes();
    //创建了一个和目标方法形式参数个数一样多的数组,会用来保存每一个参数的值
   Object[] args = new Object[paramTypes.length];

   for (int i = 0; i < args.length; i++) {
      MethodParameter methodParam = new MethodParameter(handlerMethod, i);
      methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer);
      GenericTypeResolver.resolveParameterType(methodParam, handler.getClass());
      String paramName = null;
      String headerName = null;
      boolean requestBodyFound = false;
      String cookieName = null;
      String pathVarName = null;
      String attrName = null;
      boolean required = false;
      String defaultValue = null;
      boolean validate = false;
      Object[] validationHints = null;
      int annotationsFound = 0;
      Annotation[] paramAnns = methodParam.getParameterAnnotations();
	
       //找到目标方法这个参数的所有注解,如果有注解就解析并保存注解的信息(注意是方法形式参数的注解,不是方法上的注解)
      for (Annotation paramAnn : paramAnns) {
         if (RequestParam.class.isInstance(paramAnn)) {
            RequestParam requestParam = (RequestParam) paramAnn;
            paramName = requestParam.value();
            required = requestParam.required();
            defaultValue = parseDefaultValueAttribute(requestParam.defaultValue());
            annotationsFound++;
         }
         else if (RequestHeader.class.isInstance(paramAnn)) {
            RequestHeader requestHeader = (RequestHeader) paramAnn;
            headerName = requestHeader.value();
            required = requestHeader.required();
            defaultValue = parseDefaultValueAttribute(requestHeader.defaultValue());
            annotationsFound++;
         }
         else if (RequestBody.class.isInstance(paramAnn)) {
            requestBodyFound = true;
            annotationsFound++;
         }
         else if (CookieValue.class.isInstance(paramAnn)) {
            CookieValue cookieValue = (CookieValue) paramAnn;
            cookieName = cookieValue.value();
            required = cookieValue.required();
            defaultValue = parseDefaultValueAttribute(cookieValue.defaultValue());
            annotationsFound++;
         }
         else if (PathVariable.class.isInstance(paramAnn)) {
            PathVariable pathVar = (PathVariable) paramAnn;
            pathVarName = pathVar.value();
            annotationsFound++;
         }
         else if (ModelAttribute.class.isInstance(paramAnn)) {
            ModelAttribute attr = (ModelAttribute) paramAnn;
            attrName = attr.value();
            annotationsFound++;
         }
         else if (Value.class.isInstance(paramAnn)) {
            defaultValue = ((Value) paramAnn).value();
         }
         else if (paramAnn.annotationType().getSimpleName().startsWith("Valid")) {
            validate = true;
            Object value = AnnotationUtils.getValue(paramAnn);
            validationHints = (value instanceof Object[] ? (Object[]) value : new Object[] {value});
         }
      }

      if (annotationsFound > 1) {
         throw new IllegalStateException("Handler parameter annotations are exclusive choices - " +
               "do not specify more than one such annotation on the same parameter: " + handlerMethod);
      }

       //方法形式参数中没有标注注解的参数
      if (annotationsFound == 0) {
          //解析普通参数
         Object argValue = resolveCommonArgument(methodParam, webRequest);
         if (argValue != WebArgumentResolver.UNRESOLVED) {
            args[i] = argValue;
         }
         else if (defaultValue != null) {
            args[i] = resolveDefaultValue(defaultValue);
         }
         else {
            Class<?> paramType = methodParam.getParameterType();
            if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) {
               if (!paramType.isAssignableFrom(implicitModel.getClass())) {
                  throw new IllegalStateException("Argument [" + paramType.getSimpleName() + "] is of type " +
                        "Model or Map but is not assignable from the actual model. You may need to switch " +
                        "newer MVC infrastructure classes to use this argument.");
               }
               args[i] = implicitModel;
            }
            else if (SessionStatus.class.isAssignableFrom(paramType)) {
               args[i] = this.sessionStatus;
            }
            else if (HttpEntity.class.isAssignableFrom(paramType)) {
               args[i] = resolveHttpEntityRequest(methodParam, webRequest);
            }
            else if (Errors.class.isAssignableFrom(paramType)) {
               throw new IllegalStateException("Errors/BindingResult argument declared " +
                     "without preceding model attribute. Check your handler method signature!");
            }
            else if (BeanUtils.isSimpleProperty(paramType)) {
               paramName = "";
            }
            else {
               attrName = "";
            }
         }
      }

      if (paramName != null) {
         args[i] = resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler);
      }
      else if (headerName != null) {
         args[i] = resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler);
      }
      else if (requestBodyFound) {
         args[i] = resolveRequestBody(methodParam, webRequest, handler);
      }
      else if (cookieName != null) {
         args[i] = resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler);
      }
      else if (pathVarName != null) {
         args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler);
      }
      else if (attrName != null) {
         WebDataBinder binder =
               resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);
         boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));
         if (binder.getTarget() != null) {
            doBind(binder, webRequest, validate, validationHints, !assignBindingResult);
         }
         args[i] = binder.getTarget();
         if (assignBindingResult) {
            args[i + 1] = binder.getBindingResult();
            i++;
         }
         implicitModel.putAll(binder.getBindingResult().getModel());
      }
   }

   return args;
}

image-20220527153015782

第一步:解析普通参数resolveCommonArgument(就是判断是否是Servlet原生API):
protected Object resolveCommonArgument(MethodParameter methodParameter, NativeWebRequest webRequest)
      throws Exception {

   // Invoke custom argument resolvers if present...
   if (this.customArgumentResolvers != null) {
      for (WebArgumentResolver argumentResolver : this.customArgumentResolvers) {
         Object value = argumentResolver.resolveArgument(methodParameter, webRequest);
         if (value != WebArgumentResolver.UNRESOLVED) {
            return value;
         }
      }
   }

   // Resolution of standard parameter types...
   Class<?> paramType = methodParameter.getParameterType();
   Object value = resolveStandardArgument(paramType, webRequest);
   if (value != WebArgumentResolver.UNRESOLVED && !ClassUtils.isAssignableValue(paramType, value)) {
      throw new IllegalStateException("Standard argument type [" + paramType.getName() +
            "] resolved to incompatible value of type [" + (value != null ? value.getClass() : null) +
            "]. Consider declaring the argument type in a less specific fashion.");
   }
   return value;
}

解析标准参数--resolveStandardArgument(就是判断是否是Servlet原生API):

protected Object resolveStandardArgument(Class<?> parameterType, NativeWebRequest webRequest) throws Exception {
   HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
   HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);

   if (ServletRequest.class.isAssignableFrom(parameterType) ||
         MultipartRequest.class.isAssignableFrom(parameterType)) {
      Object nativeRequest = webRequest.getNativeRequest(parameterType);
      if (nativeRequest == null) {
         throw new IllegalStateException(
               "Current request is not of type [" + parameterType.getName() + "]: " + request);
      }
      return nativeRequest;
   }
   else if (ServletResponse.class.isAssignableFrom(parameterType)) {
      this.responseArgumentUsed = true;
      Object nativeResponse = webRequest.getNativeResponse(parameterType);
      if (nativeResponse == null) {
         throw new IllegalStateException(
               "Current response is not of type [" + parameterType.getName() + "]: " + response);
      }
      return nativeResponse;
   }
   else if (HttpSession.class.isAssignableFrom(parameterType)) {
      return request.getSession();
   }
   else if (Principal.class.isAssignableFrom(parameterType)) {
      return request.getUserPrincipal();
   }
   else if (Locale.class.equals(parameterType)) {
      return RequestContextUtils.getLocale(request);
   }
   else if (InputStream.class.isAssignableFrom(parameterType)) {
      return request.getInputStream();
   }
   else if (Reader.class.isAssignableFrom(parameterType)) {
      return request.getReader();
   }
   else if (OutputStream.class.isAssignableFrom(parameterType)) {
      this.responseArgumentUsed = true;
      return response.getOutputStream();
   }
   else if (Writer.class.isAssignableFrom(parameterType)) {
      this.responseArgumentUsed = true;
      return response.getWriter();
   }
   return super.resolveStandardArgument(parameterType, webRequest);
}

第2步:判断是否是Model或者是Map旗下的参数,如果是,将之前创建的隐含模型直接赋值给这个参数

image-20220527155508750

方法上标注的ModelAttribute注解如果有value:

image-20220527161939087

image-20220527162519714

image-20220527162811309

如何确定目标方法形式参数每一个参数的值:

@RequestMapping("/modelAttribute")
public String updateBook(
        @RequestParam("author") String author,
        @ModelAttribute("haha") Book book, Map<String,Object> map) 

image-20220527170021314

image-20220527170433078

image-20220527170813436

image-20220527172253240

image-20220527172127467

对于第二个形式参数: @ModelAttribute("haha") Book book(POJO)

image-20220527173811944

目标方法参数标了注解:

image-20220528092600680

目标方法参数没有标注解:

image-20220528092542296

image-20220528100514206

image-20220528100349766

image-20220528101037460

总结:

image-20220528101608103

2-4 ModelAttribute的其他用法:

1.显示存储自定义对象:

image-20220528134759411

image-20220528135832547

image-20220528140454237

源码参考:

bindObject就是所对应的值对象:

image-20220528140702511

2.使用@ModelAttribute标注的方法隐式存储自定义对象:

image-20220528141729873

image-20220528142400857

开发中推荐用法:

image-20220528142731387

2-5 视图解析:forward前缀指定一个转发

1.需求:转发到任意页面

image-20220528180340003

方式1:

image-20220528182658027

方式2:使用forward

image-20220528182753878

好处:处理器方法直接可以相互转发

image-20220528183225778

代码参考:

image-20220528183455720

package com.xu1.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @auther xu
 * @Description
 * @date 2022/5/28 - 17:42
 * @Modified By:
 */
@Controller
public class HelloController {
    @RequestMapping("/hello")
    public String helloHandle01() {
        System.out.println("helloHandle01....");
//        return "success";
        return "../../hello";
    }
    /**
     *
     * @Description
     * forward:/hello.jsp
     * forward:转发到下一个页面
     * /hello.jsp:转发当前项目下的hello
     * 注意:一定加上/,如果不加/就是相对路径。容易出问题
     *
     * @Date 2022/5/28 18:21
     *
     */
    @RequestMapping("/handle02")
    public String helloHandle02() {
        System.out.println("helloHandle02....");
//        return "success";
        return "forward:/hello.jsp";
    }

    @RequestMapping("/handle03")
    public String helloHandle03() {
        System.out.println("helloHandle03....");
//        return "success";
        return "forward:/handle02";
    }
}

2-6 视图解析:redirect前缀指定重定向到页面

image-20220528213133307

image-20220528213542747

image-20220528214119413

2-7 视图解析源码

image-20220528215551973

image-20220528215905069

image-20220528220130171

image-20220528220859667

image-20220528220816895

image-20220528221059141

返回相应的View对象:

image-20220528222833087

image-20220528221708982

image-20220528221927986

image-20220528223236624

image-20220528223505155

image-20220528223843846

image-20220528224132041

image-20220528224239390

image-20220528224636481

image-20220528225127612

image-20220528225602952

总结:

image-20220528225720560

相关概念:

image-20220529084006019

image-20220529084206519

image-20220529084258483

2-8 jstl支持便捷的国际化功能(出现异常--没有解决成功)---第174个视频

image-20220529103721385

image-20220529103816944

image-20220529103857170

image-20220529103952503

image-20220529093451142

image-20220529103331422

解决:

image-20220529103111303

老师笔记参考:

2)、有了JstlView以后;

​ 1)、让Spring管理国际化资源就行

 <!--让SpringMVC管理国际化资源文件;配置一个资源文件管理器  -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <!--  basename指定基础名-->
        <property name="basename" value="i18n"></property>
    </bean>

​ 2)、直接去页面使用fmt:message

<fmt:message key="welcomeinfo"/>
</h1>
<form action="">
    <fmt:message key="username"/>:<input /><br/>
    <fmt:message key="password"/>:<input /><br/>
    <input type="submit" value='<fmt:message key="loginBtn"/>'/>
</form>

3)注意:一定要过SpringMVC的视图解析流程,人家会创建一个jstlView帮你快速国际化;

也不能写forward

if (viewName.startsWith(FORWARD_URL_PREFIX)) {
            String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
            return new InternalResourceView(forwardUrl);
        }

2-9:view-controller将请求直接映射到目标页面,而不必创建目标方法

image-20220529105544366

image-20220529113857740

代码参考:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!--    可以导入JSTL包:fmt:message-->
    <context:component-scan base-package="com.xu1"></context:component-scan>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

<!--    让SpringMVC管理国际化资源文件:配置一个资源文件管理器,id必须为messageSource-->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <!--  basename指定基础名  -->
        <property name="basename" value="i18n"></property>
    </bean>

<!--
需求:发送一个请求("toLoginPage").没有创建目标方法处理该请求,直接到达login页面
path:指定哪一个请求
view-name:指定映射给哪个视图
(实际上走了SpringMVC的整个流程;经过了视图解析,也就拥有国际化的功能)
 -->
    <mvc:view-controller path="/toLoginPage" view-name="success"></mvc:view-controller>
    <!--  开启mvc注解驱动模式(view-controller配置了必须配置annotation-driven,否则会导致处理其他请求的目标方法失效)  -->
    <mvc:annotation-driven></mvc:annotation-driven>
</beans>

2-10:自定义视图和自定义视图解析器

(一定要自己调试,然后借助简单的自定义视图和视图解析器理解别的视图解析器和视图)

image-20220529152808814

image-20220529152920903

image-20220529152537052

代码参考:

MyGirlViewResolver:

package com.xu1.view;

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;

import javax.naming.spi.Resolver;
import java.util.Locale;

/**
 * @auther xu
 * @Description
 * 自定义视图解析器和视图对象
 * @date 2022/5/29 - 14:45
 * @Modified By:
 */
public class MyGirlViewResolver implements ViewResolver, Ordered {

    private Integer order = 0;
    /**
     *
     * @Description
     * 根据视图名返回视图对象
     *
     * girl:/gaoqing
     * @Date 2022/5/29 14:45
     *
     */
    @Override
    public View resolveViewName(String viewName, Locale locale) throws Exception {

        if (viewName.startsWith("girl:")) {
            return new MyView();
        } else {
            //如果不能处理,返回null即可
            return null;
        }
    }

    @Override
    public int getOrder() {
        return order;
    }
    public void setOrder(Integer order) {
        this.order = order;
    }
}

MyView:

package com.xu1.view;

import org.springframework.web.servlet.View;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;

/**
 * @auther xu
 * @Description
 * 自定义视图
 * @date 2022/5/29 - 14:50
 * @Modified By:
 */
public class MyView implements View {
    @Override
    public String getContentType() {
        return "text/html";
    }

    @Override
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

        System.out.println("之前保存的数据:"+model);
        List<String> vedio = (List<String>)model.get("video");

        //因为web.xml中的只设置了response的编码格式为UTF-8,并没有设置内容类型,所以此处设置相应的内容类型为text/html
        response.setContentType("text/html");

        response.getWriter().write("哈哈<h1>即将展现精彩内容</h1>");
        for (String s: vedio
             ) {
            response.getWriter().print("<a>下载"+s+".avi</a>");
        }
    }
}

springMVC.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!--    可以导入JSTL包:fmt:message-->
    <context:component-scan base-package="com.xu1"></context:component-scan>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

<!--    让SpringMVC管理国际化资源文件:配置一个资源文件管理器,id必须为messageSource-->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <!--  basename指定基础名  -->
        <property name="basename" value="i18n"></property>
    </bean>

<!--
需求:发送一个请求("toLoginPage").没有创建目标方法处理该请求,直接到达login页面
path:指定哪一个请求
view-name:指定映射给哪个视图
(实际上走了SpringMVC的整个流程;经过了视图解析,也就拥有国际化的功能)
 -->
    <mvc:view-controller path="/toLoginPage" view-name="success"></mvc:view-controller>
    <!--  开启mvc注解驱动模式(view-controller配置了必须配置annotation-driven,否则会导致处理其他请求的目标方法失效)  -->
    <mvc:annotation-driven></mvc:annotation-driven>



<!--    自定义视图解析器-->
    <bean class="com.xu1.view.MyGirlViewResolver">
<!--        数字越小,视图解析器的优先级就越高-->
        <property name="order" value="1"></property>
    </bean>
</beans>

MyViewResovlerController:

package com.xu1.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.ArrayList;

/**
 * @auther xu
 * @Description
 * 自定义视图解析器和视图对象
 * @date 2022/5/29 - 14:38
 * @Modified By:
 */
@Controller
public class MyViewResovlerController {
    @RequestMapping("/download")
    public String handleVeidio(Model model) {

        ArrayList<String> vedioName = new ArrayList<>();
        vedioName.add("阿甘正传");
        vedioName.add("人狗奇缘");
        ArrayList<String> imageName = new ArrayList<>();
        imageName.add("李欣婷");
        model.addAttribute("video",vedioName);//放入一些视频
        model.addAttribute("imgs",imageName);//放入一些图片
        return "girl:/gaoqing";
    }
}

输出结果:

image-20220529155812888

image-20220529160019504

image-20220531104005866

第3章 实战(见springMVC-08-crud)

3-1 RestfulCRUD---环境搭建

image-20220529170801547

image-20220529174210597

3-2 员工列表展示

3-3 来到添加页面

3-4 表单标签使用

image-20220530080335030

image-20220530084005865

2种解决方式:

image-20220530085358816

方式1:给请求域种指定一个key=command的map

image-20220530084658472

方式2:自定义请求域中的一个对象

image-20220530085939149

3-5 来到员工回显页面

image-20220530122007940

image-20220530112919882

image-20220530113715576

结果:

image-20220530113110760

image-20220530113147480

3-6:修改完成

image-20220530122012315

image-20220530124240387

解决null问题:

image-20220530125333225

image-20220530135416653

以下为解决思路:

image-20220530135625697

image-20220530141433616

3-7 删除:

image-20220530150756510

知识点补充:

image-20220530150241122

第4章--数据绑定

4-1 数据绑定的原理和思想

image-20220601083335715

image-20220601084502582

新的源码参考:

/**
 * Resolve the argument from the model or if not found instantiate it with
 * its default if it is available. The model attribute is then populated
 * with request values via data binding and optionally validated
 * if {@code @java.validation.Valid} is present on the argument.
 * @throws BindException if data binding and validation result in an error
 * and the next method parameter is not of type {@link Errors}.
 * @throws Exception if WebDataBinder initialization fails.
 */
@Override
public final Object resolveArgument(
      MethodParameter parameter, ModelAndViewContainer mavContainer,
      NativeWebRequest request, WebDataBinderFactory binderFactory)
      throws Exception {

   String name = ModelFactory.getNameForParameter(parameter);
   Object attribute = (mavContainer.containsAttribute(name)) ?
         mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request);

   WebDataBinder binder = binderFactory.createBinder(request, attribute, name);
   if (binder.getTarget() != null) {
      bindRequestParameters(binder, request);
      validateIfApplicable(binder, parameter);
      if (binder.getBindingResult().hasErrors()) {
         if (isBindExceptionRequired(binder, parameter)) {
            throw new BindException(binder.getBindingResult());
         }
      }
   }

   // Add resolved attribute and BindingResult at the end of the model

   Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
   mavContainer.removeAttributes(bindingResultModel);
   mavContainer.addAllAttributes(bindingResultModel);

   return binder.getTarget();
}

image-20220601090108730

WebDataBinder:数据绑定器负责数据绑定工作;数据绑定期间产生的类型转换、格式化、数据校验等问题;

image-20220601090629302

ConversionService converters =
    @org.springframework.format.annotation.DateTimeFormat java.lang.Long -> java.lang.String: org.springframework.format.datetime.DateTimeFormatAnnotationFormatterFactory@32abc654,@org.springframework.format.annotation.NumberFormat java.lang.Long -> java.lang.String: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@140bb45d
    @org.springframework.format.annotation.DateTimeFormat java.time.LocalDate -> java.lang.String: org.springframework.format.datetime.standard.Jsr310DateTimeFormatAnnotationFormatterFactory@39274b77,java.time.LocalDate -> java.lang.String : org.springframework.format.datetime.standard.TemporalAccessorPrinter@edd23ab
    @org.springframework.format.annotation.DateTimeFormat java.time.LocalDateTime -> java.lang.String: org.springframework.format.datetime.standard.Jsr310DateTimeFormatAnnotationFormatterFactory@39274b77,java.time.LocalDateTime -> java.lang.String : org.springframework.format.datetime.standard.TemporalAccessorPrinter@4f10103d
    @org.springframework.format.annotation.DateTimeFormat java.time.LocalTime -> java.lang.String: org.springframework.format.datetime.standard.Jsr310DateTimeFormatAnnotationFormatterFactory@39274b77,java.time.LocalTime -> java.lang.String : org.springframework.format.datetime.standard.TemporalAccessorPrinter@2b482406
    @org.springframework.format.annotation.DateTimeFormat java.time.OffsetDateTime -> java.lang.String: org.springframework.format.datetime.standard.Jsr310DateTimeFormatAnnotationFormatterFactory@39274b77,java.time.OffsetDateTime -> java.lang.String : org.springframework.format.datetime.standard.TemporalAccessorPrinter@5bff2a4e
    @org.springframework.format.annotation.DateTimeFormat java.time.OffsetTime -> java.lang.String: org.springframework.format.datetime.standard.Jsr310DateTimeFormatAnnotationFormatterFactory@39274b77,java.time.OffsetTime -> java.lang.String : org.springframework.format.datetime.standard.TemporalAccessorPrinter@220f2940
    @org.springframework.format.annotation.DateTimeFormat java.time.ZonedDateTime -> java.lang.String: org.springframework.format.datetime.standard.Jsr310DateTimeFormatAnnotationFormatterFactory@39274b77,java.time.ZonedDateTime -> java.lang.String : org.springframework.format.datetime.standard.TemporalAccessorPrinter@174673c
    @org.springframework.format.annotation.DateTimeFormat java.util.Calendar -> java.lang.String: org.springframework.format.datetime.DateTimeFormatAnnotationFormatterFactory@32abc654
    @org.springframework.format.annotation.DateTimeFormat java.util.Date -> java.lang.String: org.springframework.format.datetime.DateTimeFormatAnnotationFormatterFactory@32abc654
    @org.springframework.format.annotation.NumberFormat java.lang.Double -> java.lang.String: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@140bb45d
    @org.springframework.format.annotation.NumberFormat java.lang.Float -> java.lang.String: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@140bb45d
    @org.springframework.format.annotation.NumberFormat java.lang.Integer -> java.lang.String: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@140bb45d
    @org.springframework.format.annotation.NumberFormat java.lang.Short -> java.lang.String: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@140bb45d
    @org.springframework.format.annotation.NumberFormat java.math.BigDecimal -> java.lang.String: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@140bb45d
    @org.springframework.format.annotation.NumberFormat java.math.BigInteger -> java.lang.String: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@140bb45d
    java.lang.Boolean -> java.lang.String : org.springframework.core.convert.support.ObjectToStringConverter@67fe49bf
    java.lang.Character -> java.lang.Number : org.springframework.core.convert.support.CharacterToNumberFactory@5184d61b
    java.lang.Character -> java.lang.String : org.springframework.core.convert.support.ObjectToStringConverter@6a45e172
    java.lang.Enum -> java.lang.String : org.springframework.core.convert.support.EnumToStringConverter@64048fc3
    java.lang.Long -> java.util.Calendar : org.springframework.format.datetime.DateFormatterRegistrar$LongToCalendarConverter@37cbfe30
    java.lang.Long -> java.util.Date : org.springframework.format.datetime.DateFormatterRegistrar$LongToDateConverter@79fc7df4
    java.lang.Number -> java.lang.Character : org.springframework.core.convert.support.NumberToCharacterConverter@6d7337c1
    java.lang.Number -> java.lang.Number : org.springframework.core.convert.support.NumberToNumberConverterFactory@6e0b7bd8
    java.lang.Number -> java.lang.String : org.springframework.core.convert.support.ObjectToStringConverter@1135afc3
    java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.lang.Long: org.springframework.format.datetime.DateTimeFormatAnnotationFormatterFactory@32abc654,java.lang.String -> @org.springframework.format.annotation.NumberFormat java.lang.Long: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@140bb45d
    java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.time.LocalDate: org.springframework.format.datetime.standard.Jsr310DateTimeFormatAnnotationFormatterFactory@39274b77,java.lang.String -> java.time.LocalDate: org.springframework.format.datetime.standard.TemporalAccessorParser@6228dd42
    java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.time.LocalDateTime: org.springframework.format.datetime.standard.Jsr310DateTimeFormatAnnotationFormatterFactory@39274b77,java.lang.String -> java.time.LocalDateTime: org.springframework.format.datetime.standard.TemporalAccessorParser@6bf61650
    java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.time.LocalTime: org.springframework.format.datetime.standard.Jsr310DateTimeFormatAnnotationFormatterFactory@39274b77,java.lang.String -> java.time.LocalTime: org.springframework.format.datetime.standard.TemporalAccessorParser@2837beeb
    java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.time.OffsetDateTime: org.springframework.format.datetime.standard.Jsr310DateTimeFormatAnnotationFormatterFactory@39274b77,java.lang.String -> java.time.OffsetDateTime: org.springframework.format.datetime.standard.TemporalAccessorParser@60f3f7
    java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.time.OffsetTime: org.springframework.format.datetime.standard.Jsr310DateTimeFormatAnnotationFormatterFactory@39274b77,java.lang.String -> java.time.OffsetTime: org.springframework.format.datetime.standard.TemporalAccessorParser@3cb8bdb7
    java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.time.ZonedDateTime: org.springframework.format.datetime.standard.Jsr310DateTimeFormatAnnotationFormatterFactory@39274b77,java.lang.String -> java.time.ZonedDateTime: org.springframework.format.datetime.standard.TemporalAccessorParser@14a1511f
    java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.util.Calendar: org.springframework.format.datetime.DateTimeFormatAnnotationFormatterFactory@32abc654
    java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.util.Date: org.springframework.format.datetime.DateTimeFormatAnnotationFormatterFactory@32abc654
    java.lang.String -> @org.springframework.format.annotation.NumberFormat java.lang.Double: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@140bb45d
    java.lang.String -> @org.springframework.format.annotation.NumberFormat java.lang.Float: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@140bb45d
    java.lang.String -> @org.springframework.format.annotation.NumberFormat java.lang.Integer: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@140bb45d
    java.lang.String -> @org.springframework.format.annotation.NumberFormat java.lang.Short: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@140bb45d
    java.lang.String -> @org.springframework.format.annotation.NumberFormat java.math.BigDecimal: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@140bb45d
    java.lang.String -> @org.springframework.format.annotation.NumberFormat java.math.BigInteger: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@140bb45d
    java.lang.String -> java.lang.Boolean : org.springframework.core.convert.support.StringToBooleanConverter@22f562e2
    java.lang.String -> java.lang.Character : org.springframework.core.convert.support.StringToCharacterConverter@5f2594f5
    java.lang.String -> java.lang.Enum : org.springframework.core.convert.support.StringToEnumConverterFactory@1347a7be
    【java.lang.String -> java.lang.Number : org.springframework.core.convert.support.StringToNumberConverterFactory@28a5e291】
    java.lang.String -> java.time.Instant: org.springframework.format.datetime.standard.InstantFormatter@1eb74d34
    java.lang.String -> java.util.Locale : org.springframework.core.convert.support.StringToLocaleConverter@6def03d3
    java.lang.String -> java.util.Properties : org.springframework.core.convert.support.StringToPropertiesConverter@569d2e80
    java.lang.String -> java.util.UUID : org.springframework.core.convert.support.StringToUUIDConverter@343b18b
    java.time.Instant -> java.lang.String : org.springframework.format.datetime.standard.InstantFormatter@1eb74d34
    java.time.ZoneId -> java.util.TimeZone : org.springframework.core.convert.support.ZoneIdToTimeZoneConverter@63b3b722
    java.util.Calendar -> java.lang.Long : org.springframework.format.datetime.DateFormatterRegistrar$CalendarToLongConverter@1f07f950
    java.util.Calendar -> java.util.Date : org.springframework.format.datetime.DateFormatterRegistrar$CalendarToDateConverter@3bfdcdf6
    java.util.Date -> java.lang.Long : org.springframework.format.datetime.DateFormatterRegistrar$DateToLongConverter@19cfea5e
    java...

image-20220601090714453

image-20220530164111438

image-20220530164257587

4-2 自定义类型转换器

1.问题引出:

image-20220601112851298

image-20220601110606412

image-20220601110722435

2.自定义一个转换器,将字符串数据封装为一个Employee

image-20220601102902726

image-20220601103746162

image-20220601105313512

image-20220601112159637

结果:

image-20220601112442836

添加保存方法,将封装的对象进行保存:

image-20220601114558518

image-20220601114527847

代码参考:

自定义的类型转换器:

package com.xu1.component;

import com.xu1.bean.Department;
import com.xu1.bean.Employee;
import com.xu1.dao.DepartmentDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;

/**
 * @auther xu
 *
 * @Description
 * s:source
 * t:tatget
 * 将s转为t
 * @date 2022/6/1 - 9:58
 * @Modified By:
 */
public class MyStringToEmployeeConverter implements Converter<String, Employee> {
//    @Override
//    public Object convert(Object o) {
//        return null;
//    }

    @Autowired
    DepartmentDao departmentDao;
    @Override
    public Employee convert(String source) {

        //empAdmin-admin@qq.com-1-101
        Employee employee = new Employee();
        if (source.contains("-")) {
            String[] split = source.split("-");
            employee.setLastName(split[0]);
            employee.setEmail(split[1]);
            employee.setGender(Integer.parseInt(split[2]));
            employee.setDepartment(departmentDao.getDepartment((Integer.parseInt(split[3]))));
        }
        return employee;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:contex="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
">

    <contex:component-scan base-package="com.xu1"></contex:component-scan>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
<!--   conversion-service="conversionService":使用我们自己配置的类型转换组件 -->
    <mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>

<!--    告诉SpringMVC别用默认的ConversionService,而用我自定义的ConversionService,可能有我们自定义的Converter-->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<!--        converters转换器添加我们自定义的类型转换器-->
        <property name="converters">
            <set>
                <bean class="com.xu1.component.MyStringToEmployeeConverter"></bean>
            </set>
        </property>
    </bean>
</beans>
package com.xu1.conroller;

import com.xu1.bean.Department;
import com.xu1.bean.Employee;
import com.xu1.dao.DepartmentDao;
import com.xu1.dao.EmployeeDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.Collection;

/**
 * @auther xu
 * @Description
 * @date 2022/5/30 - 5:49
 * @Modified By:
 */
@Controller
public class EmployeeController {
    @Autowired
    EmployeeDao employeeDao;

    @Autowired
    DepartmentDao departmentDao;

    /**
     *
     * @Description
     * 处理查询所有员工的请求
     * @Date 2022/5/30 10:31
     *
     */
    @RequestMapping("/emps")
    public String getEmps(Model model) {
        Collection<Employee> all = employeeDao.getAll();
        model.addAttribute("emps",all);
        return "list";
    }

    /**
     *
     * @Description
     * 去员工添加页面,去页面之前需要查出所有部门信息进行展示
     * @Date 2022/5/30 6:55
     *
     */
    @RequestMapping("/toaddpage")
    public String toAddPage(Model model) {

        //1.在到添加页面之前,先查出所有部门
        Collection<Department> departments = departmentDao.getDepartments();
        //2.放到请求域中
        model.addAttribute("depts",departments);
//        model.addAttribute("command",new Employee(null,"zhang3","xu@163.com",1,departmentDao.getDepartment(105)));
        model.addAttribute("employee",new Employee(null,"zhang3","xu@163.com",
                1,departmentDao.getDepartment(105)));
        //3.去添加页面
        return "add";
    }

    /**
     *
     * @Description
     * 处理添加员工成功之后的请求,直接重定向到list员工列表页面,查看新添加的员工
     * @Date 2022/5/30 10:33
     *
     */
    @RequestMapping(value = "/emp",method= RequestMethod.POST)
    public String addEmp(Employee employee) {
        System.out.println("要添加的员工为"+employee);
        employeeDao.save(employee);
        return "redirect:/emps";//直接重定向到所有员工的展示页面
    }


    /**
     *
     * @Description
     * 用于处理更新员工的请求--该方法用于回显某一个员工的信息,以便于修改员工信息
     * @Date 2022/5/30 10:57
     *
     */
    @RequestMapping(value = "/emp/{id}",method = RequestMethod.GET)
    public String getEmp(@PathVariable("id") Integer id, Model model) {
        //1.查出员工信息
        Employee employee = employeeDao.get(id);
        //2.放到请求域中
        model.addAttribute("employee",employee);
        //3.继续查出部门信息放到隐含模型中
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("depts",departments);
        return "edit";
    }

    @RequestMapping(value = "/emp/{id}",method = RequestMethod.PUT)
    public String updateEmployee(@ModelAttribute("employee") Employee employee) {
        System.out.println("要修改的员工为:"+employee);
        //更新保存二合一
        employeeDao.save(employee);
        return "redirect:/emps";//修改员工完成之后,还是需要重定向到所有员工展示页面,查看是否修改成功
    }

    @ModelAttribute
    //从带了请求参数的路径获取id
    public void myModelAttribute(@RequestParam(value = "id",required = false) Integer id, Model model) {
        if (id != null) {
            Employee employee = employeeDao.get(id);
            model.addAttribute("employee",employee);
        }
        System.out.println("hahaha");
    }

    @RequestMapping(value = "/emp/{id}",method = RequestMethod.DELETE)
    public String deleteEmp(@PathVariable("id") Integer id) {
        employeeDao.delete(id);
        return "redirect:/emps";
    }

    @RequestMapping("/quickadd")
    public String quickAdd(@RequestParam("empinfo") Employee employee) {
        System.out.println("封装的employee对象:"+employee);
        employeeDao.save(employee);
        return "redirect:emps";
    }

}

源码调试:

image-20220601113810489

image-20220601113457469

image-20220601113614491

image-20220601113929830

image-20220601114033379

4-3 annotation-driven标签的解析

image-20220602084105131

image-20220602085208103

image-20220602084915119

SpringMVC解析:mvc:annotation-driven标签到底做了那些事情?

解析这个标签添了好多东西:

class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {

   private static final boolean jsr303Present = ClassUtils.isPresent(
         "javax.validation.Validator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

   private static final boolean jaxb2Present =
         ClassUtils.isPresent("javax.xml.bind.Binder", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

   private static final boolean jackson2Present =
         ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
               ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

   private static final boolean jacksonPresent =
         ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
               ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

   private static boolean romePresent =
         ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

   @Override
   public BeanDefinition parse(Element element, ParserContext parserContext) {
      Object source = parserContext.extractSource(element);

      CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
      parserContext.pushContainingComponent(compDefinition);

      RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);

      RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
      handlerMappingDef.setSource(source);
      handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
      handlerMappingDef.getPropertyValues().add("order", 0);
      handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
      String methodMappingName = parserContext.getReaderContext().registerWithGeneratedName(handlerMappingDef);
      if (element.hasAttribute("enable-matrix-variables") || element.hasAttribute("enableMatrixVariables")) {
         Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute(
               element.hasAttribute("enable-matrix-variables") ? "enable-matrix-variables" : "enableMatrixVariables"));
         handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
      }

      RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
      RuntimeBeanReference validator = getValidator(element, source, parserContext);
      RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element, source, parserContext);

      RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
      bindingDef.setSource(source);
      bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
      bindingDef.getPropertyValues().add("conversionService", conversionService);
      bindingDef.getPropertyValues().add("validator", validator);
      bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);

      ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);
      ManagedList<?> argumentResolvers = getArgumentResolvers(element, source, parserContext);
      ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, source, parserContext);
      String asyncTimeout = getAsyncTimeout(element, source, parserContext);
      RuntimeBeanReference asyncExecutor = getAsyncExecutor(element, source, parserContext);
      ManagedList<?> callableInterceptors = getCallableInterceptors(element, source, parserContext);
      ManagedList<?> deferredResultInterceptors = getDeferredResultInterceptors(element, source, parserContext);

      RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
      handlerAdapterDef.setSource(source);
      handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
      handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
      handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
      handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
      if (element.hasAttribute("ignore-default-model-on-redirect") || element.hasAttribute("ignoreDefaultModelOnRedirect")) {
         Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute(
               element.hasAttribute("ignore-default-model-on-redirect") ? "ignore-default-model-on-redirect" : "ignoreDefaultModelOnRedirect"));
         handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
      }
      if (argumentResolvers != null) {
         handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
      }
      if (returnValueHandlers != null) {
         handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
      }
      if (asyncTimeout != null) {
         handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);
      }
      if (asyncExecutor != null) {
         handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
      }
      handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
      handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
      String handlerAdapterName = parserContext.getReaderContext().registerWithGeneratedName(handlerAdapterDef);

      String uriCompContribName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME;
      RootBeanDefinition uriCompContribDef = new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class);
      uriCompContribDef.setSource(source);
      uriCompContribDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);
      uriCompContribDef.getPropertyValues().addPropertyValue("conversionService", conversionService);
      parserContext.getReaderContext().getRegistry().registerBeanDefinition(uriCompContribName, uriCompContribDef);

      RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
      csInterceptorDef.setSource(source);
      csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
      RootBeanDefinition mappedCsInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
      mappedCsInterceptorDef.setSource(source);
      mappedCsInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
      mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
      mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
      String mappedInterceptorName = parserContext.getReaderContext().registerWithGeneratedName(mappedCsInterceptorDef);

      RootBeanDefinition exceptionHandlerExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
      exceptionHandlerExceptionResolver.setSource(source);
      exceptionHandlerExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
      exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
      exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
      exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
      String methodExceptionResolverName =
            parserContext.getReaderContext().registerWithGeneratedName(exceptionHandlerExceptionResolver);

      RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
      responseStatusExceptionResolver.setSource(source);
      responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
      responseStatusExceptionResolver.getPropertyValues().add("order", 1);
      String responseStatusExceptionResolverName =
            parserContext.getReaderContext().registerWithGeneratedName(responseStatusExceptionResolver);

      RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
      defaultExceptionResolver.setSource(source);
      defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
      defaultExceptionResolver.getPropertyValues().add("order", 2);
      String defaultExceptionResolverName =
            parserContext.getReaderContext().registerWithGeneratedName(defaultExceptionResolver);

      parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, methodMappingName));
      parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, handlerAdapterName));
      parserContext.registerComponent(new BeanComponentDefinition(uriCompContribDef, uriCompContribName));
      parserContext.registerComponent(new BeanComponentDefinition(exceptionHandlerExceptionResolver, methodExceptionResolverName));
      parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
      parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
      parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));

      // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
      MvcNamespaceUtils.registerDefaultComponents(parserContext, source);

      parserContext.popAndRegisterContainingComponent();

      return null;
   }

加上mvc:default--servlet-handler/*mvc:annotation-driven/*对于访问静态域动态资源的权限问题

image-20220602090431610

4-3-1 对于mvc:default--servlet-handler/*mvc:annotation-driven/*都没加的情况:

只能够处理动态资源,无法处理静态资源:

image-20220602092004163

image-20220602092131411

image-20220602094216389

4-3-2 对于只加*mvc:default--servlet-handler/*情况:

只能够处理静态资源的请求,无法处理动态资源的请求:

image-20220602093402138

更正:DefaultAnnotationHandlerMapping

image-20220602094026793

4-3-3 对于mvc:default--servlet-handler/*mvc:annotation-driven/*都加的情况

image-20220602095024329

image-20220602095231564

image-20220602095658831

image-20220602095605074

image-20220602095815554

源码改进版:

image-20220602101323311

image-20220602101403164

image-20220602101508051

image-20220602101658017

处理@ModelAttribute标注的方法:

image-20220602101904883

image-20220602102042596

著目标方法获取参数:

image-20220602102229277

image-20220602102345461

image-20220602102511582

image-20220602103151910

image-20220602103355512

对于目标方法中的每一个参数,都有特定的解析器对对其进行处理。

4-4 数据绑定--日期格式化

image-20220602150043976

image-20220602150100474

image-20220602150157165

image-20220602150216529

解决问题:给日期一个具体的格式化形式

image-20220602150941496

image-20220602151315154

image-20220602151732040

此处使用法2:

image-20220602152225722

结果:

image-20220602152421458

image-20220602152448031

无法使用别的格式:400错误

image-20220602152553342

image-20220602152626454

4-5 数据校验

image-20220602161541152

image-20220602161658923

image-20220602163315436

image-20220602163013869

image-20220602163435668

image-20220602172024613

输出结果:

image-20220602172847246

代码参考:

Employee.java:

package com.xu1.bean;


import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.format.annotation.DateTimeFormat;

import javax.validation.constraints.Past;
import java.util.Date;

public class Employee {

   private Integer id;

   @NotEmpty//后端校验,保证传过来的参数非空
   @Length(max = 18,min = 2)//保证名字输入最大为18,最小为2
   private String lastName;

   @Email
   private String email;
   //1 male, 0 female

   private Integer gender;

   //规定提交日期的格式
   @Past
   @DateTimeFormat(pattern = "yyyy-MM-dd")

   private Date birth;
   private Department department;

// //假设页面为了显示方便提交工资是:10,000.98¥
// @NumberFormat(pattern = "##,###.##¥")
// private Integer salary;

// public Integer getSalary() {
//    return salary;
// }
//
// public void setSalary(Integer salary) {
//    this.salary = salary;
// }

   public Date getBirth() {
      return birth;
   }

   public void setBirth(Date birth) {
      this.birth = birth;
   }

   public Integer getId() {
      return id;
   }

   public void setId(Integer id) {
      this.id = id;
   }

   public String getLastName() {
      return lastName;
   }

   public void setLastName(String lastName) {
      this.lastName = lastName;
   }

   public String getEmail() {
      return email;
   }

   public void setEmail(String email) {
      this.email = email;
   }

   public Integer getGender() {
      return gender;
   }

   public void setGender(Integer gender) {
      this.gender = gender;
   }

   public Department getDepartment() {
      return department;
   }

   public void setDepartment(Department department) {
      this.department = department;
   }

   public Employee(Integer id, String lastName, String email, Integer gender,
         Department department) {
      super();
      this.id = id;
      this.lastName = lastName;
      this.email = email;
      this.gender = gender;
      this.department = department;
   }

   public Employee() {
   }

   @Override
   public String toString() {
      return "Employee{" +
            "id=" + id +
            ", lastName='" + lastName + '\'' +
            ", email='" + email + '\'' +
            ", gender=" + gender +
            ", birth=" + birth +
            ", department=" + department +
            '}';
   }
}

image-20220602173232858

add.jsp页面:

<%--
  Created by IntelliJ IDEA.
  User: 许荣
  Date: 2022/5/30
  Time: 6:51
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>


<%--
表单标签:
通过SpringMVC的表单标签可以实现将模型数据中的属性和HTML表单元素相绑定,以实现表单数据更便捷的编辑和表单值的回显
--%>
<%
    pageContext.setAttribute("cpt",request.getContextPath());
%>
<form:form action="${cpt}/emp" modelAttribute="employee" method="post">
<%--
 path就是原来html-input的name项:
 path:
 1)当原生的name项
 2)自动回现隐含模型中某一个对象对应的这个属性值
--%>

    lastName:<form:input path="lastName"/><form:errors path="lastName"/> <br/>
    email:<form:input path="email"/><form:errors path="email"/> <br/>
    gender:<br/>c
        男:<form:radiobutton path="gender" value="1"/><br/>
        女:<form:radiobutton path="gender" value="0"/><br/>
    birth:
        生日:<form:input path="birth"/><form:errors path="birth"/> <br/>
<%--    salary:--%>
<%--        工资:<form:input path="salary"/><br/>--%>
    dept:
        <form:select path="department.id" items="${depts}" itemLabel="departmentName" itemValue="id"></form:select><br/>
    <input type="submit" value="保存">
</form:form>
<%--    <form action="">--%>
<%--        lastName:<input type="text" name="lastName"><br/>--%>
<%--        email:<input type="text" name="email">--%>
<%--        gender:<br/>--%>
<%--            男:<input type="radio" name="gender" value="1"><br/>--%>
<%--            女:<input type="radio" name="gender" value="0"><br/>--%>
<%--        dept:--%>
<%--            <select name="department.id">--%>
<%--                <c:forEach items="${depts}" var="deptItem">--%>
<%--&lt;%&ndash;                    在标签体中的是在页面的提示信息,value才是正真提交的值&ndash;%&gt;--%>
<%--                    <option value="${deptItem.id}">${deptItem.departmentName}</option>--%>
<%--                </c:forEach>--%>
<%--            </select>--%>
<%--        <input type="submit" value="保存"/>--%>
<%--    </form>--%>

4-6 普通表单将错误信息放到请求域中获取

image-20220604141159508

image-20220604141141873

操作:

image-20220604142532431

结果:

image-20220604142310041

代码参考:

package com.xu1.conroller;

import com.xu1.bean.Department;
import com.xu1.bean.Employee;
import com.xu1.dao.DepartmentDao;
import com.xu1.dao.EmployeeDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @auther xu
 * @Description
 * @date 2022/5/30 - 5:49
 * @Modified By:
 */
@Controller
public class EmployeeController {
    @Autowired
    EmployeeDao employeeDao;

    @Autowired
    DepartmentDao departmentDao;

    /**
     *
     * @Description
     * 处理查询所有员工的请求
     * @Date 2022/5/30 10:31
     *
     */
    @RequestMapping("/emps")
    public String getEmps(Model model) {
        Collection<Employee> all = employeeDao.getAll();
        model.addAttribute("emps",all);
        return "list";
    }

    /**
     *
     * @Description
     * 去员工添加页面,去页面之前需要查出所有部门信息进行展示
     * @Date 2022/5/30 6:55
     *
     */
    @RequestMapping("/toaddpage")
    public String toAddPage(Model model) {

        //1.在到添加页面之前,先查出所有部门
        Collection<Department> departments = departmentDao.getDepartments();
        //2.放到请求域中
        model.addAttribute("depts",departments);
//        model.addAttribute("command",new Employee(null,"zhang3","xu@163.com",1,departmentDao.getDepartment(105)));
        model.addAttribute("employee",new Employee(null,"zhang3","xu@163.com",
                1,departmentDao.getDepartment(105)));
        //3.去添加页面
        return "add";
    }

    /**
     *
     * @Description
     * 处理添加员工成功之后的请求,直接重定向到list员工列表页面,查看新添加的员工
     * @Date 2022/5/30 10:33
     *
     */
    @RequestMapping(value = "/emp",method= RequestMethod.POST)
    public String addEmp(@Valid Employee employee, BindingResult result,Model model) {
        System.out.println("要添加的员工为"+employee);

        //创建一个map用于存储错误信息
        Map<String,Object> errorsMap = new HashMap<String,Object>();
        Boolean hasErrors = result.hasErrors();
        if (hasErrors) {
            List<FieldError> fieldErrors = result.getFieldErrors();
            for (FieldError fieldError: fieldErrors
                 ) {
                System.out.println("错误信息提示:"+fieldError.getDefaultMessage());
                System.out.println("错误的字段是:"+fieldError.getField());
                System.out.println(fieldError);
                System.out.println("-------------------");
                errorsMap.put(fieldError.getField(),fieldError.getDefaultMessage());
            }
            model.addAttribute("errorInfo",errorsMap);
            System.out.println("后端校验时有错误");
            return "add";
        } else {
            employeeDao.save(employee);
            return "redirect:/emps";//直接重定向到所有员工的展示页面
        }
    }


    /**
     *
     * @Description
     * 用于处理更新员工的请求--该方法用于回显某一个员工的信息,以便于修改员工信息
     * @Date 2022/5/30 10:57
     *
     */
    @RequestMapping(value = "/emp/{id}",method = RequestMethod.GET)
    public String getEmp(@PathVariable("id") Integer id, Model model) {
        //1.查出员工信息
        Employee employee = employeeDao.get(id);
        //2.放到请求域中
        model.addAttribute("employee",employee);
        //3.继续查出部门信息放到隐含模型中
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("depts",departments);
        return "edit";
    }

    @RequestMapping(value = "/emp/{id}",method = RequestMethod.PUT)
    public String updateEmployee(@ModelAttribute("employee") Employee employee) {
        System.out.println("要修改的员工为:"+employee);
        //更新保存二合一
        employeeDao.save(employee);
        return "redirect:/emps";//修改员工完成之后,还是需要重定向到所有员工展示页面,查看是否修改成功
    }

    @ModelAttribute
    //从带了请求参数的路径获取id
    public void myModelAttribute(@RequestParam(value = "id",required = false) Integer id, Model model) {
        if (id != null) {
            Employee employee = employeeDao.get(id);
            model.addAttribute("employee",employee);
        }
        System.out.println("hahaha");
    }

    @RequestMapping(value = "/emp/{id}",method = RequestMethod.DELETE)
    public String deleteEmp(@PathVariable("id") Integer id) {
        employeeDao.delete(id);
        return "redirect:/emps";
    }

    @RequestMapping("/quickadd")
    public String quickAdd(@RequestParam("empinfo") Employee employee) {
        System.out.println("封装的employee对象:"+employee);
        employeeDao.save(employee);
        return "redirect:emps";
    }

}

4-7 自定义国际化错误信息的显示

image-20220605054544844

image-20220605054759780

这是国际化时,.properties配置规范

image-20220605063436273

image-20220605063527299

image-20220605055448658

自定义国际化错误信息的显示步骤:

第一步:编写国际化配置文件

errors_en_US.properties:

Email.email=email errors!~~~
NotEmpty=not empty!~~~
Length.java.lang.String=length incorrect!~~~
Past=Wrong time format,must be past time!~~~

errors_zh_CN.properties:

Email.email=\u90ae\u7bb1\u4e0d\u5bf9\ua!~~
NotEmpty=\u4e0d\u80fd\u4e3a\u7a7a!~~~
Length.java.lang.String=\u957f\u5ea6\u4e0d\u6b63\u786e!~~
Past=\u5fc5\u987b\u662f\u8fc7\u53bb\u7684\u65f6\u95f4!~~~

第二步:管理国际化资源文件

image-20220605063023448

<!--  管理国际化资源文件  -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="errors"></property>
    </bean>

结果:

image-20220605063806540

image-20220605065142020

image-20220605065417800

补充细节:

image-20220605093352787

2.直接取出字段名和值:

image-20220605094116207

结果:

image-20220605094328170

3.message

image-20220605094923341

思考:如何给请求域中放入自定义的错误提示,并且有国际化功能?

第5章--SpringMVC-ajax

image-20220605095455298

image-20220605095601907

5-1 环境配置

1.导包

image-20220605101846479

2.测试:

image-20220605103757048

测试结果:

image-20220605103951376

指定具体属性值不转为json格式

image-20220605104256428

利用jsckson包将json时间数据格式化:

image-20220605104756332

image-20220605105100404

5-2 SpringMVC-ajax获取所有员工(ajax不懂--待了解)

<%@ page import="java.util.Date" %><%--
  Created by IntelliJ IDEA.
  User: 许荣
  Date: 2022/6/5
  Time: 11:08
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>\
<%
    pageContext.setAttribute("absPath",request.getContextPath());
%>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%--看一看每一次的时间,是不是自动刷新--%>
<%=new Date()%>
<a href="${absPath}/EmployeesToAjax">ajax获取所有员工</a>
<div>

</div>
<script type="text/javascript">
    $("a:first").click(function () {
        //1.发送ajax获取所有员工
        $.ajax({
            url:"${absPath}/getallajax",
            type:"GET",
            success:function (data) {
                // console.log(data);
                $.each(data,function () {
                    var empInfo = this.lastName+"--->"+this.birth+"--->"+this.gender;
                    $("div").append(empInfo);
                });
            }
        });
    });

</script>
</body>
</html>

5-3 @RequestBody获取请求体

/**
 *
 * @Description
 * @RequestBody:获取一个请求的请求体
 * @Date 2022/6/5 16:17
 *
 */
@RequestMapping("/requestBodyTest")
public String requestBodyTest(@RequestBody String body) {
    System.out.println("请求体为:"+body);
    return "success";
}
<%--
  Created by IntelliJ IDEA.
  User: 许荣
  Date: 2022/6/5
  Time: 15:35
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%
    pageContext.setAttribute("absPath",request.getContextPath());
%>
<html>
<head>
    <title>Title</title>
</head>
<body>

<form action="${absPath}/requestBodyTest" method="post">
    <input name="username" value="tomcat"/>
    <input name="password" value="123456">
    <input type="submit">
</form>
</body>
</html>

输出结果:

image-20220605162434572

image-20220605162554710

5-3 发送json数据给服务器---第200个视频

使用@RequestBody接受json数据,封装为对象,使用@ResponseBody可以把对象封装为json数据,返回给浏览器

5-4 HttpEntity--可以获取到请求头

image-20220605164316525

/**
 *
 * @Description
 * 比@RequestBody更强,除了拿到请求体之外,还可以拿到请求头
 * @Date 2022/6/5 16:45
 *
 */
@RequestMapping("/requestBodyTest")
public String requestBodyTest(HttpEntity<String> str) {
    System.out.println("请求数据为:"+str);
    return "success";
}

结果:

image-20220605164822055

请求数据为:<username=tomcat&password=123456,
{host=[localhost:8080], 
connection=[keep-alive], 
content-length=[31], 
cache-control=[max-age=0], 
sec-ch-ua=[" Not A;Brand";v="99",
"Chromium";v="101", "Google Chrome";v="101"], 
sec-ch-ua-mobile=[?0], 
sec-ch-ua-platform=["Windows"], 
upgrade-insecure-requests=[1], 
origin=[http://localhost:8080], 
user-agent=[Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36], 
accept=[text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9], 
sec-fetch-site=[same-origin], 
sec-fetch-mode=[navigate], 
sec-fetch-user=[?1], 
sec-fetch-dest=[document], 
referer=[http://localhost:8080/springMVC_09/testRequesstBody.jsp], 
accept-encoding=[gzip, deflate, br], 
accept-language=[en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7], 
cookie=[JSESSIONID=7AECCC24159B95E9483608E250F1D9FD], 
Content-Type=[application/x-www-form-urlencoded;charset=UTF-8]}>

image-20220605165408499

5-5 ResponseEntity既能放回响应数据,还能定制响应头

1.ResponseBody-将返回数据放到响应体中

/**
 *
 * @Description
 * @ResponseBody:将返回数据放到响应体中
 * 
 * @Date 2022/6/5 17:18
 *
 */
@ResponseBody
@RequestMapping("/requestBodyTest2")
public String requestBodyTest2() {
    System.out.println("haha...");
    return "success";
}

结果:没有转发到success.jsp页面

image-20220605172107519

image-20220605172403421

image-20220605172331335

2.ResponseEntity

image-20220605172918195

/**
 *
 * @Description
 * ResponseEntity:不仅可以将返回数据放到响应体中,还可以自定义响应头
 * @Date 2022/6/5 17:37
 *
 */
@RequestMapping("/requestEntityTest2")
public ResponseEntity<String> requestEntityTest2() {

    String body = null;
    MultiValueMap<String, String> headers = new HttpHeaders();
    HttpStatus statusCode = null;

    body = "<h1>success</h1>";
    headers.add("Set-Cookie","username=lixixting");
    statusCode = HttpStatus.OK;

    ResponseEntity<String> stringResponseEntity = new ResponseEntity<>(body, headers, statusCode);
    return stringResponseEntity;
}

结果:

image-20220605174114099

image-20220605174031979

第6章--文件的上传于下载

6-1文件下载

image-20220606100456321

@RequestMapping(value = "/downLoadFile")
public ResponseEntity<byte[]> downLoadFile(HttpServletRequest request) throws IOException {
    //1.得到要下载的的文件流;找到要下载的文件的真实路径
    ServletContext servletContext = request.getServletContext();
    String realPath = servletContext.getRealPath("/FileTest");
    FileInputStream fileInputStream = new FileInputStream(realPath);

    byte[] temp = new byte[fileInputStream.available()];
    fileInputStream.read(temp);
    fileInputStream.close();

    //2.将要下载的文件流返回
    HttpHeaders httpHeaders = new HttpHeaders();
    httpHeaders.set("Content-Disposition","attachment;filename="+"FileTest.text");
    ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(temp, httpHeaders, HttpStatus.OK);
    return responseEntity;
}

结果:

image-20220606100621920

6-2 HttpMessageConverter

image-20220606100844829

image-20220606101321211

image-20220606101823730

image-20220606102410061

image-20220606102553723

6-2 文件上传

image-20220606161842723

6-2-1 环境搭建:

image-20220606162619179

image-20220606162649910

image-20220606163100291

文件上传步骤总结如下:

文件上传:
第1步:文件上传表单准备:enctype="multipart/form-data"
第2步:导入用于文件上传的jar包
commons-fileupload-1.2.1.jar
commons-io-2.0.jar
第3步:在springMVC中配置文件上传解析器
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
第4步:在控制类方法中形参中传入该注解参数:
@RequestParam("headering") MultipartFile file,用于封装上传的文件

6-2-2 单文件上传:

FileUploadController.java

package com.xu1.conroller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

/**
 * @auther xu
 * @Description
 * @date 2022/6/6 - 16:35
 * @Modified By:
 */
@Controller
public class FileUploadController {

    @RequestMapping(value = "/uploadFile")
    public String fileUploadTest1(@RequestParam(value = "userName",required = false) String userName,
                                  Model model,
                                  @RequestParam("headering") MultipartFile file) {
        System.out.println("上传的文件信息");
        System.out.println("文件的名字:"+file.getName());
        System.out.println("文件的名字:"+file.getOriginalFilename());

        try {
            //上传文件保存的位置
            file.transferTo(new File("D:\\Java\\ssm\\SpringMVC\\SpringMVC\\springMVC-10-uploadFile\\web\\WEB-INF\\saveFile\\copytest1.text"));
            model.addAttribute("token","文件上传成功");
        } catch (Exception e) {
            model.addAttribute("token","文件上传失败"+e.getMessage());
        }

        return "forward:/index.jsp";
    }
}

SpringMVC.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="com.xu1"></context:component-scan>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

<!--    对于静态与动态资源的处理-->
    <mvc:default-servlet-handler/>
    <mvc:annotation-driven></mvc:annotation-driven>

<!--    配置文件上传及解析器-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--        设置上传文件的最大尺寸-->
        <property name="maxInMemorySize" value="#{1024*1024*20}"></property>
<!--        设置默认编码-->
        <property name="defaultEncoding" value="UTF-8"></property>
    </bean>
</beans>

index.jsp:

<%--
  Created by IntelliJ IDEA.
  User: 许荣
  Date: 2022/6/6
  Time: 15:51
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    pageContext.setAttribute("absPath",request.getContextPath());
%>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  ${token}
<%--
文件上传:
第1步:文件上传表单准备:enctype="multipart/form-data"
第2步:导入用于文件上传的jar包
commons-fileupload-1.2.1.jar
commons-io-2.0.jar
第3步:在springMVC中配置文件上传解析器
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
第4步:在控制类方法中形参中传入该注解参数:
@RequestParam("headering") MultipartFile file
--%>
    <form action="${absPath}/uploadFile" method="post" enctype="multipart/form-data">
      用户头像:<input type="file" name="headering"/><be/>
      用户名:<input type="text" name="userName"/><be/>
      <input type="submit"/>
    </form>


  </body>
</html>

结果:

image-20220606171527360

image-20220606171606742

对于可能导致文件上传错误的情况:所传文件超过规定大小

6-2-3 多文件上传

/**
 *
 * @Description
 * 多文件上传
 * @Date 2022/6/6 20:52
 *
 */
@RequestMapping(value = "/uploadFile")
public String fileUploadTest1(@RequestParam(value = "userName",required = false) String userName,
                              Model model,
                              @RequestParam("headering") MultipartFile[] multipartFiles) {
    System.out.println("上传的文件信息");
    for (MultipartFile file:multipartFiles
         ) {
      if (!file.isEmpty()) {
          //上传的文件保存
          try {
              file.transferTo(new File("D:\\Java\\ssm\\SpringMVC\\SpringMVC\\springMVC-10-uploadFile\\web\\WEB-INF\\saveFile\\"+file.getOriginalFilename()));
              model.addAttribute("token","文件上传成功");
          } catch (Exception e) {
              model.addAttribute("token","文件上传失败"+e.getMessage());
          }
      }
    }

    return "forward:/index.jsp";
}
<%--
  Created by IntelliJ IDEA.
  User: 许荣
  Date: 2022/6/6
  Time: 15:51
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    pageContext.setAttribute("absPath",request.getContextPath());
%>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  ${token}
<%--
文件上传:
第1步:文件上传表单准备:enctype="multipart/form-data"
第2步:导入用于文件上传的jar包
commons-fileupload-1.2.1.jar
commons-io-2.0.jar
第3步:在springMVC中配置文件上传解析器
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
第4步:在控制类方法中形参中传入该注解参数:
@RequestParam("headering") MultipartFile file
--%>
    <form action="${absPath}/uploadFile" method="post" enctype="multipart/form-data">
        用户头像:<input type="file" name="headering"/><br/>
        用户头像:<input type="file" name="headering"/><br/>
        用户头像:<input type="file" name="headering"/><br/>
        用户头像:<input type="file" name="headering"/><br/>
        用户名:<input type="text" name="userName"/><br/>
      <input type="submit"/>
    </form>


  </body>
</html>

运行结果:

image-20220606210727665

image-20220606210754102

image-20220606210859957

第7章--拦截器

7-1 拦截器器的介绍

image-20220607050758259

7-2 拦截器--单拦截运行流程

使用拦截器的步骤:

  • 第1步:实现拦截器的接口,并将preHandle()方法的返回值改为true

  • 第2步:在SpringMVC中注册拦截器,并可以针对请求进行拦截处理

  • 第3步:拦截器的运行流程

  • * MyFirstInterceptor....preHandle
    * interceptorTest01目标方法执行。。。
    * MyFirstInterceptor....postHandle
    * success.jsp....
    * MyFirstInterceptor....afterCompletion
    

注意:image-20220607054430335

第一步:

实现接口:

package com.xu1.conroller;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @auther xu
 * @Description
 *
 * 步骤:
 * 第1步:实现拦截器的接口,并将preHandle()方法的返回值改为true
 * 第2步:在SpringMVC中注册拦截器,并可以针对请求进行拦截处理
 * 第3步:拦截器的运行流程
 * MyFirstInterceptor....preHandle
 * interceptorTest01目标方法执行。。。
 * MyFirstInterceptor....postHandle
 * success.jsp....
 * MyFirstInterceptor....afterCompletion
 *
 * @date 2022/6/7 - 5:15
 * @Modified By:
 */
public class MyFirstInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {

        System.out.println("MyFirstInterceptor....preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        System.out.println("MyFirstInterceptor....postHandle");

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        System.out.println("MyFirstInterceptor....afterCompletion");

    }
}

目标方法:

package com.xu1.conroller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @auther xu
 * @Description
 * @date 2022/6/7 - 5:09
 * @Modified By:
 */
@Controller
public class InterceptorTestController {
    @RequestMapping(value = "/interceptor01")
    public String interceptorTest01() {

        System.out.println("interceptorTest01目标方法执行。。。");
        return "success";
    }
}

第2步:注册一个拦截器

image-20220607054756799

第3步:观察数据验证拦截器方法执行顺序

image-20220607055428819

MyFirstInterceptor....preHandle
interceptorTest01目标方法执行。。。
MyFirstInterceptor....postHandle
success.jsp....
MyFirstInterceptor....afterCompletion

总结:

image-20220607062622842

image-20220607063026824

7-3 拦截器--多拦截器运行流程

再创建一个拦截器:

package com.xu1.conroller;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @auther xu
 * @Description
 * @date 2022/6/7 - 6:32
 * @Modified By:
 */
public class MySecondInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MySecondInterceptor...preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MySecondInterceptor...postHandle");

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MySecondInterceptor...afterCompletion");
    }
}

注册了两个拦截器:

image-20220607063737446

1.多拦截器正常运行流程

image-20220607064908455

image-20220607065259795

2.多拦截器异常运行流程

image-20220607072036315

image-20220607072305921

7-4 源码--拦截器运行流程

image-20220607095036377

image-20220607095309574

第1步:对于applyPreHandle处理

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
   if (getInterceptors() != null) {
      for (int i = 0; i < getInterceptors().length; i++) {
         HandlerInterceptor interceptor = getInterceptors()[i];
         if (!interceptor.preHandle(request, response, this.handler)) {
            triggerAfterCompletion(request, response, null);
            return false;
         }
         this.interceptorIndex = i;
      }
   }
   return true;
}

image-20220607095950513

补充:如果进入if语句,triggerAfterCompletion(request,response,null)方法会直接跳到afterCompletion方法执行。

image-20220607101154417

image-20220607101924139

image-20220607102024215

第2步:探究applyPostHandle()方法

/**
 * Apply postHandle methods of registered interceptors.
 */
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
   if (getInterceptors() == null) {
      return;
   }
   for (int i = getInterceptors().length - 1; i >= 0; i--) {
      HandlerInterceptor interceptor = getInterceptors()[i];
      interceptor.postHandle(request, response, this.handler, mv);
   }
}

image-20220607102903195

image-20220607103506686

第3步:探究对拦截器处理结果渲染方法--processDispatchResult()

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
      HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

   boolean errorView = false;

   if (exception != null) {
      if (exception instanceof ModelAndViewDefiningException) {
         logger.debug("ModelAndViewDefiningException encountered", exception);
         mv = ((ModelAndViewDefiningException) exception).getModelAndView();
      }
      else {
         Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
         mv = processHandlerException(request, response, handler, exception);
         errorView = (mv != null);
      }
   }

   // Did the handler return a view to render?
   if (mv != null && !mv.wasCleared()) {
      render(mv, request, response);
      if (errorView) {
         WebUtils.clearErrorRequestAttributes(request);
      }
   }
   else {
      if (logger.isDebugEnabled()) {
         logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
               "': assuming HandlerAdapter completed request handling");
      }
   }

   if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
      // Concurrent handling started during a forward
      return;
   }

   if (mappedHandler != null) {
      mappedHandler.triggerAfterCompletion(request, response, null);
   }
}

image-20220607104422079

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
      throws Exception {

   if (getInterceptors() == null) {
      return;
   }
   for (int i = this.interceptorIndex; i >= 0; i--) {
      HandlerInterceptor interceptor = getInterceptors()[i];
      try {
         interceptor.afterCompletion(request, response, this.handler, ex);
      }
      catch (Throwable ex2) {
         logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
      }
   }
}

image-20220607104549014

补充:this.interceptorIndex记录了已经放行的最后一个拦截器的下标。

image-20220607104947425

7-5 其他情况源码探究:(preHandle()返回false,渲染视图时出现异常。。。。自己调试)

image-20220607111752114

7-6 总结

image-20220607112107473

image-20220607112235617

第8章--国际化

8-1 简单的国际化

image-20220607144336088

image-20220607143059700

package com.xu1.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @auther xu
 * @Description
 * @date 2022/6/7 - 13:50
 * @Modified By:
 */
@Controller
public class I18nTestController {

    @RequestMapping(value = "/toLoginPage")
    public String login() {
        return "login";
    }
}

index.jsp

<%--
  Created by IntelliJ IDEA.
  User: 许荣
  Date: 2022/6/7
  Time: 13:43
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
  pageContext.setAttribute("absPath",request.getContextPath());
%>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
    <a href="${absPath}/toLoginPage">去登录页面</a>
  </body>
</html>

login.jsp:

<%--
  Created by IntelliJ IDEA.
  User: 许荣
  Date: 2022/6/7
  Time: 13:54
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1><fmt:message key="welcomeinfo"/> </h1>
    <form>
        <fmt:message key="username"/>:<input type="text" name="username"/><br/>
        <fmt:message key="password"/>:<input type="password" name="password"/><br/>
        <input type="submit" value="<fmt:message key="loginBtn"/>">
    </form>
</body>
</html>

结果:

image-20220607143239091

image-20220607143325343

8-2 国际化区域信息解析器负责解析区域信息(P215)

8-3 程序中获取国际化信息

image-20220607145419860

代码参考:

package com.xu1.controller;

import com.sun.media.jfxmediaimpl.HostUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Locale;

/**
 * @auther xu
 * @Description
 * @date 2022/6/7 - 13:50
 * @Modified By:
 */
@Controller
public class I18nTestController {


    @Autowired
    ResourceBundleMessageSource bundleMessageSource;

    @RequestMapping(value = "/toLoginPage")
    public String login(Locale locale, Model model) {
        System.out.println(locale);
        String welcomeinfo = bundleMessageSource.getMessage("welcomeinfo", null, locale);
        System.out.println(welcomeinfo);

        //可以将拿到国际化信息放到隐含模型中,可以提供给下一个页面参考数据
        model.addAttribute("welcomeinfo",welcomeinfo);
        return "login";
    }
}

8-4 点击连接切换为国际化

image-20220607160319818

image-20220607161626458

我们可以模仿上图,自己构建一个LocaleResolver接口的实现类:

image-20220607161729081

使用步骤:

第一步:实现自己的区域解析器,实现LocaleResolver接口

package com.xu1.controller;


import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;


/**
 * @auther xu
 * @Description
 * @date 2022/6/7 - 14:56
 * @Modified By:
 */
public class MyLocalResolver implements LocaleResolver {


    /**
     *
     * @Description
     * 返回解析locale
     * @Date 2022/6/7 16:05
     *
     */
    @Override
    public Locale resolveLocale(HttpServletRequest httpServletRequest) {
        Locale locale = null;
        String locale1 = httpServletRequest.getParameter("locale");
        //如果带了locale参数就用参数指定的区域信息,如果没有就用请求头自带的区域信息
        if (locale1 != null && !"".equals(locale1)) {
            String[] s = locale1.split("_");
            locale = new Locale(s[0], s[1]);//public Locale(@NotNull String language,@NotNull String country)
        } else {
            locale = httpServletRequest.getLocale();
        }
        return locale;
    }

    /**
     *
     * @Description
     * 修改locale
     * @Date 2022/6/7 16:05
     *
     */
    @Override
    public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
        throw new UnsupportedOperationException(
                "Cannot change HTTP accept header - use a different locale resolution strategy");
    }
}

第2步:在SpringMVC.xml中注册自己定义的区域解析器

!--    保证使用我自定义区域解析器-->
    <bean id="localeResolver" class="com.xu1.controller.MyLocalResolver"></bean>

代码参考:

image-20220607164047035

结果:

image-20220607164136024

image-20220607164201789

3.源码探究

image-20220607164618932

8-5 使用SessionLocaleResolver实现点击连接实现国际化

image-20220607170119176

AcceptHeaderLocaleResolver.java中的方法:(使用请求头的区域信息)

@Override
	public Locale resolveLocale(HttpServletRequest request) {
		return request.getLocale();
	}

	@Override
	public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
		throw new UnsupportedOperationException(
				"Cannot change HTTP accept header - use a different locale resolution strategy");
	}

FixedLocaleResolver.java中的方法:(使用系统默认的区域信息)

@Override
public Locale resolveLocale(HttpServletRequest request) {
   Locale locale = getDefaultLocale();
   if (locale == null) {
      locale = Locale.getDefault();
   }
   return locale;
}

@Override
public LocaleContext resolveLocaleContext(HttpServletRequest request) {
   return new TimeZoneAwareLocaleContext() {
      @Override
      public Locale getLocale() {
         return getDefaultLocale();
      }
      @Override
      public TimeZone getTimeZone() {
         return getDefaultTimeZone();
      }
   };
}

@Override
public void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext) {
   throw new UnsupportedOperationException("Cannot change fixed locale - use a different locale resolution strategy");
}

SessionLocaleResolver.java中的方法:

/*
 * Copyright 2002-2013 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.servlet.i18n;

import java.util.Locale;
import java.util.TimeZone;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
import org.springframework.web.util.WebUtils;

/**
 * Implementation of LocaleResolver that uses a locale attribute in the user's
 * session in case of a custom setting, with a fallback to the specified default
 * locale or the request's accept-header locale.
 *
 * <p>This is most appropriate if the application needs user sessions anyway,
 * that is, when the HttpSession does not have to be created for the locale.
 * The session may optionally contain an associated time zone attribute as well;
 * alternatively, you may specify a default time zone.
 *
 * <p>Custom controllers can override the user's locale and time zone by calling
 * {@code #setLocale(Context)} on the resolver, e.g. responding to a locale change
 * request. As a more convenient alternative, consider using
 * {@link org.springframework.web.servlet.support.RequestContext#changeLocale}.
 *
 * @author Juergen Hoeller
 * @since 27.02.2003
 * @see #setDefaultLocale
 * @see #setDefaultTimeZone
 */
public class SessionLocaleResolver extends AbstractLocaleContextResolver {

   /**
    * Name of the session attribute that holds the Locale.
    * Only used internally by this implementation.
    * <p>Use {@code RequestContext(Utils).getLocale()}
    * to retrieve the current locale in controllers or views.
    * @see org.springframework.web.servlet.support.RequestContext#getLocale
    * @see org.springframework.web.servlet.support.RequestContextUtils#getLocale
    */
   public static final String LOCALE_SESSION_ATTRIBUTE_NAME = SessionLocaleResolver.class.getName() + ".LOCALE";

   /**
    * Name of the session attribute that holds the TimeZone.
    * Only used internally by this implementation.
    * <p>Use {@code RequestContext(Utils).getTimeZone()}
    * to retrieve the current time zone in controllers or views.
    * @see org.springframework.web.servlet.support.RequestContext#getTimeZone
    * @see org.springframework.web.servlet.support.RequestContextUtils#getTimeZone
    */
   public static final String TIME_ZONE_SESSION_ATTRIBUTE_NAME = SessionLocaleResolver.class.getName() + ".TIME_ZONE";


   @Override
   public Locale resolveLocale(HttpServletRequest request) {
      Locale locale = (Locale) WebUtils.getSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME);
      if (locale == null) {
         locale = determineDefaultLocale(request);
      }
      return locale;
   }

   @Override
   public LocaleContext resolveLocaleContext(final HttpServletRequest request) {
      return new TimeZoneAwareLocaleContext() {
         @Override
         public Locale getLocale() {
            Locale locale = (Locale) WebUtils.getSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME);
            if (locale == null) {
               locale = determineDefaultLocale(request);
            }
            return locale;
         }
         @Override
         public TimeZone getTimeZone() {
            TimeZone timeZone = (TimeZone) WebUtils.getSessionAttribute(request, TIME_ZONE_SESSION_ATTRIBUTE_NAME);
            if (timeZone == null) {
               timeZone = determineDefaultTimeZone(request);
            }
            return timeZone;
         }
      };
   }

   @Override
   public void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext) {
      Locale locale = null;
      TimeZone timeZone = null;
      if (localeContext != null) {
         locale = localeContext.getLocale();
         if (localeContext instanceof TimeZoneAwareLocaleContext) {
            timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
         }
      }
      WebUtils.setSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME, locale);
      WebUtils.setSessionAttribute(request, TIME_ZONE_SESSION_ATTRIBUTE_NAME, timeZone);
   }


   /**
    * Determine the default locale for the given request,
    * Called if no Locale session attribute has been found.
    * <p>The default implementation returns the specified default locale,
    * if any, else falls back to the request's accept-header locale.
    * @param request the request to resolve the locale for
    * @return the default locale (never {@code null})
    * @see #setDefaultLocale
    * @see javax.servlet.http.HttpServletRequest#getLocale()
    */
   protected Locale determineDefaultLocale(HttpServletRequest request) {
      Locale defaultLocale = getDefaultLocale();
      if (defaultLocale == null) {
         defaultLocale = request.getLocale();
      }
      return defaultLocale;
   }

   /**
    * Determine the default time zone for the given request,
    * Called if no TimeZone session attribute has been found.
    * <p>The default implementation returns the specified default time zone,
    * if any, or {@code null} otherwise.
    * @param request the request to resolve the time zone for
    * @return the default time zone (or {@code null} if none defined)
    * @see #setDefaultTimeZone
    */
   protected TimeZone determineDefaultTimeZone(HttpServletRequest request) {
      return getDefaultTimeZone();
   }

}

image-20220607165834448

image-20220607171546177

代码实现:

@RequestMapping(value = "/toLoginPage")
public String login2(@RequestParam(value = "locale",defaultValue = "zh_CN") String localestr, Locale locale, HttpSession session) {
    Locale loc = null;
    //如果带了locale参数就用参数指定的区域信息,如果没有就用请求头自带的区域信息
    if (localestr != null && !"".equals(localestr)) {
        String[] s = localestr.split("_");
        loc = new Locale(s[0], s[1]);//public Locale(@NotNull String language,@NotNull String country)
    } else {
        loc = locale;
    }
    session.setAttribute(SessionLocaleResolver.class.getName() + ".LOCALE",loc);
    return "login";
}

image-20220607172812905

image-20220607172859218

image-20220607172955840

结果:

image-20220607173033644

image-20220607173209329

8-6 SessionLocaleResolver配合

image-20220607205706234

image-20220607205538980

代码参考:

<!--    使用SessionLocaleResolver解析器-->
    <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"></bean>
    <mvc:interceptors>
        <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>
    </mvc:interceptors>

image-20220607205901741

结果:

image-20220607205826923

8-6 Filter与拦截器

image-20220607210220256

第9章-异常处理

9-1 异常处理流程(源码)

image-20220607225548571

image-20220607230137337

image-20220607231037763

image-20220607231140607

源码调试:

image-20220607233211979

image-20220607233857873

image-20220607234317454

image-20220607234613094

image-20220607234847451

补充:将该算术异常抛给Tomcat服务器,将会以错误页面显示。

image-20220607235829745

image-20220608000104815

9-2 异常处理-@ExceptionHandler

image-20220608002739319

代码参考:

package com.xu1.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

/**
 * @auther xu
 * @Description
 * @date 2022/6/7 - 23:22
 * @Modified By:
 */
@Controller
public class ExceptionTestController {

    @RequestMapping(value = "/exceptionHandle01")
    public String handle01(@RequestParam(value = "integerParam") Integer integer) {
        System.out.println("handle01.....");
        System.out.println(10/integer);
        return "success";
    }

    /**
     *
     * @Description
     * 搞SpringMVC,这个方法专门用来处理这个类的异常
     * @Date 2022/6/8 0:15
     *
     */
    @ExceptionHandler(value = {ArithmeticException.class})
    public ModelAndView handleException(Exception e) {

        System.out.println("所报异常为:"+e);
        ModelAndView myError = new ModelAndView("myError");//同样使用视图解析器,拼接转发地址
        myError.addObject("exception",e);
        return myError;
    }
}

image-20220608002545380

结果:

image-20220608002626477

9-2 异常处理--@ExceptionHandle--建造异常处理类

image-20220608003241081

image-20220608003724106

代码参考:

package com.xu1.controller;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

/**
 * @auther xu
 * @Description
 * 直接建一个类,用于集中处理异常
 * @date 2022/6/8 - 0:30
 * @Modified By:
 */
@ControllerAdvice
public class FocusHandleException {
    /**
     *
     * @Description
     * 搞SpringMVC,这个方法专门用来处理这个类的异常
     * @Date 2022/6/8 0:15
     *
     */
    @ExceptionHandler(value = {ArithmeticException.class})
    public ModelAndView handleException01(Exception e) {

        System.out.println("全局的handleException01:"+e);
        ModelAndView myError = new ModelAndView("myError");//同样使用视图解析器,拼接转发地址
        myError.addObject("exception",e);
        return myError;
    }

    @ExceptionHandler(value = {Exception.class})
    public ModelAndView handleException02(Exception e) {

        System.out.println("全局的handleException02:"+e);
        ModelAndView myError = new ModelAndView("myError");//同样使用视图解析器,拼接转发地址
        myError.addObject("exception",e);
        return myError;
    }
}

由此,产生一个问题,对于同一个异常,在异常处理类中有对应的方法处理,在本类中也有方法处理,到底是调用本类的还是全局异常处理方法?

答:本类优先

验证:

image-20220608004140542

image-20220608004407491

image-20220608004727186

小结:

image-20220608004759624

9-3 异常处理--@ResponseStatus

image-20220608010026223

image-20220608010231200

代码参考:

image-20220608011309355

ExceptionTestController.java

/**
 *
 * @Description
 * 用于测试@ResponseStatus
 * @Date 2022/6/8 1:11
 *
 */
@RequestMapping(value = "/exceptionHandle02")
public String handle02(@RequestParam(value = "username") String username) {
  if (!"admin".equals(username)) {
      System.out.println("登录失败。。。");
      throw new UserNameNoFoundException();
  }
    System.out.println("登录成功。。。。");
    return "success";
}

UserNameNoFoundException.java:自定义异常类

package com.xu1.controller;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

/**
 * @auther xu
 * @Description
 * @date 2022/6/8 - 0:54
 * @Modified By:
 */
@ResponseStatus(reason = "用户拒绝登录",value = HttpStatus.NOT_ACCEPTABLE)
public class UserNameNoFoundException extends RuntimeException{
    private static final long serialVersionUID = 1L;
}

image-20220608011418226

处理结果:

username = admin:此时不会出现异常

image-20220608010743748

username != admin:此时会出现异常

image-20220608011124357

总结:@ResponseStatus运用在异常类上(eg:自定义异常类)

9--4 @DefaultHandleExceptionResolver

image-20220608011903668

image-20220608012926710

源码探究:

image-20220608013544608

image-20220608013611571

image-20220608013834494

@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
      Object handler, Exception ex) {

   try {
      if (ex instanceof NoSuchRequestHandlingMethodException) {
         return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, request, response,
               handler);
      }
      else if (ex instanceof HttpRequestMethodNotSupportedException) {
         return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request,
               response, handler);
      }
      else if (ex instanceof HttpMediaTypeNotSupportedException) {
         return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, request, response,
               handler);
      }
      else if (ex instanceof HttpMediaTypeNotAcceptableException) {
         return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, request, response,
               handler);
      }
      else if (ex instanceof MissingServletRequestParameterException) {
         return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, request,
               response, handler);
      }
      else if (ex instanceof ServletRequestBindingException) {
         return handleServletRequestBindingException((ServletRequestBindingException) ex, request, response,
               handler);
      }
      else if (ex instanceof ConversionNotSupportedException) {
         return handleConversionNotSupported((ConversionNotSupportedException) ex, request, response, handler);
      }
      else if (ex instanceof TypeMismatchException) {
         return handleTypeMismatch((TypeMismatchException) ex, request, response, handler);
      }
      else if (ex instanceof HttpMessageNotReadableException) {
         return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, request, response, handler);
      }
      else if (ex instanceof HttpMessageNotWritableException) {
         return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, request, response, handler);
      }
      else if (ex instanceof MethodArgumentNotValidException) {
         return handleMethodArgumentNotValidException((MethodArgumentNotValidException) ex, request, response, handler);
      }
      else if (ex instanceof MissingServletRequestPartException) {
         return handleMissingServletRequestPartException((MissingServletRequestPartException) ex, request, response, handler);
      }
      else if (ex instanceof BindException) {
         return handleBindException((BindException) ex, request, response, handler);
      }
      else if (ex instanceof NoHandlerFoundException) {
         return handleNoHandlerFoundException((NoHandlerFoundException) ex, request, response, handler);
      }
   }
   catch (Exception handlerException) {
      logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
   }
   return null;
}

9-4 异常处理--基于配置的异常处理---SimpleMappingExceptionResolver

注意以下异常处理器对于异常处理是有先后顺序的,以下实验是ResponseStatusExceptionResolver处理器和ExceptionHandlerExceptionResolver处理器都不具备对空指针异常处理的情况下实现,SimpleMappingExceptionResolver处理器实现对NullPointerException异常的处理。

image-20220608073805147

springMVC.xml需要加入的组件:

 <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
<!--                key:异常全类名;value:要去的页面视图名-->
                <prop key="java.lang.NullPointerException">myError</prop>
            </props>
        </property>
<!--        指定错误信息取出时使用的key-->
        <property name="exceptionAttribute" value="exeptionTest"></property>
    </bean>

测试的目标方法:(造了一个空指针异常)

/**
 *
 * @Description
 * 用于测试--SimpleMappingExceptionResolver的使用
 * @Date 2022/6/8 7:42
 *
 */
@RequestMapping(value = "/handle04")
public String handle04() {
    System.out.println("handle04....");
    //造一个空指针异常
    String str = null;
    System.out.println(str.charAt(2));

    return "success";
}

前端发送的请求:

image-20220608080901444

处理结果:

错误页面获取错误信息:

获取错误信息方式1:

image-20220608080114519

image-20220608080152562

获取错误信息方式2:

image-20220608080439051

image-20220608080616970

源码验证SimpleMappingExceptionResolver:

image-20220608082142421

第10章--总结及其Spring-SpringMVC整合

10-1:运行流程

SpringMVC运行流程:
1、所有请求,前端控制器(DispatcherServlet)收到请求,调用doDispatch进行处理

2、根据HandlerMapping中保存的请求映射信息找到,处理当前请求的,处理器执行链(包含拦截器)

3、根据当前处理器找到他的HandlerAdapter(适配器)

4、拦截器的preHandle先执行

5、适配器执行目标方法,并返回ModelAndView

1)、ModelAttribute注解标注的方法提前运行

​ 2)、执行目标方法的时候(确定目标方法用的参数)

​ 1)、有注解

​ 2)、没注解:

​ 1)、 看是否Model、Map以及其他的

​ 2)、如果是自定义类型

​ 1)、从隐含模型中看有没有,如果有就从隐含模型中拿

​ 2)、如果没有,再看是否SessionAttributes标注的属性,如果是从Session中拿,如果拿不到会抛异常 3)、都不是,就利用反射创建对象6、拦截器的postHandle执行7、处理结果;(页面渲染流程)

​ 1)、如果有异常使用异常解析器处理异常;处理完后还会返回ModelAndView

​ 2)、调用render进行页面渲染

​ 1)、视图解析器根据视图名得到视图对象

​ 2)、视图对象调用render方法;

​ 3)、执行拦截器的afterCompletion;

图示:

image-20220610085443710

10-2 SpringMVC-Spring整合

SpringMVC和Spring整合的目的;

分工明确;

SpringMVC的配置文件就来配置和网站转发逻辑以及网站功能有关的(视图解析器,文件上传解析器,支持ajax,xxx);

Spring的配置文件来配置和业务有关的(事务控制,数据源,xxx);

image-20220610102054769

1.保证2个容器不重复创建对象

image-20220610102557672

image-20220610102359643

2.子容器与父容器使用组件bean要求:

image-20220610100608755

image-20220610103158463

3.代码参考:

image-20220610102717456

BookController.java

package com.xu1.controller;

import com.xu1.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @auther xu
 * @Description
 * @date 2022/6/10 - 9:01
 * @Modified By:
 */
@Controller
public class BookController {

    @Autowired
    BookService bookService;

    public BookController() {
        System.out.println("BookController类被创建。。。。");
    }

    @RequestMapping(value = "/hello")
    public String handle01() {
        System.out.println(bookService);
        return"success";
    }
}

BookService.java

package com.xu1.service;

import org.springframework.stereotype.Service;

/**
 * @auther xu
 * @Description
 * @date 2022/6/10 - 9:14
 * @Modified By:
 */
@Service
public class BookService {

    public BookService () {
        System.out.println("BookService类被创建。。。。");
    }
}

spring.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<!--    保证spring扫描其他组件-->
    <context:component-scan base-package="com.xu1">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
    </context:component-scan>
</beans>

SpringMVC.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd ">

<!--    保证SpringMVC只扫描了控制器组件-->
    <context:component-scan base-package="com.xu1" use-default-filters="false">
        <context:include-filter type="annotation"
                                expression="org.springframework.stereotype.Controller"/>
        <context:include-filter type="annotation"
                                expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
    </context:component-scan>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>


</beans>

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring.xml</param-value>
    </context-param>
<!--    保证项目一启动,就启动ioc容器-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:SpringMVC.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>
posted @ 2022-06-10 10:40  远道而重任  阅读(5)  评论(0编辑  收藏  举报