Подробное объяснение знаний SpringMVC, включая ключевой анализ исходного кода! ! !

Эта статья является учебной заметкой для SpringMVC2021 в Силиконовой долине на станции b , и я надеюсь, что она будет полезна всем для изучения! ! !

Каталог статей

1. Введение в Spring MVC

1. Что такое MVC

MVC — это идея архитектуры программного обеспечения, которая делит программное обеспечение на модели, представления и контроллеры.

M: Модель, уровень модели, относится к JavaBean в проекте, роль заключается в обработке данных.

JavaBean делится на две категории:

  • Один класс называется Entity Bean: он специально хранит бизнес-данные, такие как Student, User и т. д.
  • Один класс называется компонентом бизнес-обработки: он относится к объекту Service или Dao, который специально используется для обработки бизнес-логики и доступа к данным.

V: Просмотр, уровень просмотра, относится к таким страницам, как html или jsp в проекте, который используется для взаимодействия с пользователями и отображения данных.

C: Контроллер, уровень управления, относится к сервлету в проекте, роль которого состоит в том, чтобы получать запросы и отвечать на запросы браузеров.

Рабочий процесс MVC:
пользователь отправляет запрос на сервер через уровень представления, и запрос принимается контроллером на сервере. Контроллер вызывает соответствующий уровень модели для обработки запроса. После обработки результат возвращается контроллеру. Затем контроллер находит соответствующее представление представления в соответствии с результатом обработки запроса и, наконец, отвечает браузеру после рендеринга данных.

2. Что такое Spring MVC

SpringMVC — это продолжение Spring и подпроект Spring.

SpringMVC — это полный набор решений, предоставляемых Spring для разработки уровня представления. После того, как структура уровня представления претерпела последовательные изменения во многих продуктах, таких как Strut, WebWork и Strut2, индустрия обычно выбирает SpringMVC в качестве предпочтительного решения для разработки уровня представления проектов Java EE .

Примечание: трехуровневая архитектура разделена на уровень представления (или уровень представления), уровень бизнес-логики и уровень доступа к данным. Уровень представления представляет собой страницу переднего плана и фоновый сервлет.

3. Характеристики Spring MVC

  • Нативные продукты семейства Spring , беспрепятственно взаимодействующие с инфраструктурой, такой как контейнеры IOC.
  • На основе собственного Servlet через мощный фронт-контроллер DispatcherServlet запрос и ответ обрабатываются единообразно.
  • Проблемы, которые необходимо решить в каждом сегменте уровня представления, охватываются всесторонне , и предоставляются комплексные решения .
  • Код свежий и лаконичный , что значительно повышает эффективность разработки.
  • Степень внутренней компонентизации высока, а подключаемые компоненты можно подключать и воспроизводить Вы можете настроить соответствующие компоненты для любой функции, которую хотите.
  • Отличная производительность , особенно подходящая для требований современных крупномасштабных и сверхкрупных интернет-проектов.

Два, привет мир

(1) Создайте проект maven:

изображение-20210809114519055

(2) Файл pom.xml задает метод упаковки для военного пакета и вводит необходимые зависимости.

<groupId>org.oymn</groupId>
<artifactId>SpringMVC-demo1</artifactId>
<version>1.0-SNAPSHOT</version>
<!--设置打包方式:war-->
<packaging>war</packaging>

<dependencies>
    <!-- SpringMVC -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.1</version>
    </dependency>

    <!-- 日志 -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>

    <!-- ServletAPI -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>

    <!-- Spring5和Thymeleaf整合包 -->
    <dependency>
        <groupId>org.thymeleaf</groupId>
        <artifactId>thymeleaf-spring5</artifactId>
        <version>3.0.12.RELEASE</version>
    </dependency>
</dependencies>

(3) Создайте каталог webapp в основном каталоге и добавьте файл конфигурации web.xml в каталог webapp.

Создайте каталог веб-приложения:

изображение-20210809115645185

Добавьте файл web.xml:

изображение-20210809115748867

Измените каталог вручную:

изображение-20210809120121036

результат:

изображение-20210809120808007

(4) Настройте web.xml :

Зарегистрируйте передний контроллер SpringMVC DispatcherServlet

a> Режим конфигурации по умолчанию:

В этой конфигурации файл конфигурации SpringMVC по умолчанию находится в WEB-INF, а имя по умолчанию — <servlet-name>-servlet.xml.Например, файл конфигурации SpringMVC, соответствующий следующей конфигурации, находится в WEB-INF, а имя файла — springMVC-servlet.xml.

<!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 -->
<servlet>
    <servlet-name>springMVC</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>springMVC</servlet-name>
    <!--
        设置springMVC的核心控制器所能处理的请求的请求路径
        /所匹配的请求可以是/login或.html或.js或.css方式的请求路径
        但是/不能匹配.jsp请求路径的请求
    -->
    <url-pattern>/</url-pattern>
</servlet-mapping>

б>Расширенный метод настройки (обычно используйте этот) :

<!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 -->
<servlet>
    <servlet-name>springMVC</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 通过初始化参数指定SpringMVC配置文件的位置和名称 -->
    <init-param>
        <!-- contextConfigLocation为固定值 -->
        <param-name>contextConfigLocation</param-name>
        <!-- 使用classpath:表示从类路径查找配置文件,例如maven工程中的src/main/resources -->
        <param-value>classpath:springMVC.xml</param-value>
    </init-param>
    <!-- 
 		作为框架的核心组件,在启动过程中有大量的初始化操作要做
		而这些操作放在第一次请求时才执行会严重影响访问速度
		因此需要通过此标签将启动控制DispatcherServlet的初始化时间设置成服务器启动时
	-->
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>springMVC</servlet-name>
    <!--
        设置springMVC的核心控制器所能处理的请求的请求路径
        /所匹配的请求可以是/login或.html或.js或.css方式的请求路径
        但是/不能匹配.jsp请求路径的请求
    -->
    <url-pattern>/</url-pattern>
</servlet-mapping>

(5) Создайте файл конфигурации SpringMVC :

<!-- 自动扫描包 -->
<context:component-scan base-package="com.oymn"/>

<!-- 配置Thymeleaf视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
    <!--配置视图解析器的优先级,这说明可以有多个视图解析器-->
    <property name="order" value="1"/>
    <property name="characterEncoding" value="UTF-8"/>
    <property name="templateEngine">
        <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
            <property name="templateResolver">
                <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">

                    <!-- 视图前缀 -->
                    <property name="prefix" value="/WEB-INF/templates/"/>

                    <!-- 视图后缀 -->
                    <property name="suffix" value=".html"/>
                    <property name="templateMode" value="HTML5"/>
                    <property name="characterEncoding" value="UTF-8" />
                </bean>
            </property>
        </bean>
    </property>
</bean>

(6) Создайте контроллер запросов

Хотя фронт-контроллер единообразно обрабатывает запросы, отправленные браузером, разные запросы требуют разных процедур обработки, поэтому необходимо создать контроллер запросов, и каждый метод обработки запросов в контроллере запросов называется методом контроллера.

Контроллер запросов должен быть идентифицирован как компонент уровня управления с помощью аннотации @Controller и передан контейнеру IOC Spring для управления, чтобы SpringMVC мог распознать существование контроллера.

@Controller   //请求控制器
public class HelloController {
    
    

    @RequestMapping("/")     //将路径“/”和index.html页面联系起来
    public String index(){
    
    
        //返回视图名称
        return "index";
    }

    @RequestMapping("/target")  //将路径“/target”和target.html页面联系起来
    public String toTarget() {
    
    
        return "target";
    }
}

(7) Создайте каталог шаблонов в каталоге WEB-INF, а затем создайте в нем страницы index.html и target.html.

index.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>首页</title>
    </head>
    <body>
        <h1>你好</h1>
        <!--用th修饰href属性,然后就可以使用@{} 来自动填充绝对路径了-->
        <a th:href="@{/target}">跳转到target页面</a>
    </body>
</html>

Страница перехода target.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        HelloWorld
    </body>
</html>

(8) Резюме :

Браузер отправляет запрос, и если адрес запроса совпадает с url-шаблоном фронт-контроллера, запрос будет обработан DispatcherServlet фронт-контроллера. Фронт-контроллер прочитает файл конфигурации SpringMVC, найдет контроллер, просканировав компоненты, и сопоставит адрес запроса со значением аннотации @RequestMapping в контроллере.Если совпадение успешно, метод контроллера является методом для обработки запроса. Метод обработки запроса должен возвращать имя представления строкового типа. Это имя представления будет проанализировано преобразователем представления, а также префиксом и суффиксом для формирования полного пути, и представление будет отображаться Thymeleaf и, наконец, **перенаправлено (вместо перенаправления)** на соответствующую страницу.

3. Аннотация @RequestMapping

1. Введение

  • Свяжите запрос с методом контроллера, который обрабатывает запрос, чтобы установить отношение сопоставления.

  • Расположение аннотации @RequestMapping:

    • Изменен класс: представляет исходную информацию о сопоставленном пути запроса.

    • Изменить метод: указывает конкретную информацию сопоставленного пути запроса.

    • @Controller
      @RequestMapping("/test")
      public class RequestMappingController {
              
              
      
      	//此时请求映射所映射的请求的请求路径为:/test/testRequestMapping
          @RequestMapping("/testRequestMapping")
          public String testRequestMapping(){
              
              
              return "success";
          }
      
      }
      
  • Сочетание клавиш Alt + 7 для просмотра всех свойств RequestMapping

    29

2. атрибут значения

  • Атрибут value соответствует отображению запроса по адресу запроса .

  • Атрибут value @RequestMapping представляет собой массив строк , который может сопоставлять запросы, соответствующие нескольким адресам запросов одновременно.

<a th:href="@{/testRequestMapping}">测试@RequestMapping的value属性-->/testRequestMapping</a><br>
<a th:href="@{/test}">测试@RequestMapping的value属性-->/test</a><br>
@RequestMapping(
        value = {
    
    "/testRequestMapping", "/test"}
)
public String testRequestMapping(){
    
    
    return "success";
}

В приведенном выше примере, будь то запрос /test или запрос /testRequestMapping, вы можете перейти на страницу success.html.

3. атрибут метода

  • Атрибут метода соответствует сопоставлению запроса по методу запроса (получить или опубликовать).
  • Атрибут метода @RequestMapping представляет собой массив типа RequestMethod, который может сопоставлять запросы нескольких методов запроса одновременно.
  • Если текущий адрес запроса удовлетворяет атрибуту значения сопоставления запроса, но метод запроса не соответствует атрибуту метода, браузер сообщит 405 , например: Метод запроса «POST» не поддерживается.
<a th:href="@{/test}">测试@RequestMapping的value属性-->/test</a><br>
<form th:action="@{/test}" method="post">
    <input type="submit">
</form>
@RequestMapping(
        value = {
    
    "/testRequestMapping", "/test"},
        method = {
    
    RequestMethod.GET, RequestMethod.POST}
)
public String testRequestMapping(){
    
    
    return "success";
}

Примечание:

  1. Для методов контроллера, обрабатывающих определенные запросы, SpringMVC предоставляет производные аннотации @RequestMapping:

    Аннотация для обработки запросов на получение: @GetMapping

    Аннотации для обработки почтовых запросов: @PostMapping

    Аннотация для обработки запросов на размещение: @PutMapping

    Аннотация для обработки запросов на удаление: @DeleteMapping

  2. Обычно используемые методы запроса: получить, опубликовать, поставить, удалить.

    Однако текущие браузеры поддерживают только два метода запроса: get и post.Если в отправке формы заданы другие методы запроса (поместить или удалить), для обработки будет использоваться метод запроса по умолчанию get.

    Чтобы отправить запрос на размещение или удаление, вам необходимо передать фильтр HiddenHttpMethodFilter, предоставляемый Spring, который будет упомянут в разделе RESTful.

4. атрибут параметров

  • Атрибут params сопоставляет сопоставления запросов по параметрам запроса .
  • Атрибут params представляет собой массив строкового типа, а взаимосвязь соответствия между параметрами запроса и отображением запроса можно установить с помощью четырех выражений.
    • «param»: требуется, чтобы соответствующий запрос содержал параметр запроса param.
    • "!param": Требуется, чтобы соответствующий запрос не содержал параметр запроса param.
    • "param=value": Требуется, чтобы соответствующий запрос содержал параметр запроса param, а значением было значение.
    • "param!=value": требуется, чтобы соответствующий запрос содержал параметр запроса param, но значение не может быть значением
<a th:href="@{/test(username='admin',password=123456)">测试@RequestMapping的params属性-->/test</a><br>
@RequestMapping(
        value = {
    
    "/testRequestMapping", "/test"}
        ,method = {
    
    RequestMethod.GET, RequestMethod.POST}
        ,params = {
    
    "username","password!=123456"}
)
public String testRequestMapping(){
    
    
    return "success";
}

Примечание. Если текущий запрос удовлетворяет атрибуту value и атрибуту метода @RequestMapping, но не удовлетворяет атрибуту params, страница сообщит об ошибке 400 : условия параметра «имя пользователя, пароль! = 123456» не выполнены для фактических параметров запроса: имя пользователя = {admin}, пароль = {123456}

5. атрибут заголовков

  • Атрибут заголовков соответствует отображению запроса по информации заголовка запроса.
  • Атрибут headers представляет собой массив строковых типов, и четыре выражения могут использоваться для установки отношения соответствия между информацией заголовка запроса и сопоставлением запроса.
    • «header»: запрос, соответствующий отображению запроса, должен содержать информацию заголовка запроса заголовка.
    • "!header": запрос, соответствующий отображению запроса, не должен содержать информацию о заголовке запроса заголовка.
    • "header=value": запрос, соответствующий отображению запроса, должен содержать информацию о заголовке запроса заголовка и header=value.
    • "header!=value": запрос, соответствующий отображению запроса, должен содержать информацию о заголовке запроса заголовка и заголовок!=значение
  • Если текущий запрос удовлетворяет атрибуту value и атрибуту метода @RequestMapping, но не удовлетворяет атрибуту headers, страница отображает ошибку 404 , то есть ресурс не найден.
@RequestMapping(
    value="/test1",
    headers={
    
    "Host=localhost:8081"}      //若使用默认端口8080访问,则会报错404
)
public String test1(){
    
    
    return "success";
}

6. SpringMVC поддерживает пути в стиле муравья

(1)? : представляет любой одиночный символ

@RequestMapping("/a?a")    //请求路径中?的位置可以替代成任意单个字符(但不能省略),例如"/aba","/aga","/a:a"等都是可以的
public String test2(){
    
    
    return "success";
}

(2) *: Указывает любые 0 или более символов

@RequestMapping("/a*a")  //请求路径中*的位置可以替代成0个或多个字符,例如"/aba","/aa","/aaaaa"等都是可以的
public String test2(){
    
    
    return "success";
}

(3) **: указывает любой один или несколько слоев каталогов

@RequestMapping("/**/a")   //请求路径中**位置可以替代成0层或多层目录,例如"/a/a","/b/b/b/a","/a"等都是可以的
public String test2(){
    
    
    return "success";
}

7. Поддержка заполнителей путей в SpringMVC

  • Оригинальный метод: /deleteUser?id=1

  • Метод отдыха: /deleteUser/1

Заполнители в пути SpringMVC часто используются в стиле RESTful.Когда параметры передаются на сервер через путь запроса, параметры, представленные заполнителями, могут быть присвоены формальным параметрам метода контроллера через заполнитель {xxx} в атрибуте value аннотации @RequestMapping соответствующего метода контроллера, а затем через аннотацию ** @ PathVariable ** .

<a th:href="@{/test/1/admin}">测试路径中的占位符</a><br/>
@RequestMapping("/test/{id}/{username}")
public String test(@PathVariable("id") Integer id, @PathVariable("username") String username){
    
    
    System.out.println("id="+id+",username="+username);
    return "success";
}

В-четвертых, SpringMVC получает параметры запроса

1. Получить через Servlet API

HttpServletRequest используется как формальный параметр метода контроллера.В это время параметр типа HttpServletRequest представляет объект, который инкапсулирует сообщение запроса текущего запроса.

<a th:href="@{/test01(username='oymn',password='000000')}">测试通过HttpServletRequest获取请求参数</a><br/>
@RequestMapping("/test01")
public String test1(HttpServletRequest request){
    
            //传入HttpServletRequest对象
    String username = request.getParameter("username");
    String password = request.getParameter("password");
    System.out.println("username="+username+",password="+password);
    return "success";
}

2. Автоматически получать параметры запроса через одноимённые формальные параметры

В позиции формального параметра метода контроллера установите формальный параметр с тем же именем, что и у параметра запроса . Когда браузер отправляет запрос и соответствует сопоставлению запроса, DispatcherServlet автоматически назначит параметр запроса соответствующему формальному параметру.

<a th:href="@{/test02(username='oymn',password='000000')}">测试同名形参获取请求参数</a><br/>
@RequestMapping("/test02")
public String test2(String username, String password){
    
           //设置同名形参,自动赋值
    System.out.println("username="+username+",password="+password);
    return "success";
}

Если в запросе есть несколько параметров запроса с одинаковыми именами , например флажок в форме, вы можете установить массив строк или параметр строкового типа , чтобы принять параметр запроса.

Если используется массив строк , массив содержит каждый параметр запроса.

Если используется строковый тип , это строка, объединенная запятыми для параметров запроса.

3. @RequestParam

Из приведенного выше исследования мы знаем, что параметры запроса могут быть автоматически получены через формальные параметры с тем же именем, но когда формальные параметры и параметры запроса несовместимы или когда вы хотите явно указать отношение отображения между формальными параметрами и параметрами запроса, вы можете использовать аннотацию @RequestParam.

@RequestParam имеет три атрибута:

  • value : укажите имя параметра запроса, назначенного в качестве формального параметра.
  • required : Установите, должен ли быть передан параметр запроса, по умолчанию true .
  • defaultValue : Независимо от того, требуется ли значение true или false, когда параметр запроса, указанный значением, не передается или переданное значение является пустой строкой, значение по умолчанию defaultValue используется для присвоения значения формальному параметру.

Примечание. Если для параметра required установлено значение true, но параметр запроса не включен в запрос, а атрибут defaultValue не установлен, будет сообщено об ошибке 400: параметр Required String 'xxx' отсутствует; если для него установлено значение false, параметр запроса не требуется, а если он не передается, значение формального параметра, указанное в аннотации, равно null .

@RequestMapping("/test04")
//value的值为name,对应html文件中请求参数的名字;required设置为false表示该参数不是必须项;defaultValue设置默认值为oymn
public String test4(@RequestParam(value="name",required=false,defaultValue="oymn")String username){
    
    
    System.out.println(username);
    return "success";
}
<a th:href="@{/test04(name='oymn')}">测试RequestParam注解</a><br/>

4. @RequestHeader

@RequestHeader создает отношение сопоставления между информацией заголовка запроса и формальными параметрами метода контроллера.

Аннотация @RequestHeader имеет три атрибута: value, required, defaultValue, использование такое же, как у @RequestParam.

5. @CookieValue

@CookieValue предназначен для создания отношения сопоставления между данными cookie и параметрами метода контроллера.

Аннотация @CookieValue имеет три атрибута: value, required, defaultValue, использование такое же, как у @RequestParam.

@RequestMapping("/test05")
public String test5(@CookieValue("JSESSIONID") String JSESSIONID){
    
       //获取session的cookie
    System.out.println(JSESSIONID);
    return "success";
}

6. Получить параметры запроса через POJO

Формальный параметр метода контроллера устанавливается в объект класса сущностей.При этом, если имя параметра запроса, отправляемого браузером, соответствует имени атрибута объекта класса сущностей , то атрибуту класса сущностей будет автоматически присвоено значение.

<form th:action="@{/testpojo}" method="post">
    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    性别:<input type="radio" name="sex" value=""><input type="radio" name="sex" value=""><br>
    年龄:<input type="text" name="age"><br>
    邮箱:<input type="text" name="email"><br>
    <input type="submit">
</form>
@RequestMapping("/testpojo")
public String testPojo(User user){
    
    
    System.out.println(user.toString());
    return "success";
}

В приведенном выше примере, если форма отправляется по почте , пол будет искажен , когда пользовательский объект выводится в методе контроллера testPojo() (потому что он на китайском языке), и проблему с искажением необходимо решить; если форма отправляется с помощью get, проблема с искажением не возникнет (если это произойдет, это должна быть проблема с котом).

7. Решить искаженную проблему получения параметров запроса

Чтобы решить искаженную проблему, вам нужно обработать его на этапе фильтрации , потому что отправка запроса будет проходить слушателя, фильтр, а затем служить.Если вы установите его через метод request.SetCharacterenCoding(), например JavaWeb, потому что этот метод должен быть выполнен до получения параметра запроса, а srpingmvc проходит через S через s. необходимо заранее перейти к стадии фильтрации.

SpringMVC предоставляет фильтр кодирования CharacterEncodingFilter , который необходимо зарегистрировать в файле web.xml.

<!--配置SpringMVC的编码过滤器-->
<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>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Часть исходного кода CharacterEncodingFilter :

public class CharacterEncodingFilter extends OncePerRequestFilter {
    
    
    @Nullable
    private String encoding;     //这里就是设置编码的属性
    private boolean forceRequestEncoding;  //强制设置请求编码,默认为false
    private boolean forceResponseEncoding; //强制设置响应编码,默认为false

    //无参构造函数
    public CharacterEncodingFilter() {
    
    
        this.forceRequestEncoding = false;
        this.forceResponseEncoding = false;
    }
    
    //这个就是过滤器的执行方法dofilter
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
    
        
        String encoding = this.getEncoding();   //如果没有设置默认为null
        
        if (encoding != null) {
    
       //如果设置了encoding
            
            //request.getCharacterEncoding()==null这个条件表明了只要之前没有设置过编码,请求编码就可以设置成功了
            if (this.isForceRequestEncoding() || request.getCharacterEncoding() == null) {
    
    
                request.setCharacterEncoding(encoding);
            }
            
			//想要设置响应编码,还需要将forceResponseEncoding设置为true
            if (this.isForceResponseEncoding()) {
    
    
                response.setCharacterEncoding(encoding);
            }
        }
		
        filterChain.doFilter(request, response);
    }
    
}

С помощью приведенного выше анализа исходного кода, если вы хотите установить кодировку ответа, вам необходимо установить для кодирования и forceResponseEncoding значение true , Таким образом, более полный фильтр кодирования выглядит следующим образом :

<!--配置SpringMVC的编码过滤器-->
<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>forceResponseEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Примечание. Фильтр кодирования SpringMVC необходимо настроить перед другими фильтрами, иначе он будет недействительным.

5. Объекты домена обмениваются данными

1. Используйте ServletAPI для обмена данными с объектами домена запроса.

@RequestMapping("/testscope")
public String testScope(HttpServletRequest request){
    
    
    request.setAttribute("hello","testScopeByServletAPI");   //调用setAttribute方法
    return "success";
}
<p th:text="${hello}"></p>   

2. Используйте ModelAndView для обмена данными с объектами домена запроса.

ModelAndView имеет функции Model и View:

  • Модель: используется для обмена данными с доменом запроса.
  • Вид: используется для установки вида и реализации перехода на страницу.
@RequestMapping("/testscope2")
public ModelAndView testScope2(){
    
    
    
    //创建ModelAndView对象
    ModelAndView mav = new ModelAndView();
    
    //向请求域中共享数据
    mav.addObject("hello","hello,testScopeByModelAndView");
    
    //设置视图,实现页面跳转
    mav.setViewName("success");
    
    return mav;
}

3. Используйте модель для обмена данными с объектами домена запроса.

@RequestMapping("/testscope3")
public String testScope3(Model model){
    
    
    model.addAttribute("hello","testScopeByModel");
    return "success";
}

4. Используйте карту для обмена данными с объектами домена запроса.

@RequestMapping("/testscope4")
public String testScope4(Map<String, Object> map){
    
    
    map.put("hello", "testScopeByMap");
    return "success";
}

5. Используйте ModelMap для обмена данными с объектами домена запроса.

@RequestMapping("/testscope5")
public String testScope5(ModelMap modelMap){
    
    
    modelMap.addAttribute("hello","testScopeByModelMap");
    return "success";
}

6. Связь между Model, ModelMap и Map

System.out.println(model.getClass().getName());

System.out.println(modelMap.getClass().getName());

System.out.println(map.getClass().getName());

Выведите полное имя класса реализации этих трех параметров в методе контроллера, и выходные результаты будут следующими: org.springframework.validation.support.BindingAwareModelMap, что указывает на то, что параметры типов Model, ModelMap и Map по существу являются объектами BindingAwareModelMap.

//ModelMap是ExtendedModelMap的父类,ExtendedModelMap是BindingAwareModelMap的父类
//相当于ModelMap是BindingAwareModelMap的父类
public class ExtendedModelMap extends ModelMap implements Model {
    
    }   
public class BindingAwareModelMap extends ExtendedModelMap {
    
    }  

//LinkedHashMap是ModelMap的父类,而LinkedHashMap实现了Map,相当于ModelMap实现了Map
//而BindingAwareModelMap是ModelMap的子类,相当于BindingAwareModelMap实现了Map
public class ModelMap extends LinkedHashMap<String, Object> {
    
    }
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {
    
    }

//ExtendedModelMap实现了Model,相当于BindingAwareModelMap实现了Model
public interface Model {
    
    }
public class ExtendedModelMap extends ModelMap implements Model {
    
    } 

Схема отношений выглядит следующим образом:

изображение-20210812182433880

7. Делитесь данными с доменом сеанса

@RequestMapping("/testscope6")
public String testScope6(HttpSession session){
    
    
    session.setAttribute("hello","testSession");
    return "success";	
}
<p th:text="${session.hello}"></p>

8. Делитесь данными с доменом приложения

@RequestMapping("/testscope7")
public String testScope7(HttpSession session){
    
    
    ServletContext application = session.getServletContext();    //先获取ServletContext对象
    application.setAttribute("hello","testApplication");
    return "success";
}
<p th:text="${application.hello}"></p>

6. Вид SpringMVC

Представление в SpringMVC — это интерфейс представления, функция представления — визуализировать данные и отображать данные в модели модели для пользователя.

В SpringMVC существует множество видов представлений, и по умолчанию есть перенаправленные представления и перенаправленные представления .

  • Когда в проект будет введена зависимость jstl , представление переадресации будет автоматически преобразовано в jstlView .

  • Когда используется технология представления Thymeleaf , то есть преобразователь представления Thymeleaf настраивается в файле конфигурации SpringMVC, а ThymeleafView получается после разрешения преобразователя представления .

1. ТимелеафВью

Когда имя представления, установленное в методе контроллера, не имеет префикса , имя представления в это время будет проанализировано преобразователем представления в файле конфигурации SpringMVC , а окончательный путь, полученный путем соединения имени представления с префиксом представления и суффиксом представления, будет перенаправлен путем перенаправления .

@RequestMapping("/testThymeleaf")
public String testThymeleaf(){
    
    
    return "success";
}

Отследить исходный код:

Разбейте точку в методе контроллера, а затем найдите метод doDispatch в стеке методов отладки.В это время метод doDispatch выполняется до этой строки:

изображение-20210813093821919

Затем выполните программу для обработки DispatchReuslt и введите метод:

изображение-20210813093937115

В методе processDispatchResult выполните метод рендеринга и введите:

изображение-20210813094039791

После выполнения метода resolveViewName в методе рендеринга вы можете видеть, что в настоящее время представление является объектом ThymeleafView.

изображение-20210813094254713

2. Предварительный просмотр InternalResourceView

Представление пересылки по умолчанию в SpringMVC — InternalResourceView .

Ситуация создания перенаправления представления в SpringMVC: Когда имя представления, заданное в методе контроллера, имеет префикс «forward:» , представление InternalResourceView будет создано первым . В это время имя представления не будет анализироваться синтаксическим анализатором представления, настроенным SpringMVC, но префикс «forward:» будет удален , а оставшаяся часть будет использоваться в качестве конечного пути для реализации перехода через перенаправление .

@RequestMapping("/testInternalResourceView")
public String testInternalResourceView(){
    
    
    return "forward:/testThymeleaf";
}
изображение-20210813220019115

3. Перенаправление просмотра RedirectView

Представление перенаправления SpringMVC по умолчанию — RedirectView .

Ситуация создания представления перенаправления в SpringMVC: Когда имя представления, заданное в методе контроллера, имеет префикс "redirect:" , представление RedirectView будет создано первым . В это время имя представления не будет анализироваться синтаксическим анализатором представления, настроенным SpringMVC, но префикс "redirect:" будет удален , а оставшаяся часть будет использоваться в качестве конечного пути для реализации перехода через перенаправление.

@RequestMapping("/testRedirectView")
public String testRedirectView(){
    
    
    return "redirect:/testThymeleaf";
}
изображение-20210813215519424

Примечание. При анализе перенаправленного представления сначала удаляется префикс redirect:, а затем оценивается, начинается ли оставшаяся часть с /, и если да, то контекстный путь будет автоматически объединен.

4. Контроллер просмотра

Когда метод контроллера используется только для реализации перехода по страницам , то есть когда нужно установить только имя представления, вместо метода контроллера можно использовать тег представления-контроллера.

<!--
	path:设置处理的请求地址
	view-name:设置请求地址所对应的视图名称
-->
<mvc:view-controller path="/testView" view-name="success"></mvc:view-controller>
<!--开启mvc注解驱动-->
<mvc:annotation-driven />

5. Парсер представления, используемый jsp-страницей

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/templates/"></property>     <!--视图前缀-->
    <property name="suffix" value=".jsp"></property>					<!--视图后缀-->
</bean>

Семь, RESTful

1. ОТДЫХ

  • REST: Передача состояния представления , передача состояния ресурса на уровне представления .

В протоколе Http есть четыре глагола: GET, POST, PUT и DELETE , соответствующие четырем основным операциям: GET используется для получения ресурсов, POST используется для создания новых ресурсов, PUT используется для обновления ресурсов и DELETE используется для удаления ресурсов, то есть добавления, удаления, изменения и проверки.

Стиль REST поддерживает использование единого стиля для URL-адресов. Каждое слово разделяется символом "/" спереди назад. Вместо использования пар "ключ-значение" с вопросительным знаком для передачи параметров запроса параметры запроса, отправляемые на сервер, используются как часть URL-адреса, чтобы обеспечить единообразие общего стиля .

действовать традиционный способ ОСТАЛЬНЫЙ стиль
операция запроса получитьUserById?id=1 user/1->получить метод запроса
сохранить операцию saveUser пользователь -> метод отправки запроса
удалить операцию удалитьпользователя?id=1 user/1->удалить метод запроса
операция обновления updateUser user->put метод запроса

2. Скрытый фильтр метода HTTP

Поскольку браузер поддерживает только отправку запросов на получение и отправку, как отправлять запросы на размещение и удаление?

SpringMVC предоставляет HiddenHttpMethodFilter , чтобы помочь нам преобразовать запросы POST в запросы DELETE или PUT.

HiddenHttpMethodFilter обрабатывает условия для запросов на размещение и удаление:

  • Метод запроса текущего запроса должен быть post
  • Текущий запрос должен передавать параметр запроса _method

Если вышеуказанные условия соблюдены, фильтр HiddenHttpMethodFilter преобразует метод запроса текущего запроса в значение параметра запроса _method , поэтому значение параметра запроса _method является окончательным методом запроса.

Зарегистрируйте фильтр HiddenHttpMethodFilter в файле web.xml:

<!--配置HiddenHttpMethodFilter-->
<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>
<form th:action="@{/user}" method="post">                    <!--条件1:设置请求方式为post-->
    <input type="hidden" name="_method" value="put">         <!--条件2:通过隐藏域传输请求参数_method-->
    <input type="submit" >
</form>

Примечание:

На данный момент в SpringMVC предусмотрено два фильтра: CharacterEncodingFilter и HiddenHttpMethodFilter.

При регистрации в web.xml необходимо сначала прописать CharacterEncodingFilter, а затем прописать HiddenHttpMethodFilter

(CharacterEncodingFilter записывается перед HiddenHttpMethodFilter в файле web.xml)

причина:

  • В CharacterEncodingFilter установите набор символов с помощью метода request.setCharacterEncoding(encoding)

  • Метод request.setCharacterEncoding(encoding) не требует никаких предыдущих операций для получения параметров запроса.

  • А HiddenHttpMethodFilter имеет ровно одну операцию для получения метода запроса:

  • String paramValue = request.getParameter(this.methodParam);
    

Отследить исходный код:

Самое главное в фильтре — это метод его выполнения.Во-первых, начните отсюда: doFilter (doFilterInternal)

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
    
    
    HttpServletRequest requestToUse = request;
    
    //这个判断需要满足两个条件:request是一个post请求;
    //而request.getAttribute("javax.servlet.error.exception")为null 可以认为是恒成立的,这里不太清楚
    if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) 	 {
    
    
        
        //这里的methodParam是一个字符串,值为"_method"
        String paramValue = request.getParameter(this.methodParam);
        
        //如果_method的值有长度,也就是不为空
        if (StringUtils.hasLength(paramValue)) {
    
    
           
            //将_method的值转换为大写
            String method = paramValue.toUpperCase(Locale.ENGLISH);
            
            //ALLOWED_METHODS是一个List集合,包含"PUT","DELETE","PATCH"
            if (ALLOWED_METHODS.contains(method)) {
    
    
                //HttpMethodRequestWrapper是HiddenHttpMethodFilter的一个内部类
                //这里就是根据_method的值也就是真正的请求方式重新创建了一个请求对象
                requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
            }
        }
    }
	//从这里也可以看出,过滤器在放行时用的是根据_method的值新创建的请求对象
    filterChain.doFilter((ServletRequest)requestToUse, response);
}

八、HttpMessageConverter

HttpMessageConverter, преобразователь информации о сообщении, преобразует сообщение запроса в объект Java или преобразует объект Java в ответное сообщение .

HttpMessageConverter предоставляет две аннотации и два типа: @RequestBody, @ResponseBody, RequestEntity, ResponseEntity .

1、@RequestBody

@RequestBody может получить тело запроса, вам нужно установить формальный параметр в методе контроллера, использовать @RequestBody для идентификации, тело запроса текущего запроса присвоит значение формальному параметру, указанному текущей аннотацией

<form th:action="@{/testRequestBody}" method="post">
    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    <input type="submit">
</form>
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String requestBody){
    
    
    System.out.println("requestBody:"+requestBody);
    return "success";
}

Выходной результат:

тело запроса: имя пользователя = admin и пароль = 123456

2、ЗапросЭнтити

RequestEntity инкапсулирует тип сообщения запроса, который необходимо установить в формальном параметре метода контроллера, а сообщение запроса текущего запроса будет присвоено формальному параметру, а информация заголовка запроса может быть получена через getHeaders(), а информация тела запроса может быть получена через getBody()

@RequestMapping("/testRequestEntity")
public String testRequestEntity(RequestEntity<String> requestEntity){
    
    
    System.out.println("requestHeader:"+requestEntity.getHeaders());
    System.out.println("requestBody:"+requestEntity.getBody());
    return "success";
}

输出结果:
requestHeader:[host:"localhost:8080", connection:"keep-alive", content-length:"27", cache-control:"max-age=0", sec-ch-ua:"" Not A;Brand";v="99", "Chromium";v="90", "Google Chrome";v="90"", sec-ch-ua-mobile:"?0", upgrade- незащищенные запросы: «1», происхождение: «http://localhost:8080», пользовательский агент: «Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, например Gecko) Chrome/90.0.4430.93 Safari/537.36»] requestBody:username=admin&password=
123

3、@ResponseBody

@ResponseBody используется для идентификации метода контроллера, а возвращаемое значение метода может быть напрямую передано браузеру в качестве тела ответа ответного сообщения.

@RequestMapping("/testResponseBody")
@ResponseBody
public String testResponseBody(){
    
    
    return "success";
}

Результат: страница браузера показывает успех

4. Spring MVC обрабатывает json

Шаги для @ResponseBody для обработки json:

a> импортировать зависимости Джексона

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.1</version>
</dependency>

b> Включите драйвер аннотаций mvc в основном файле конфигурации SpringMVC.В это время в HandlerAdaptor будет автоматически собран преобразователь сообщений : MappingJackson2HttpMessageConverter, который может преобразовать Java-объект, отвечающий браузеру, в строку в формате Json

<mvc:annotation-driven />

c> Используйте аннотацию @ResponseBody в методе процессора для идентификации

d> Верните объект Java непосредственно в качестве возвращаемого значения метода контроллера, и он будет автоматически преобразован в строку в формате Json.

@RequestMapping("/testResponseUser")
@ResponseBody
public User testResponseUser(){
    
    
    return new User(1001,"admin","123456",23,"男");
}

Результаты отображаются на странице браузера:

{«id»: 1001, «имя пользователя»: «admin», «пароль»: «123456», «возраст»: 23, «пол»: «男»}

5. Spring MVC обрабатывает ajax

а> гиперссылка запроса:

<div id="app">
	<a th:href="@{/testAjax}" @click="testAjax">testAjax</a><br>
</div>

b> Обрабатывать события кликов через vue и axios:

<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
<script type="text/javascript" th:src="@{/static/js/axios.min.js}"></script>
<script type="text/javascript">
    var vue = new Vue({
      
      
        el:"#app",
        methods:{
      
      
            testAjax:function (event) {
      
      
                axios({
      
      
                    method:"post",
                    url:event.target.href,
                    params:{
      
      
                        username:"admin",
                        password:"123456"
                    }
                }).then(function (response) {
      
      
                    alert(response.data);
                });
                event.preventDefault();
            }
        }
    });
</script>

c> Метод контроллера:

@RequestMapping("/testAjax")
@ResponseBody
public String testAjax(String username, String password){
    
    
    System.out.println("username:"+username+",password:"+password);
    return "hello,ajax";
}

6. Аннотация @RestController

Аннотация @RestController — это составная аннотация, предоставляемая SpringMVC, она помечается в классе контроллера, что эквивалентно добавлению аннотации @Controller к классу и добавлению аннотации @ResponseBody к каждому методу в нем.

7、ОтветЭнтити

ResponseEntity используется для типа возвращаемого значения метода контроллера , а возвращаемое значение метода контроллера является ответным сообщением для браузера.

9. Загрузка и скачивание файлов

1. Загрузка файла

  • Используйте ResponseEntity для реализации загрузки файла.

Создайте новый статический каталог в каталоге webapp, затем создайте новый каталог img и сохраните 1.jpg в каталоге img.

изображение-20210816181120777
@RequestMapping("/fileDownload")
public ResponseEntity<byte[]> fileDownload(HttpSession session) throws IOException {
    
    
    //获取ServletContext对象
    ServletContext servletContext = session.getServletContext();
    //获取服务器中文件的真实路径
    String realPath = servletContext.getRealPath("/static/img/1.jpg");
    //创建输入流
    InputStream is = new FileInputStream(realPath);
    //创建字节数组
    byte[] bytes = new byte[is.available()];
    //将流读到字节数组中
    is.read(bytes);
    //创建HttpHeaders对象设置响应头信息
    MultiValueMap<String, String> headers = new HttpHeaders();
    //设置要下载方式以及下载文件的名字
    headers.add("Content-Disposition", "attachment;filename=1.jpg");
    //设置响应状态码
    HttpStatus statusCode = HttpStatus.OK;
    //创建ResponseEntity对象
    ResponseEntity<byte[]> responseEntity = new ResponseEntity<byte[]>(bytes, headers, statusCode);
    //关闭输入流
    is.close();
    return responseEntity;
}
<a th:href="@{/fileDownload}">文件下载 1.jpg</a>

2. Загрузка файла

  • Загрузка файла требует, чтобы метод запроса формы был post , и добавьте атрибут enctype="multipart/form-data"

  • SpringMVC инкапсулирует загруженный файл в объект MultipartFile , через который можно получить информацию о файле.

шаг:

  1. Добавьте зависимости для загрузки файлов:

    <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.1</version>
    </dependency>
    
  2. Настройте анализатор файлов в SpringMVC для преобразования файла в объект MultipartFile.

    <!--必须通过文件解析器的解析才能将文件转换为MultipartFile对象-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
    
  3. Метод контроллера:

    @RequestMapping("/fileUpload")
    //注意:这里MultipartFile的对象名photo需要和html中表单设置的名字一致,才能实现将文件转化为MultipartFile对象
    public String fileUpload(MultipartFile photo, HttpSession session) throws IOException {
          
          
        //获取上传的文件的文件名
        String fileName = photo.getOriginalFilename();
        //处理文件重名问题
        String hzName = fileName.substring(fileName.lastIndexOf("."));
        fileName = UUID.randomUUID().toString() + hzName;
        //获取服务器中photo目录的路径
        ServletContext servletContext = session.getServletContext();
        String photoPath = servletContext.getRealPath("/img");
        File file = new File(photoPath);
        if(!file.exists()){
          
          
            file.mkdir();
        }
        String finalPath = photoPath + File.separator + fileName;
        //实现上传功能
        photo.transferTo(new File(finalPath));
        return "success";
    }
    
  4. html-страница: установите для отправки запроса, enctype — «multipart/form-data» и обратите внимание, что атрибут имени в теге <input> и имя объекта MultipartFile формального параметра метода контроллера должны быть согласованы .

    <form th:action="@{/fileUpload}" enctype="multipart/form-data" method="post">
        <input type="file" name="photo">
        <input type="submit">
    </form>
    

10. Перехватчик

1. Конфигурация перехватчика

  • Перехватчики SpringMVC используются для перехвата выполнения методов контроллера.

шаг:

  1. Реализуйте интерфейс HandlerInterceptor и перепишите три метода preHandle(), postHandle() и afterCompletion().

    public class TestInterceptor implements HandlerInterceptor {
          
          
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
          
          
            System.out.println("preHandle");
            return true;
        }
    
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
          
          
            System.out.println("postHandle");
        }
    
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
          
          
            System.out.println("afterCompletion");
        }
    }
    
  2. Настроить в конфигурационном файле SpringMVC

    Есть три способа записи:

    Первый:

    <mvc:interceptors>
    	<bean class="com.oymn.interceptor.TestInterceptor"></bean>    <!--设置拦截器-->
    </mvc:interceptors>
    

    Второй тип:

    <mvc:interceptors>
    	<ref bean="testInterceptor"></ref>      <!--设置拦截器-->
    </mvc:interceptors>
    

    Третий тип:

    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>                   <!--/**表示拦截所有请求-->
            <mvc:exclude-mapping path="/"/>            <!--表示不拦截/请求-->
            <ref bean="testInterceptor"></ref>         <!--设置拦截器-->
        </mvc:interceptor>
    </mvc:interceptors>
    

    Первый и второй предназначены для перехвата всех запросов, обрабатываемых DispatcherServlet .

    Третий — установить перехватчик через тег ref или bean , задать запрос на перехват через mvc:mapping и задать запрос на исключение через mvc:exclude-mapping, то есть тот запрос, который не нужно перехватывать .

2. Три абстрактных метода перехватчиков

Перехватчики в SpringMVC имеют три абстрактных метода:

  • preHandle : выполнить preHandle() перед выполнением метода контроллера.Его возвращаемое значение логического типа указывает, следует ли перехватывать или освобождать , возвращать true, чтобы указать освобождение, то есть вызвать метод контроллера; вернуть false, чтобы указать перехват, то есть не вызывать метод контроллера .
  • postHandle : выполнить postHandle() после выполнения метода контроллера .
  • afterCompletion : после обработки данных представления и модели выполните afterCompletion() после рендеринга представления .
изображение-20210816220758732

исходный код:

изображение-20210816221251251

Введите метод processDispatchResult:

изображение-20210816221610173

3. Порядок выполнения нескольких перехватчиков

(1) Если prehandle() каждого перехватчика возвращает true :

В настоящее время порядок выполнения нескольких перехватчиков связан с порядком конфигурации файла конфигурации SpringMVC.

preHandle() будет выполняться в порядке конфигурации, а postHandle() и afterComplation() будут выполняться в обратном порядке конфигурации .

(2) Если prehandle() перехватчика возвращает false :

Перехватчик, который возвращает false из preHandle() и preHandle() перехватчика до его выполнения, postHandle() не будет выполняться, а afterComplation() перехватчика перед перехватчиком, который возвращает false, будет выполнен, и он будет выполнен в обратном порядке.

Анализ исходного кода:

Введите метод applyPreHandle():

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
    
    //正向遍历,同时interceptorIndex记录着最后一个返回true的拦截器的下标
    for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
    
    
        
        //获取拦截器
        HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i); 
        
        //执行preHandle方法,然后判断是否返回false
        if (!interceptor.preHandle(request, response, this.handler)) {
    
    
            
            //当preHandle方法返回false时,会执行afterComplation()方法
            this.triggerAfterCompletion(request, response, (Exception)null);
            return false;
        }
        
    }

    return true;
}

Введите метод applyPostHandle():

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
    
    
    //反序遍历
    for(int i = this.interceptorList.size() - 1; i >= 0; --i) {
    
    
        HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
        //执行postHandle方法
        interceptor.postHandle(request, response, this.handler, mv);
    }

}

Введите метод triggerAfterCompletion:

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
    
    
    //反序遍历,并且是只遍历interceptorIndex,也就是只遍历返回false的拦截器之前的拦截器
    for(int i = this.interceptorIndex; i >= 0; --i) {
    
    
        HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);

        try {
    
    
            //执行afterCompletion方法
            interceptor.afterCompletion(request, response, this.handler, ex);
        } catch (Throwable var7) {
    
    
            logger.error("HandlerInterceptor.afterCompletion threw exception", var7);
        }
    }

}

Отсюда видно, что когда метод applyPreHandle возвращает false , то есть когда в списке перехватчиков есть перехватчик, у которого preHandle возвращает false, последующий метод applyPostHandle выполняться не будет .

изображение-20210817104722037

Одиннадцать, обработчик исключений

1. Обработчик исключений на основе конфигурации

SpringMVC предоставляет интерфейс для обработки исключений, возникающих во время выполнения методов контроллера : HandlerExceptionResolver .

Классы реализации HandlerExceptionResolver: DefaultHandlerExceptionResolver и SimpleMappingExceptionResolver

изображение-20210817105947919

Среди них SimpleMappingExceptionResolver — настраиваемый обработчик исключений, предоставляемый SpringMVC .

<!--注册异常处理器-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <!--key表示控制器方法执行过程中可能出现的异常:ArigthmeticException算术异常-->
            <!--值error是指出现异常后跳转到指定页面error.html,该视图同样被thymeleaf解析,补充前后缀-->
            <prop key="java.lang.ArithmeticException">error</prop>
        </props>
    </property>
    <!--为exceptionAttribute设置一个值,作为异常信息的属性名,将出现的异常信息在请求域中进行共享-->
    <property name="exceptionAttribute" value="exception"></property>
</bean>

Метод контроллера:

@RequestMapping("/testException")
public String testException(){
    
    
    int i = 1/0;    //出现异常
    return "success";
}

Страница error.html:

出现了错误<br/>
错误信息是:<p th:text="${exception}"></p>     <!-这里的exception就是配置文件中exceptionAttribute的value值--->

Эффект:

изображение-20210817114758956

2. Обработчик исключений на основе аннотаций

@ControllerAdvice      //@ControllerAdvice将当前类表示为处理异常的组件
public class ExceptionController {
    
    
	//@ExceptionHandler设置所处理的异常
    @ExceptionHandler(value = {
    
    ArithmeticException.class,NullPointerException.class})
    public String testException1(Exception ex, Model model){
    
       //通过形参Exception类型对象接收异常信息
        model.addAttribute("ex",ex);
        return "error";
    }
}

Страница error.html:

出现了错误<br/>
错误信息是:<p th:text="${ex}"></p>     <!--这里的exception就是配置文件中exceptionAttribute的value值-->

12. Конфигурация аннотаций SpringMVC

  • Используйте классы конфигурации и аннотации для замены функций файлов конфигурации web.xml и SpringMVC.

1. Создайте класс инициализации для замены web.xml

В средах Spring 3.0 и более поздних версиях контейнер будет искать класс, реализующий интерфейс javax.servlet.ServletContainerInitializer в пути к классам , и, если он будет найден, использовать его для настройки контейнера Servlet, то есть сервера Tomcat.

И Spring предоставляет реализацию этого интерфейса под названием SpringServletContainerInitializer , который, в свою очередь, будет искать классы, реализующие WebApplicationInitializer , и передавать им задачи настройки.

И Spring3.2 представил класс реализации WebApplicationInitializer, названный AbstractAnnotationConfigDispatcherServletInitializer , когда наш класс наследует этот класс реализации и развертывает его в контейнере Servlet3.0, контейнер автоматически находит его и использует для настройки контекста сервлета.

public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    

    //指定Spring的配置类
    protected Class<?>[] getRootConfigClasses() {
    
    
        return new Class[]{
    
    SpringConfig.class};
    }

    //指定SpringMVC的配置类
    protected Class<?>[] getServletConfigClasses() {
    
    
        return new Class[]{
    
    WebConfig.class};
    }

    //指定DispatcherServlet的映射规则,即url-pattern
    protected String[] getServletMappings() {
    
    
        return new String[]{
    
    "/"};
    }

    //添加过滤器
    @Override
    protected Filter[] getServletFilters() {
    
    
        //编码过滤器
        CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
        encodingFilter.setEncoding("UTF-8");
        encodingFilter.setForceResponseEncoding(true);

        //处理put、delete请求的过滤器
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        return new Filter[]{
    
    encodingFilter,hiddenHttpMethodFilter};
    }
}

2. Создайте класс конфигурации SpringConfig, чтобы заменить файл конфигурации Spring.

@Configuration
public class SpringConfig {
    
    
	//ssm整合之后,spring的配置信息写在此类中
}

3. Создайте класс конфигурации WebConfig для замены файла конфигурации SpringMVC.

//  1.组件扫描     2.视图解析器     3.mvc注解驱动      4.视图控制view-controller
//  5.文件上传解析器  6.defaultServletHandler  7.拦截器  8.异常处理器

@Configuration   //标识配置类
@ComponentScan("com.oymn")   //1.开启组件扫描
@EnableWebMvc    //3.开启mvc注解驱动
public class WebConfig implements WebMvcConfigurer {
    
         //实现WebMvcConfigurer接口

    //8.异常处理器
    @Bean
    public SimpleMappingExceptionResolver exceptionResolver(){
    
    
        //创建SimpleMappingExceptionResolver
        SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();

        Properties prop = new Properties();
        //key表示可能出现的算数异常,value表示出现异常后跳转的error.html页面
        prop.setProperty("java.lang.ArithmeticException","error");
        //设置异常映射
        exceptionResolver.setExceptionMappings(prop);
        //设置共享信息的键
        exceptionResolver.setExceptionAttribute("ex");

        return exceptionResolver;
    }

    //7.配置拦截器
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        //自定义的拦截器类FirstInterceptor
        FirstInterceptor firstInterceptor = new FirstInterceptor();
        //设置拦截规则:addPathPatterns的/**表示拦截所有请求,excludePathPatterns的/表示/请求不拦截
        registry.addInterceptor(firstInterceptor).addPathPatterns("/**").excludePathPatterns("/");
    }

    //6.默认Servlet处理静态资源
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    
    
        configurer.enable();
    }

    //5.配置文件上传解析器
    @Bean
    public CommonsMultipartResolver multipartResolver(){
    
    
        return new CommonsMultipartResolver();
    }

    //4.视图控制view-controller
    public void addViewControllers(ViewControllerRegistry registry) {
    
    
        registry.addViewController("/").setViewName("index");
    }

    //2.配置生成模板解析器
    @Bean
    public ITemplateResolver templateResolver() {
    
    
        WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
        // ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过WebApplicationContext 的方法获得
        ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(
                webApplicationContext.getServletContext());
        templateResolver.setPrefix("/WEB-INF/templates/");
        templateResolver.setSuffix(".html");
        templateResolver.setCharacterEncoding("UTF-8");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        return templateResolver;
    }

    //生成模板引擎并为模板引擎注入模板解析器
    @Bean
    public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
    
    
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
        return templateEngine;
    }

    //生成视图解析器并未解析器注入模板引擎
    @Bean
    public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
    
    
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setCharacterEncoding("UTF-8");
        viewResolver.setTemplateEngine(templateEngine);
        return viewResolver;
    }
}

Приведенный выше обработчик исключений, помимо возврата объекта SimpleMappingExceptionResolver через тег @Bean, также можно переписать

Интерфейс WebMvcConfigurer предоставляет метод configureHandlerExceptionResolvers , а переписанное содержимое в основном такое же, как и выше.

//8.异常处理器
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
    
    
    SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
    Properties prop = new Properties();
    prop.setProperty("java.lang.ArithmeticException", "error");
    //设置异常映射
    exceptionResolver.setExceptionMappings(prop);
    //设置共享异常信息的键
    exceptionResolver.setExceptionAttribute("ex");
    resolvers.add(exceptionResolver);
}

Тринадцать, процесс выполнения SpringMVC

1. Общие компоненты SpringMVC

  • DispatcherServlet: интерфейсный контроллер , не требует разработки инженерами, предоставляется фреймворком

Роль: Единая обработка запросов и ответов, центр управления всем процессом, вызывающий другие компоненты для обработки запросов пользователей.

  • HandlerMapping: процессорный маппер , не требует разработки инженерами, предоставляется фреймворком

Функция: Найдите обработчик в соответствии с запрошенным URL, методом и другой информацией, то есть методом контроллера.

  • Handler: Процессор , который должен быть разработан инженерами

Функция: Обработчик обрабатывает определенные пользовательские запросы под управлением DispatcherServlet.

  • HandlerAdapter: Адаптер процессора , не требующий инженерной разработки, предоставляется фреймворком.

Функция: выполнить процессор (метод контроллера) через HandlerAdapter

  • ViewResolver: преобразователь представлений , не требующий инженерной разработки, предоставляемый фреймворком.

Функция: выполнение анализа представлений для получения соответствующих представлений, например: ThymeleafView, InternalResourceView, RedirectView.

  • Просмотр: просмотр

Роль: отображать данные модели пользователю через страницу

2. Процесс инициализации DispatcherServlet

  • DispatcherServlet по сути является сервлетом

    изображение-20210818113300628

Выше приведена упрощенная схема процесса вызова инициализации DispatcherServlet:

Затем проанализируйте исходный код:

  1. Инициализируйте WebApplicationContext:
protected WebApplicationContext initWebApplicationContext() {
    
    
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
        WebApplicationContext wac = null;
    
		//webApplicationContext为null,if语句不会执行
        if (this.webApplicationContext != null) {
    
    
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
    
    
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
                if (!cwac.isActive()) {
    
    
                    if (cwac.getParent() == null) {
    
    
                        cwac.setParent(rootContext);
                    }

                    this.configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }

        if (wac == null) {
    
    
            wac = this.findWebApplicationContext();
        }

        if (wac == null) {
    
    
            //创建WebApplicationContext
            wac = this.createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
    
    
            synchronized(this.onRefreshMonitor) {
    
    
                //刷新WebApplicationContext
                this.onRefresh(wac);
            }
        }

        if (this.publishContext) {
    
    
            String attrName = this.getServletContextAttributeName();
            //将IOC容器在应用域中共享
            this.getServletContext().setAttribute(attrName, wac);
        }

        return wac;
    }
  1. Создайте контекст веб-приложения
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    
    
        Class<?> contextClass = this.getContextClass();
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
    
    
            throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
        } else {
    
    
            
            //通过反射创建IOC容器对象
            ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
            //设置环境
            wac.setEnvironment(this.getEnvironment());
            //设置父容器(也就是Spring)
            wac.setParent(parent);
            String configLocation = this.getContextConfigLocation();
            if (configLocation != null) {
    
    
                wac.setConfigLocation(configLocation);
            }

            this.configureAndRefreshWebApplicationContext(wac);
            return wac;
        }
    }
  1. Стратегия инициализации DispatcherServlet

    После того, как FrameworkServlet создает WebApplicationContext, он обновляет контейнер и вызывает onFresh(wac).Этот метод переписан в DispatcherServlet, а метод initStategies(context) вызывается для инициализации стратегии, то есть для инициализации каждого компонента DispatcherServlet.

    protected void initStrategies(ApplicationContext context) {
          
          
        this.initMultipartResolver(context);
        this.initLocaleResolver(context);
        this.initThemeResolver(context);
        this.initHandlerMappings(context);
        this.initHandlerAdapters(context);
        this.initHandlerExceptionResolvers(context);
        this.initRequestToViewNameTranslator(context);
        this.initViewResolvers(context);
        this.initFlashMapManager(context);
    }
    

3. DispatcherServlet вызывает компонент для обработки запроса

Служба HttpServlet中**(запрос HttpServletRequest, ответ HttpServletResponse)**方法:

Вызвать соответствующий метод doXXX по запросу

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
    String method = req.getMethod();
    long lastModified;
    if (method.equals("GET")) {
    
    
        lastModified = this.getLastModified(req);
        if (lastModified == -1L) {
    
    
            this.doGet(req, resp);
        } else {
    
    
            long ifModifiedSince = req.getDateHeader("If-Modified-Since");
            if (ifModifiedSince < lastModified) {
    
    
                this.maybeSetLastModified(resp, lastModified);
                this.doGet(req, resp);
            } else {
    
    
                resp.setStatus(304);
            }
        }
    } else if (method.equals("HEAD")) {
    
    
        lastModified = this.getLastModified(req);
        this.maybeSetLastModified(resp, lastModified);
        this.doHead(req, resp);
    } else if (method.equals("POST")) {
    
    
        this.doPost(req, resp);
    } else if (method.equals("PUT")) {
    
    
        this.doPut(req, resp);
    } else if (method.equals("DELETE")) {
    
    
        this.doDelete(req, resp);
    } else if (method.equals("OPTIONS")) {
    
    
        this.doOptions(req, resp);
    } else if (method.equals("TRACE")) {
    
    
        this.doTrace(req, resp);
    } else {
    
    
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[]{
    
    method};
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(501, errMsg);
    }

}

FrameworkServlet переписывает метод службы (запрос HttpServletRequest, ответ HttpServletResponse) и метод doXXX() в HttpServlet , и эти методы вызывают processRequest(запрос, ответ).

//重写HttpServlet的service方法
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
    if (httpMethod != HttpMethod.PATCH && httpMethod != null) {
    
    
        
        //调用父类的service方法,但从上面的源码可以知道,实质上是调用了对应的doxxx方法
        //而FrameworkServlet又重写了doXXX方法,如下,可以知道doXXX方法中又调用了processRequest方法,所以和下面else一样
        super.service(request, response);     
        
    } else {
    
    
        //调用processRequest方法
        this.processRequest(request, response);
    }

}

//重写HttpServlet的doGet方法
protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    this.processRequest(request, response);
}

//重写HttpServlet的doPost方法
protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    this.processRequest(request, response);
}

//重写HttpServlet的doPut方法
protected final void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    this.processRequest(request, response);
}

//重写HttpServlet的doDelete方法
protected final void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    this.processRequest(request, response);
}

Метод processRequest(запрос, ответ) выглядит следующим образом:

protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    
    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
    
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = this.buildLocaleContext(request);
    
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
    
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor());
    
    this.initContextHolders(request, localeContext, requestAttributes);

    try {
    
    
        //执行服务,doService是一个抽象方法,在DispatcherServlet中进行了重写
        this.doService(request, response);
    } catch (IOException | ServletException var16) {
    
    
        failureCause = var16;
        throw var16;
    } catch (Throwable var17) {
    
    
        failureCause = var17;
        throw new NestedServletException("Request processing failed", var17);
    } finally {
    
    
        this.resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
    
    
            requestAttributes.requestCompleted();
        }

        this.logResult(request, response, (Throwable)failureCause, asyncManager);
        this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);
    }

}

Метод doService в DispatcherServlet:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
    this.logRequest(request);
    
    Map<String, Object> attributesSnapshot = null;
    
    if (WebUtils.isIncludeRequest(request)) {
    
    
        attributesSnapshot = new HashMap();
        Enumeration attrNames = request.getAttributeNames();

        label120:
        while(true) {
    
    
            String attrName;
            do {
    
    
                if (!attrNames.hasMoreElements()) {
    
    
                    break label120;
                }

                attrName = (String)attrNames.nextElement();
            } while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));

            attributesSnapshot.put(attrName, request.getAttribute(attrName));
        }
    }

    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
    if (this.flashMapManager != null) {
    
    
        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
        if (inputFlashMap != null) {
    
    
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
        }

        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    }

    RequestPath requestPath = null;
    if (this.parseRequestPath && !ServletRequestPathUtils.hasParsedRequestPath(request)) {
    
    
        requestPath = ServletRequestPathUtils.parseAndCache(request);
    }

    try {
    
    
        //处理请求和响应
        this.doDispatch(request, response);
    } finally {
    
    
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
    
    
            this.restoreAttributesAfterInclude(request, attributesSnapshot);
        }

        if (requestPath != null) {
    
    
            ServletRequestPathUtils.clearParsedRequestPath(request);
        }

    }

}

doDispatch метод:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    

    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

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

            try {
    
    
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = processedRequest != request;

             //执行链,包含handler,interceptorList,interceporIndex
             //handler:浏览器发送的请求所匹配的控制器方法
             //interceptorList:处理控制器方法的所有拦截器集合
             //interceporIndex:拦截器索引,preHanler方法返回false的最后一个拦截器的下标,控制afterCompletion方法的执行
                mappedHandler = this.getHandler(processedRequest);

                if (mappedHandler == null) {
    
    
                    this.noHandlerFound(processedRequest, response);
                    return;
                }

                //处理器适配器,用于调用请求所对应的控制器方法
                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
    
    
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet){
    
    
                        return;
                    }
                }

                //调用拦截器的preHandle方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    
    
                    return;
                }

                //由处理器适配器调用具体的控制器方法,最终获得ModelAndView对象
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
    
    
                    return;
                }

                this.applyDefaultViewName(processedRequest, mv);

                //调用拦截器的postHandle方法
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception var20) {
    
    
                dispatchException = var20;
            } 
            catch (Throwable var21) {
    
    
                dispatchException = new NestedServletException("Handler dispatch failed", var21);
            }

			//后续处理:处理模型数据和渲染视图
            processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
        } 
        catch (Exception var22) {
    
    
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
        } 
        catch (Throwable var23) {
    
    
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
        }

    } finally {
    
    
        if (asyncManager.isConcurrentHandlingStarted()) {
    
    
            if (mappedHandler != null) {
    
    
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        } else if (multipartRequestParsed) {
    
    
            this.cleanupMultipart(processedRequest);
        }

    }
}

метод processDispatchResult:

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
    
    
    
    boolean errorView = false;
    if (exception != null) {
    
    
        if (exception instanceof ModelAndViewDefiningException) {
    
    
            this.logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException)exception).getModelAndView();
        } else {
    
    
            Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
            mv = this.processHandlerException(request, response, handler, exception);
            errorView = mv != null;
        }
    }

    if (mv != null && !mv.wasCleared()) {
    
    
        //处理模型数据和渲染视图
        this.render(mv, request, response);
        if (errorView) {
    
    
            WebUtils.clearErrorRequestAttributes(request);
        }
    } else if (this.logger.isTraceEnabled()) {
    
    
        this.logger.trace("No view rendering, null ModelAndView returned.");
    }

    if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
    
    
        if (mappedHandler != null) {
    
    
            //调用拦截器的afterCompletion方法
            mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
        }

    }
}

4. Процесс выполнения SpringMVC

  1. Пользователь отправляет запрос на сервер, и этот запрос перехватывается фронт-контроллером SpringMVC DispatcherServlet.

  2. DispatcherServlet анализирует URL-адрес запроса, получает идентификатор ресурса запроса (URI) и определяет сопоставление, соответствующее URI запроса:

а) не существует

I. Затем оцените, настроен ли mvc: default-servlet-handler.

ii. Если не настроено, консоль сообщит, что сопоставление не может быть найдено, и клиент отобразит ошибку 404.

[Не удалось передать изображение по внешней ссылке, исходный сайт может иметь механизм защиты от пиявки, рекомендуется сохранить изображение и загрузить его напрямую (img-SaH6iGR1-1672844074895) (D:\File\Study Materials\SpringMVC\Notes\img\img006.png)]

[Не удалось передать изображение по внешней ссылке, исходный сайт может иметь механизм защиты от кражи ссылок, рекомендуется сохранить изображение и загрузить его напрямую (img-63YAc6XD-1672844074896) (D:\File\Study Materials\SpringMVC\Notes\img\img007.png)]

3. Если есть конфигурация, получить доступ к целевому ресурсу (обычно статическому ресурсу, такому как: JS, CSS, HTML), и если клиент не может быть найден, будет отображаться ошибка 404.

[Не удалось передать изображение по внешней ссылке, исходный сайт может иметь механизм защиты от пиявки, рекомендуется сохранить изображение и загрузить его напрямую (img-P9ZPpkcL-1672844074897) (D:\File\Study Materials\SpringMVC\Notes\img\img008.png)]

[Не удалось передать изображение по внешней ссылке, исходный сайт может иметь механизм защиты от пиявки, рекомендуется сохранить изображение и загрузить его напрямую (img-8jFa567Q-1672844074898) (D:\File\Study Materials\SpringMVC\Notes\img\img009.png)]

б) Если он существует, выполните следующий процесс

  1. В соответствии с URI вызовите HandlerMapping, чтобы получить все связанные объекты, настроенные обработчиком (включая объект обработчика и перехватчик, соответствующий объекту обработчика), и, наконец, вернуть его в виде объекта цепочки выполнения HandlerExecutionChain.

  2. DispatcherServlet выбирает подходящий HandlerAdapter в соответствии с полученным обработчиком.

  3. Если HandlerAdapter успешно получен, в это время будет выполнен метод preHandler(...) перехватчика [Forward]

  4. Извлеките данные модели из запроса, заполните входные параметры обработчика, начните выполнение метода обработчика (контроллера) и обработайте запрос. В процессе заполнения входных параметров Handler, в соответствии с вашей конфигурацией, Spring поможет вам выполнить дополнительную работу:

а) HttpMessageConveter: преобразование сообщения запроса (например, Json, xml и т. д.) в объект и преобразование объекта в указанную информацию ответа

b) Преобразование данных: выполнить преобразование данных в сообщении запроса. Например, преобразование String в Integer, Double и т. д.

c) Форматирование данных: форматирование данных сообщения запроса. Например, преобразование строки в форматированное число или форматированную дату и т. д.

г) Проверка данных: проверьте правильность данных (длина, формат и т. д.) и сохраните результаты проверки в BindingResult или Error.

  1. После выполнения Handler он возвращает объект ModelAndView DispatcherServlet.

  2. В этот момент будет выполнен метод перехватчика postHandle(...) [reverse].

  3. В соответствии с возвращенным ModelAndView (в это время он будет судить, есть ли исключение: если есть исключение, выполнить HandlerExceptionResolver для обработки исключения) выберите подходящий ViewResolver для разрешения представления и визуализируйте представление в соответствии с моделью и представлением.

  4. После рендеринга представления выполните метод afterCompletion(…) перехватчика [reverse].

  5. Верните обработанный результат клиенту.

Supongo que te gusta

Origin blog.csdn.net/OYMNCHR/article/details/120076465
Recomendado
Clasificación