SpringMVC学习笔记

40 篇文章 3 订阅
11 篇文章 0 订阅

SpringMVC学习笔记

image-20220921151139280

1、SpringMVC概述

  • SpringMVC是什么

    SpringMVC是Spring的一个子项目,他是Spring为表现层开发而提供的一个框架,采用了MVC这种思想来进行设计的,能够很好地将模型和视图进行分离。

    Spring官网:Spring | Home

  • MVC是什么?

    MVC(Model View Controller)是一种软件设计的架构模式,它要求软件开发时,需要进行将【业务的处理】和【视图显示】 的实现代码进行分离,然后使用控制器对他们进行调度。


    推荐阅读:浅析三层架构和MVC模式的区别和联系

  • SpringMVC的特点

    • 继承Spring框架的特点:轻量、低侵入、灵活、组件化、声明式……
    • 角色划分清晰。采用MVC的思想,让代码角色划分十分清晰,每一个不同的角色都有一个专门的对象来实现,同时各细分领域需要解决的问题全访问覆盖,提供一套全面且实用的解决方案
    • 性能优异。SpringMVC底层源码使用了大量的设计模式,具有优秀的高并发能力,十分适合现代大型、超大型的互联网项目要求
    • 基于Servlet。SpringMVC的本质是对Servlet的封装,通过功能强大的前端控制器DispatcherServlet(相当于是一个Filter),对请求和响应进行统一处理,使用SpringMVC后就不需要手动创建Servlet

    ……

  • SpringMVC的优点

    • 提高项目开发效率。清晰的角色划分、灵活的model转换、大量重复代码的封装、.可定制的绑定和验证,同时拥有简洁而强大的JSP标签库,无一不彰显SpringMVC框架的优秀,能够极大地提高软件开发,是当前表现层最主流的框架
    • 容易上手灵活轻巧。清晰的角色划分,让使用者能很舒服地上手这框架,声明式的开发让开发变得更加方便;同时SpringMVC支持组件化开发,可以很方便地搭配其他框架和插件使用
    • 能够让Java程序性能更加优异

    ……

2、快速入门

任务:使用Thymeleaf对HTML页面进行渲染,使用SpringMVC对Servlet进行封装

创建Maven项目
导入依赖
创建SpringMVC配置文件
编写html
编写控制器
测试
编写web.xml
  • Step1:创建Maven项目

    目录结构:

    image-20220927160052371

  • Step2:导入依赖

    pom.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>day09_springmvc</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>war</packaging>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <dependencies>
            <!--servlet-api-->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>4.0.1</version>
                <!--只在编译和测试的时候用,运行servlet时不发生冲突,tomcat内置有servlet-api-->
                <scope>provided</scope>
            </dependency>
            <!--Spring和thymeleaf的整合包-->
            <dependency>
                <groupId>org.thymeleaf</groupId>
                <artifactId>thymeleaf-spring5</artifactId>
                <version>3.0.15.RELEASE</version>
            </dependency>
            <!--spring-mvc,间接引入了Spring-framework-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.3.22</version>
            </dependency>
            <!--logback,由于thymeleaf必须依赖slf4j日志门面,logback是slf4j日志门面的具体实现(和EHCache的使用一样)-->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.2.3</version>
            </dependency>
        </dependencies>
    </project>
    
  • Step3:编写web.xml

    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">
    
        <!--为SpringMVC的前端控制器起别名-->
        <servlet>
            <servlet-name>SpringMVC</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        </servlet>
    
        <!--配置DispatcherServlet的拦截路径(DispatcherServlet本质是一个Filter)-->
        <servlet-mapping>
            <!--
            为SpringMVC这个Servlet设置访问路径,要让所有静态资源的请求和响应能被SpringMVC进行统一处理
            备注:default是Tomcat中默认的Servlet,用于处理静态资源的
            -->
            <servlet-name>SpringMVC</servlet-name>
            <!-- 使用/会拦截除.jsp以外的所有请求(注意会覆盖Tomcat默认的Servlet访问路径) -->
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
    </web-app>
    
  • Step4:创建SpringMVC配置文件

    SpringMVC的配置文件不需要手动加载,当我们初始化DispatcherServlet时,SpringMVC的配置文件就会被自动加载,这也就说明:SpringMVC的配置文件的命名和位置必须固定

    • SpringMVC配置文件默认的位置:WEB-INF

      后面需要将SpringMVC的配置文件放到Resources目录下,设置如下:

      		//通过init-param标签进行设置,
      		<init-param>
                  <param-name>contextConfigLocation</param-name>
                  <param-value>classpath:springmvc.xml</param-value>
              </init-param>
      
    • SpringMVC配置文件默认的名称:DispatcherServlet的别名-serlvet.xml

    知识拓展:

    • 放到WEB-INF下的资源浏览器无法直接通过URL进行访问,只能通过请求转发的方式进行访问(这让WEB-INF目录下的资源更加安全)
    • 复制到项目中的文件,在运行时可能不会被找到,可以使用Maven的cleanpackge指令

    SpringMVC-servlet.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 https://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--扫描控制层组件-->
        <context:component-scan base-package="com.hhxy.controller"></context:component-scan>
    
        <!--配置Thymeleaf视图解析器
        注意:id是SpringMVC规定死的,该视图解析器由SpringMVC内部实现
        -->
        <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>
    
    </beans>
    
  • Step5:编写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>
    <!--
    使用Thymeleaf的链接表达式,会自动将/转义成:
    localhost:8080/ + day09_springmvc/(上下文路径,也称虚拟目录) + hello
    -->
    <a th:href="@{/hello}">使用thymeleaf的链接表达式</a><br/>
    <!--
    而原始的href属性,不会自动补全上下文路径:
    localhost:8080/ + hello
    -->
    <a href="/hello">使用原始的超链接</a>
    </body>
    </html>
    

    success.html:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>访问成功</title>
    </head>
    <body>
    <h1>success.html</h1>
    </body>
    </html>
    
  • Step6:编写控制器

    HelloController:

    package com.hhxy.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    /**
     * @author ghp
     * @date 2022/9/21
     */
    
    @Controller
    public class HelloController {
    
        /**
         * 访问index.html的方法
         * 描述:当浏览器访问项目时,该方法直接进行拦截,并返回一个逻辑视图,
         * 然后逻辑视图被Thymeleaf视图解析器进行解析生成访问路径
         * @return 返回一个逻辑视图
         */
        //将浏览器发送的请求映射到该方法(想当于这个方法就是一个Servlet),让Tomcat执行该方法
        @RequestMapping("/")
        public String entrance(){
            //将逻辑视图返回
            return "index";
        }
    
        /**
         * 访问hello.html的方法
         * @return
         */
        @RequestMapping("/hello")
        public String hello(){
            return "success";
        }
    }
    
  • Step7:测试

    image-20220927161033898

    image-20220927161048637

入门案例的执行流程

  • 请求拦截。浏览器发送请求,请求如果能被DispatcherServlet(前端控制器)拦截,则进行下一步;
  • 请求匹配。前端控制器加载SpringMVC的配置文件,通过扫描组件(注解实现Bean管理),将请求地址和控制器(使用@Controller表示的类)中的@RequestMapping中的value属性的值进行匹配
  • 视图解析@RequestMapping表示的方法会返回一个字符串,这个字符串是逻辑视图,它会被Thymeleaf视图解析器进行解析,然后将它变成一个物理视图(给物理视图加上前缀和后缀)
  • 运行响应。控制器根据这个物理视图,请求转发到对应的html页面,然后Thymeleaf对这个html页面进行渲染,呈现出最终在页面上展示的效果

DispatcherServlet继承FrameworkServlet,而Framework的继承关系如图所示:

image-20220927164931943

可以看到Framework继承了一大堆类,由于类加载的特性:当子类被加载时,会先初始化父类。这就导致初次使用SpringMVC进行访问资源时,会加载比较慢,这是比较影响用户体验的!由于这是Java的底层机制,我们是无法对其进行更改的,但机制的死的人是活的,我们可以通过将初始化操作提前到服务器启动时,这样其他用户进行访问时就会比较流畅了😄

具体可以再web.xml中进行设置:

    <!--配置DispatcherServlet-->
    <servlet>
        <!--为SpringMVC的前端控制器起别名-->
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--设置SpringMVC配置文件访问路径,默认是再WEB-INF下,现在是再resources下-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <!--将DispatcherServlet的初始化操作提前到服务器启动时-->
        <load-on-startup>1</load-on-startup>
    </servlet>

3、@RequestMapping注解详解

3.1 注解的功能和位置

  • @RequestMapping注解的功能

    用于标识控制器中的方法,然后将请求的URL和@RequestMapping的Value属性的值进行映射,这样当浏览器发送请求时,DispatcherServlet就会通过请求的URL,依据映射关系找到对应控制器中的方法,让该方法处理请求。

    PS:它的作用就相当于Servlet中的@WebServlet()注解

  • @RequestMapping注解的位置

    • 用于标识方法:设置映射请求请求路径的具体信息

    • 用于标识类:设置映射请求的请求路径的初始信息(相当于是添加了一个虚拟目录)

      使用@RequestMapping注解标识类,一般用于一个项目中多个控制器中存在同名的处理请求方法的情况,作用就是让区分不同控制器中的方法

    示例

    @Controller
    @RequestMapping("/test")
    public class TestRequestMappingController {
    
        @RequestMapping("/")
        public String entrance(){
            return "index";
        }
    
        @RequestMapping("/hello")
        public String hello(){
            return "success";
        }
    }
    

    如上所示,在之前快速入门的案例中我们没有使用@RequestMapping注解进行标识,按照上面这种写法能够直接访问到index.html,但是现在却不行了:

    image-20220927173959086

    需要在访问的URL上添加上标识在类上的@RequestMapping注解的vlaue的值

    image-20220927174328048

    image-20220927174247987

3.2 注解的属性

这里详细讲解valuemethodparamsheads四个属性

其它属性可以参考这篇文章:@RequestMapping属性详解 - SpringMVC高手进阶

  • value属性:用于请求映射

    • 属性介绍:它是一个String类型的数组,具体可以参考@RequestMapping注解的源码

    • 属性的使用:

          //当访问的URL是/hello或者是/sucess时,都会执行这个方法
      	@RequestMapping(value = {"/hello","/success"})
          public String hello(){
              return "success";
          }
      

      备注:当访问的资源没有匹配到映射关系时,会直接报404

  • method属性:设置处理请求的方法只能处理某种类型的请求方式

    • 属性介绍:是一个RequestMethod类型的数组,可以设置多种请求方式(默认是支持所有的HTTP请求)

      当请求地址能够匹配映射关系,但是处理请求的方法不支持这种请求方式,会报405

    • 属性的使用:

          @RequestMapping(value = "/hello",method = RequestMethod.POST)
          public String hello(){
              return "success";
          }
      //这时候使用了浏览器访问,直接报405: Request method 'GET' not supported
      //因为浏览器模式的请求方式是GET,这里设置的POST
      

    @RequestMapping拥有派生注解,它和method搭配起来,可以有:@GetMapping@PostMapping@PutMapping@DeleteMapping,它们相当于是自带了method属性,分别对应get、post、put、delete四种请求方式,用法和@RequestMapping是一样的,只是自带了method属性

  • params属性:指定请求中一定要含有或不含有指定参数,才会处理请求

    • 属性介绍:是一个字符串类型的数组,可以通过四种表达式设置请求参数 和请求映射的匹配关系

      • !param:要求请求映射所匹配的请求必须不能携带param请求参数
      • param=value:要求请求映射所匹配的请求必须携带param请求参数且param=value
      • param!=value:要求请求映射所匹配的请求必须携带param请求参数但是param!=value
    • 属性的使用:

          //请求参数一定要有username和password,但是password的值不能是123
      	@RequestMapping(value = "/hello",params = {"username","password!=123"})
          public String hello(){
              return "success";
          }
      

      注意:若当前请求满足@RequestMapping注解的value和method属性,但是不满足params属性,此时 页面回报错400

      image-20220927210243191

  • headers属性:指定请求中一定要包含指定的请求头,才会去处理请求

    • 属性介绍:是一个字符串类型的数组,可以通过四种表达式设置请求头信 息和请求映射的匹配关系

      • header:要求请求映射所匹配的请求必须携带header请求头信息
      • !header:要求请求映射所匹配的请求必须不能携带header请求头信息
      • header=value:要求请求映射所匹配的请求必须携带header请求头信息且header=value
      • header!=value:要求请求映射所匹配的请求必须携带header请求头信息且header!=value
    • 属性的使用:

          //表示直接收本机发送的请求
      	@RequestMapping(value = "/hello",headers="Referer=http://localhost:8080")
          public String hello(){
              return "success";
          }
      

3.3 路径相关设置和占位符的使用

  • SpringMVC支持Ant风格的路径方式,具体方式如下:

    • ?:表示任意的单个字符

    • *:表示任意的0个或多个字符

    • **:表示任意层数的任意目录

    注意事项

    • 在使用?时,必须要有一个字符,这个字符可以任意,但是对于一些特殊字符,如:?\/ 会访问失败

    • 在使用*时,同样对于上面哪些特殊符号也会访问失败

    • 在使用**时,只能使用xxx/**/xxx的方式,同时对于?会访问失败,对于斜线访问有效

  • SpringMVC支持在路径中使用占位符{}

    • 不使用占位符,传递参数:/test?id=1
    • 使用占位符,传递参数:test/1

    测试1:一个参数的传递

    image-20220927214310684

    测试2:多个参数的传递

    index.html:

    <a th:href="@{/test/张三/123}">测试在value属性中使用占位符</a>
    

    Controller:

        @RequestMapping("test/{username}/{password}")
        public String testAnt(@PathVariable("password") String password,@PathVariable("username") String username){
            System.out.println("username = " + username);
            System.out.println("password = " + password);
            return "success";
        }
    

    注意:参数的获取一定要依据URL中参数的顺序,同时要考虑参数的类型

    image-20220927215029662

    image-20220927215024214

    乱码的解决方案:

    待定
    

4、SpringMVC获取请求参数

4.1 获取请求参数的方式

这里会介绍获取请求参数的6种方式,如下所示

  • 通过@PathVariable获取:只能搭配占位符使用
  • 通过Servlet API获取:无法搭配占位符使用,可以搭配?&或表单来使用,还能获取Header、Cookie相关参数的值
  • 通过方法的形参来获取:无法搭配占位符使用,可以搭配?&或表单来使用
  • 通过@RequestHeader注解获取:只能获取Header相关的参数的值
  • 通过@CookieValue注解获取:只能获取Cookie相关的参数的值
  • 通过POJO获取:
  • 通过@PathVariable获取,这个只能搭配占位符使用

  • 通过Servlet API获取:

        @RequestMapping("/testServletAPI")
        public String testServletAPI(HttpServletRequest request){
            String username = request.getParameter("username");
            String password = request.getParameter("password");
            System.out.println("username = " + username);
            System.out.println("password = " + password);
            return "success";
        }
    
  • 通过方法的形参来获取:

        @RequestMapping("/testParam")
        public String testParam(String username,String password){
            System.out.println("username = " + username);
            System.out.println("password = " + password);
            return "success";
        }
    

    注意事项形参要求和请求参数的名字一致,如果一定想要使用不同的形参来获取,可以使用@RequestParam注解

        @RequestMapping("/testParam")
        public String testParam(
            @RequestParam(value = "username",required = false) String name, String password){
            System.out.println("username = " + name);
            System.out.println("password = " + password);
            return "success";
        }
    

    备注:required属性用于设置浏览器必须传递该请求参数,没有则报错,默认是true,即:浏览器必须传递该参数

  • 通过@RequestHeader注解获取:

        @RequestMapping("/testRequestHeader")
        public String testRequestHeader(@RequestHeader("referer") String referer){
            System.out.println("referer = " + referer);
            //输出:referer = http://localhost:8080/day10_springmvc/form
            return "success";
        }
    

    备注:它是用来获取请求头参数的值

  • 通过@CookieValue注解获取:

        @RequestMapping("/testCookieValue")
        public String testCookieValue(@CookieValue("JSESSIONID") String jsessionId){
            System.out.println("JSESSIONID = " + jsessionId);
            return "success";
        }
    

    注意事项:它是用来获取Cookie相关的参数的值,执行上面的代码前提是得要先有一个SessonId

  • 通过POJO获取请求参数:

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

    注意事项:实体类的属性名要和请求参数名称一致

4.2 解决请求参数中文乱码

只需要在web.xml中加入以下代码,即可:

    <!--配置一个Spring编码过滤器-->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <!--框架拥有自定义的编码(ISO-8859-1),我们需要修改默认的编码-->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <!--上面的编码默认只用于请求,我们还需要修改forceEncoding,让他能它用于响应-->
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <!--设置Spring编码过滤器的拦截路径-->
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

注意事项SpringMVC的编码过滤器一定要在其他过滤器之前,否则无效

5、向域对象共享数据

在JavaWeb种我们学过了四大域对象,其中PageContext是JSP中用到的,由于JSP已经被淘汰了,所以这个域对象已经被淘汰了。所以在向域对象中共享数据这里介绍7种方法,分别是:

  • 向Request域对象中共享数据共4种方式:

    • 使用Servlet API向Request域对象共享数据

    • 使用ModelAndView向Request域对象共享数据

    • 使用Model向Request域对象共享数据

    • 使用ModelMap向request域对象共享数据

  • 向Session域对象中共享数据:使用Servlet API

  • 向Application域对象中共享数据:使用Servlet API

  • 使用Servlet API向Request域对象共享数据:

    @RequestMapping("/testServletAPI")
    public String testServletAPI(HttpServletRequest request){
    	request.setAttribute("testScope", "hello,servletAPI");
    	return "success";
    }
    
  • 使用ModelAndView向Request域对象共享数据:

    这是Spring官方推荐的方法,在SpringMVC底层其他所有在域对象中共享的数据都会封装成一个ModelAndView对象

        @RequestMapping("/test/mav")
        public ModelAndView testMAV(){
            /**
             * ModeAndView包含Model和View的功能
             * Model: 向请求域中共享数据
             * View: 设置逻辑视图,实现页面跳转
             */
            //1、获取ModelAndView对象
            ModelAndView mav = new ModelAndView();
            //2、往域对象中共享数据
            mav.addObject("information","hello,ModelAndView!");
            //3、设置逻辑视图,实现网页跳转
            mav.setViewName("success");
            //4、返回ModelAndView对象
            return mav;
        }
    

    index.html:

    <!--测试域对象-->
    <a th:href="@{/test/mav}">测试通过ModelAndView向请求域中共享数据</a>
    

    success.html:

    <h1>跳转成功</h1>
    <!--获取域对象中的数据-->
    <p th:text="${information}"></p>
    </body>
    

    测试结果:

    image-20220930222407748

  • 使用Model向Request域对象共享数据:

        @RequestMapping("/test/model")
        public String testModel(Model model){
            model.addAttribute("information","hello,Model!");
            return "success";
        }
    

    image-20220930222955949

  • 使用ModelMap向Request域对象共享数据:

        @RequestMapping("/test/madelMap")
        public String testModel(ModelMap modelMap){
            modelMap.addAttribute("information","hello,ModelMap!");
            return "success";
        }
    
  • 使用Map向Request域对象共享数据:

        @RequestMapping("/test/map")
        public String testMap(Map<String, Object> map){
            map.put("information", "hello,Map!");
            return "success";
        }
    

知识拓展:

本质上,Model、ModelMap、Map类型的参数其实本质上都是 BindingAwareModelMap 类型的

可以通过对象.getClass().getName()输出测试

  • 向Session域共享数据:

    @RequestMapping("/test/session")
    public String testSession(HttpSession session){
    	session.setAttribute("testSessionScope", "hello,session!");
    	return "success";
    }
    
  • 向Application域共享数据:

    @RequestMapping("/test/application")
    public String testApplication(HttpSession session){
    	ServletContext application = session.getServletContext();
    	application.setAttribute("testApplicationScope", "hello,application!");
    	return "success";
    }
    

6、SpringMVC视图

SpringMVC中的视图是View接口,视图的作用渲染数据,也就是将模型Model中的数据展示在网页上,让用户能够看到。

而在SpringMVC中视图的种类有很多,默认的有转发视图和重定向视图;当引入JSTL依赖后,视图会自动转换成JSTL视图;

当引入Thymeleaf依赖后,并且配置好Thymeleaf视图解析器后,又会得到Thymeleaf视图。

  • 转发视图

    SpringMVC中默认的转发视图是InternalResourceView, SpringMVC中创建转发视图的情况: 当控制器方法中所设置的视图名称以forward:为前缀时,创建InternalResourceView视图,此时的视 图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"forward:"去掉,剩余部 分作为最终路径通过转发的方式实现跳转

        @RequestMapping("/test/view/forward")
        public String testForward() {
            //请求转发到TestScopeController中的test/model方法,让它处理请求,URL不会变
            return "forward:/test/model";
        }
    
  • 重定向视图

    SpringMVC中默认的重定向视图是RedirectView,当控制器方法中所设置的视图名称以redirect:为前缀时,创建RedirectView视图,此时的视图名称不 会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"redirect:"去掉,剩余部分作为最 终路径通过重定向的方式实现跳转

        @RequestMapping("/test/view/redirect")
        public String testRedirect(){
            //重定向到TestScopeController中的test/model方法,让它处理请求,URL会变
            return "redirect:/test/model";
        }
    

    注意:在SpringMVC中使用重定向,redirect:/会自动解析成上下文路径(虚拟目录),不需要像JavaWeb中一样去手动添加

  • Thymeleaf视图

    当控制器方法中所设置的视图名称没有任何前缀时,此时的视图名称会被SpringMVC配置文件中所配置 的视图解析器解析,视图名称拼接视图前缀和视图 后缀所得到的最终路径,会通过转发的方式实现跳转

        @RequestMapping("/test/view/thymeleaf")
        public String testThymeleafView(){
            return "success";
        }
    

温馨提示:一般都极少使用SpringMVC默认的视图,都是直接使用Thymeleaf视图,Thymeleaf视图既能转发也能重定向

  • 视图控制器

        <!--配置视图控制器,配置后就可以不用未index页面写一个访问方法了(其实那个方法就是一个视图控制器)
        path: 用于设置访问路径
        view-name: 访问路径对应的逻辑视图
        -->
        <mvc:view-controller path="/" view-name="index"></mvc:view-controller>
    

    备注:上面配置的那个控制器和3.1中的那个entrance方法是一致的

    注意事项:如果在SpringMVC配置文件中设置了视图控制器,只有配置文件中的的视图能被DispatcherServlet处理,需要开启MVC的注解驱动才能让Controller中的视图被DispatcherServlet处理

    <!--开启SpringMVC的注解驱动-->
    <mvc:annotation-driven/>
    

    不开启SpringMVC注解驱动,如下所示DispatcherServlet不会处理那些使用@RquestMapping设置的方法,也就会导致浏览器无法访问方法对应的页面:

    image-20221001134359296

7、RESTful

7.1 RESTful概述

  • 什么是RESTful?

    RESTful是指满足REST的应用程序或设计。本质是一种网络应用程序的设计风格和开发方式,它是基于HTTP,可以使用XML格式定义或JSON格式定义。

    个人见解:RESTful就像是一种编程风格,类似于OOP,OOP是面对对象编程,在OOP中一切皆对象,而在RESTful中,RESTful是面向资源编程,在RESTful中一切皆资源

  • 什么是REST

    REST(Representational State Transfer,表现形式状态转换)是一组架构约束条件和原则。

  • REST的原则

    • 为每一个资源设置一个URI,每一个URI代表1种资源
    • 客户端使用GET、POST、PUT、DELETE4个表示操作方式的动词对服务端资源进行操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源
    • 通过操作资源的表现形式(URI)来操作资源
    • 资源的表现形式是XML或者HTML
    • 通过XML或JSON来传递数据
    • 客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息
  • REST的约束

    • 客户端-服务端约束。以客户端-服务器形式建立一个基本的分布式架构,从而支持客户端逻辑与服务端逻辑独立演化。客户端处理js交互,CSS, HTML渲染等,服务器处理业务逻辑,数据存储。客户端可以升级某个版本,而不影响服务器的功能。
    • 无状态约束。任意一个Web请求 端提出请求时,请求本身包含了响应端为响应这一请求所需的全部信息(请求的信息都保留在客户端)
    • 缓存约束。缓存约束建立在 客户端-服务器 和 无状态 之上,对请求的响应中的数据隐式或显式标记为可缓存或不可缓存。如果响应是可缓存的,则客户端缓存有权重用该响应数据以用于以后的等效请求
    • 统一接口约束。所有的客户端和服务器必须共用一套接口约束
    • 分层系统约束。分层约束建立在 客户端-服务器约束 之上,可以向架构中添加中间件,每一层都可以独立的部署和进化。常见的中间件有,负载均衡设备(F5, Nginx, LVS),缓存组件(Redis, memcache),消息队列(kafka, rabbitmq)
    • 按需代码约束。按需代码属于可选约束,允许通过以小程序或脚本的形式下载和执行代码来扩展客户端功能
  • RESTful中相关基础概念

    • 资源:资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成。每个资源是服务器上一个可命名的抽象概念。因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、 数据库中的一张表等等具体的东西,可以将资源设计的要多抽象有多抽象,只要想象力允许而且客户端应用开发者能够理解。一个资源可以由一个或多个URI来标识。URI既是资源的名称,也是资源在Web上的地址。对某个资源感兴 趣的客户端应用,可以通过资源的URI与其进行交互
    • 资源的表述:资源的表述是一段对于资源在某个特定时刻的状态的描述。可以在客户端-服务器端之间转移(交换)。资源的表述可以有多种格式,例如HTML/XML/JSON/纯文本/图片/视频/音频等等。资源的表述格式可以通过协商机制来确定。请求-响应方向的表述通常使用不同的格式
    • 状态的转移:在客户端和服务器端之间转移代表资源状态的表述,通过转移和操作资源的表述,来间接实现操作资源的目的

7.2 RESTful的实现

满足REST的原则和约束的的设计方式或开发方式称之为RESTful(REST风格),从这里我们可以知道要实现RESTful首先要满足REST的原则和约束。REST 风格提倡 URL 地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方 式携带请求参数,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性

image-20221001145503289

7.2.1 使用RESTful风格实现CRUD(一)

任务:使用RESTful风格完成CRUD基本操作

备注:并没有进行真正的CRUD,只是对CRUD方法进行了访问

要点

  1. put、delete请求方式的表示
  2. 编码过滤器和请求方式过滤器的顺序
  3. 注解驱动的开启
创建Maven工程
导入依赖
编写web.xml
编写SpringMVC配置文件
编写HTML
编写Java代码
测试
  • Step1:创建Maven工程

    目录结构:

    image-20221001214619563

  • Step2:导入依赖

    pom.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>day11_springmvc</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>war</packaging>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <dependencies>
            <!--Spring整合Thymeleaf的依赖-->
            <dependency>
                <groupId>org.thymeleaf</groupId>
                <artifactId>thymeleaf-spring5</artifactId>
                <version>3.0.15.RELEASE</version>
            </dependency>
            <!--SpringMVC依赖,间接引入了spring-framework上下文-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.3.22</version>
            </dependency>
            <!--引入logback-classic,slf4j日志门面的实现包,thymeleaf需要依赖slf4j日志门面-->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.2.3</version>
            </dependency>
        </dependencies>
    
    </project>
    
  • Step3:编写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">
        <!--配置SpringMVC编码过滤器-->
        <filter>
            <!--给CharacterEncodingFilter起别名-->
            <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>
        <!--设置CharacterEncodingFilter的拦截路径-->
        <filter-mapping>
            <filter-name>CharacterEncodingFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
        <!--设置处理请求方式的过滤器,用于获取put或delete请求-->
        <filter>
            <!--给过滤器起别名-->
            <filter-name>HiddenHttpMethodFilter</filter-name>
            <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
        </filter>
        <!--设置HiddenHttpMethodFilter的拦截路径-->
        <filter-mapping>
            <filter-name>HiddenHttpMethodFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
        <!--SpringMVC的前端控制器-->
        <servlet>
            <!--给DispatcherServlet起别名-->
            <servlet-name>DispatcherServlet</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <!--设置SpringMVC配置文件的位置(默认是在WEB-INF目录下的)-->
            <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>
    
    </web-app>
    
  • Step4:编写SpringMVC配置文件

    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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <!--扫描组件,让IOC能够管理组件-->
        <context:component-scan base-package="com.hhxy.controller"></context:component-scan>
    
        <!--配置thymeleaf视图解析器-->
        <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
            <!--设置优先级,让视图能够优先被thymeleaf视图解析器解析(默认优先级是Integer的最大值)-->
            <property name="order" value="1"/>
            <!--设置视图解析器的编码格式-->
            <property name="characterEncoding" value="UTF-8"/>
            <!--设置thymeleaf模板引擎-->
            <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>
    
        <!--开启SpringMVC的注解驱动,当在SpringMVC配置文件中配置了视图解析器
            如果不开启注解驱动,则只有SpringMVC配置文件中的视图解析器能被DispatcherServlet进行处理
       -->
        <mvc:annotation-driven/>
    
        <!--为index.html配置一个视图控制器-->
        <mvc:view-controller path="/" view-name="index"></mvc:view-controller>
    
    </beans>
    
  • Step5:编写HTML

    index.html:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>首页</title>
    </head>
    <body>
    <h1>index.html</h1>
    <!--使用RESTful风格实现增、删、改、查方法-->
    <a th:href="@{/user}">查询所有的用户信息</a><br/>
    <a th:href="@{/user/1}">查询id为1的用户信息</a><br/>
    <form th:action="@{/user}" method="post">
        <input type="submit" value="测试添加用户信息">
    </form>
    <form th:action="@{/user}" method="post">
        <!--设置put或delete请求,前提是method必须是post-->
        <input type="hidden" name="_method" value="put">
        <input type="submit" value="测试修改用户信息">
    </form>
    <form th:action="@{/user/1}" method="post">
        <!--设置put或delete请求,前提是method必须是post-->
        <input type="hidden" name="_method" value="delete">
        <input type="submit" value="测试根据id删除用户信息">
    </form>
    </body>
    </html>
    

    success.html:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>成功跳转页面</title>
    </head>
    <body>
    <h1>success.html</h1>
    </body>
    </html>
    
  • Step6:编写Java代码

    package com.hhxy.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.*;
    
    /**
     * @author ghp
     * @date 2022/10/1
     * @title 实现REST
     * @description 测试使用RESTful风格实现的增、删、改、查方法
     */
    @Controller
    public class TestRestController {
    
    //    @RequestMapping(value = "/user",method = RequestMethod.GET)
        @GetMapping("/user")
        public String selectAll(){
            System.out.println("查询所有用户信息-->/user--->get");
            return "success";
        }
    
    //    @RequestMapping(value = "/user/{id}",method = RequestMethod.GET)
        @GetMapping("user/{id}")
        public String selectById(@PathVariable("id")int id){
            System.out.println("根据id查询用户信息-->/user/"+id+"-->get");
            return "success";
        }
    
    //    @RequestMapping(value = "/user",method = RequestMethod.POST)
        @PostMapping("/user")
        public String add(){
            System.out.println("添加用户信息-->/user-->post");
            return "success";
        }
    
    //    @RequestMapping(value = "/user",method = RequestMethod.PUT)
        @PutMapping("/user")
        public String update(){
            System.out.println("修改用户信息-->/user-->put");
            return "success";
        }
    
    //    @RequestMapping(value = "/user/{id}",method = RequestMethod.DELETE)
        @DeleteMapping("/user/{id}")
        public String deleteById(@PathVariable("id") int id){
            System.out.println("修改用户信息-->/user/"+id+"-->delete");
            return "success";
        }
    }
    
  • Step7:测试

    image-20221001215459769

    image-20221001215526635

    image-20221001215551105

7.2.2 使用RESTful风格实现CRUD(二)

任务:使用RESTful风格实现CRUD,并动态显示数据

备注:并没有连接数据库,而是通过一个EmployeeDao类来模拟存储员工信息的数据库(效果都是一样的)

要点

  1. DispatecherServlet和DefaultServlet的冲突
  2. 修改数据时,需要进行数据回显
  3. 不同操作对应不同的请求方式
创建Maven工程
导入依赖
编写web.xml
编写SpringMVC配置文件
编写HTML
编写Java
测试
  • Step1:创建Maven工程

    目录结构见7.2.1 Step1,略……

  • Step2:导入依赖

    pom.xml见7.2.1 Step2,略……

  • Step3:编写web.xml

    7.2.1 Step3,略……

  • Step4:编写SpringMVC配置文件

    7.2.1 的SpringMVC配置文件中添加如下内容:

        <!--扫描dao层的组件,让IOC能够管理-->
    	<context:component-scan base-package="com.hhxy.dao"></context:component-scan>
        <!--让DefaultServlet处理静态资源,解决DispatecherServlet和DefaultServelt产生的冲突
        这样写的效果是:当DispatecherServlet无法处理请求时,会让DefaultServlet来处理-->
        <mvc:default-servlet-handler/>
    

    知识拓展

      DefalutServletDispatecherServlet冲突产生的原因:DefaultServlet是Tomcat默认用来处理静态资源1的,可以在Tomcat的web.xml中进行查看,它的url-patter/;而我们在配置DispatecherServlet的url-pattern时也是设置的/,这就导致静态资源由DispatecherServlet来处理,而DispatecherServlet它没有处理静态资源的能力,就会导致404,从而是静态资源无法被加载到页面上

      Tomcat的web.xml和我们项目中的web.xml是一个”继承关系“,当我们在Tomcat中设置了一个Servlet的访问路径为/时,会覆盖掉Tomcat中Servlet的访问路径,让DefaultServlet失效,从而导致静态资源由DisptecherServlet来处理

  • Step5:编写HTML页面

    1)static,略……

    2)index.html,略……

    3)employee_list.html:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>employee_list</title>
          <!--引入CSS样式-->
      <link rel="stylesheet" th:href="@{/static/css/index_work.css}">
    </head>
    <body>
    <table>
      <tr>
        <th colspan="5">Employee List</th>
      </tr>
      <tr>
        <th>id</th>
        <th>lastName</th>
        <th>email</th>
        <th>gender</th>
        <th>options</th>
      </tr>
      <tr th:each="employee : ${allEmployee}">
        <td th:text="${employee.id}"></td>
        <td th:text="${employee.lastName}"></td>
        <td th:text="${employee.email}"></td>
        <td th:text="${employee.gender}"></td>
        <td>
          <a href="">delete</a>
          <a href="">update
          </a>
        </td>
      </tr>
    </table>
    </body>
    </html>
    
  • Step6:编写Java

    1)编写EmployeeDao

    package com.hhxy.dao;
    
    import com.hhxy.pojo.Employee;
    import org.springframework.stereotype.Repository;
    
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @author ghp
     * @date 2022/10/1
     * @title 员工数据类
     * @description 模拟存储员工数据的员工表
     */
    
    @Repository
    public class EmployeeDao {
    
        private static Map<Integer, Employee> employees = null;
    
        static{
            employees = new HashMap<Integer, Employee>();
            employees.put(1001, new Employee(1001, "E-AA", "aa@163.com", 1));
            employees.put(1002, new Employee(1002, "E-BB", "bb@163.com", 1));
            employees.put(1003, new Employee(1003, "E-CC", "cc@163.com", 0));
            employees.put(1004, new Employee(1004, "E-DD", "dd@163.com", 0));
            employees.put(1005, new Employee(1005, "E-EE", "ee@163.com", 1));
        }
    
        private static Integer initId = 1006;
    
        /**
         * 添加和修改功能
         * @param employee 待添加的员工数据
         */
        public void save(Employee employee){
            if(employee.getId() == null){
                //当员工的id为null时,就是添加功能
                employee.setId(initId++);
            }
            //将员工数据添加到Map集合中(如果员工的id不为null,就会直接覆盖已有id的员工信息)
            employees.put(employee.getId(), employee);
        }
    
        /**
         * 获取所有员工的信息
         * @return
         */
        public Collection<Employee> getAll(){
            return employees.values();
        }
    
        /**
         * 根据id获取员工信息
         * @param id
         * @return
         */
        public Employee get(Integer id){
            return employees.get(id);
        }
    
        /**
         * 根据id删除员工信息
         * @param id
         */
        public void delete(Integer id){
            employees.remove(id);
        }
    
    }
    

    2)Employee:

    /**
     * @author ghp
     * @date 2022/10/1
     * @title
     * @description
     */
    public class Employee {
        private Integer id;
        private String lastName;
        private String email;
        //1 male, 0 female
        private Integer gender;
        
        //构造、get、set、toString省略
    }
    

    3)EmployeeController

    package com.hhxy.controller;
    
    import com.hhxy.dao.EmployeeDao;
    import com.hhxy.pojo.Employee;
    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;
    
    /**
     * @author ghp
     * @date 2022/10/1
     * @title 员工控制器
     * @description 用于处理前端发送的对员工数据进行操作的请求
     */
    
    @Controller
    public class EmployController {
    
        //自动装配一个EmployeeDao对象,方便调用方法实现增、删、改、查的功能
        @Autowired
        private EmployeeDao employeeDao;
    
        /**
         * 展示所有员工的信息
         *
         * @param model
         * @return
         */
        @GetMapping("/employee")
        public String selectAll(Model model) {
            //从数据库中获取所有的员工信息
            Collection<Employee> allEmployee = employeeDao.getAll();
            //将数据存储到域对象中
            model.addAttribute("allEmployee", allEmployee);
            //返回逻辑视图
            return "employee_list";
        }
    
        /**
         * 添加数据
         *
         * @param employee
         * @return
         */
        @PostMapping("/employee")
        public String add(Employee employee) {
            //将employee_add.html页面传过来的数据添加到”数据库“中
            employeeDao.save(employee);
            //重定向到selectAll,同时刷新页面数据(这里重定向是用浏览器进行重新访问的,默认是get方式,所以不用去担心不会重定向到selectAll)
            return "redirect:/employee";
        }
    
        /**
         * 回显数据(点击update跳转到employee_update页面,并展示之前的数据)
         */
        @GetMapping("/employee/{id}")
        public String toUpdate(@PathVariable("id") Integer id, Model model) {
            //根据id查询员工信息
            Employee employee = employeeDao.get(id);
            //将查询出来的员工数据存储到域对象中
            model.addAttribute("employee", employee);
            //跳转到employ_update.html
            return "employee_update";
        }
    
        /**
         * 将修改的输出保存到数据库中
         */
        @PutMapping("/employee")
        public String updateEmployee(Employee employee) {
            //修改员工信息(本质是覆盖)
            employeeDao.save(employee);
            //重定向到employee_list.html
            return "redirect:/employee";
        }
    
        /**
         * 删除数据的方法
         */
        @DeleteMapping("/employee/{id}")
        public String deleteById(@PathVariable("id") Integer id) {
            employeeDao.delete(id);
            return "redirect:/employee";
        }
    
    }
    
  • Step7:测试

    这里只展示查询功能,具体完整代码可以参观我的Gitee或Github仓库

    image-20221001223717921

    image-20221001223723387

8、SpringMVC处理AJAX请求

8.1 使用@RequestBody处理AJAX请求

示例

创建Mavne工程
导入依赖
编写web.xml
编写SpringMVC配置文件
编写HTML
编写Java代码
测试
  • Step1:创建Maven工程

    目录结构:

    image-20221004162921689

  • Step2:导入依赖

    pom.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>day12_springmvc</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>war</packaging>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <dependencies>
            <!--servlet-api-->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>4.0.1</version>
                <scope>provided</scope>
            </dependency>
            <!--spring和thymeleaf整合包-->
            <dependency>
                <groupId>org.thymeleaf</groupId>
                <artifactId>thymeleaf-spring5</artifactId>
                <version>3.0.15.RELEASE</version>
            </dependency>
            <!--springMVC依赖,间接导入了spring-framework上下文-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.3.22</version>
            </dependency>
            <!--引入logback-classic,slf4j日志门面的实现包,thymeleaf需要依赖slf4j日志门面-->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.2.3</version>
            </dependency>
        </dependencies>
    
    </project>
    
  • Step3:编写web.xml

    配置一个编码过滤器、一个获取请求参数的过滤器、一个前端控制器。略……

  • Step4:编写SpringMVC配置文件

    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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <!--扫描组件-->
        <context:component-scan base-package="com.hhxy"></context:component-scan>
    
        <!--配置thymeleaf视图解析器-->
        <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
            <!--设置优先级,让视图能够优先被thymeleaf视图解析器解析(默认优先级是Integer的最大值)-->
            <property name="order" value="1"/>
            <!--设置视图解析器的编码格式-->
            <property name="characterEncoding" value="UTF-8"/>
            <!--设置thymeleaf模板引擎-->
            <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>
    
        <!--让DefaultServlet处理静态资源-->
        <mvc:default-servlet-handler/>
    
        <!--开启注解驱动-->
        <mvc:annotation-driven/>
    
        <!--为index.html配置一个视图控制器-->
        <mvc:view-controller path="/" view-name="index"></mvc:view-controller>
    
    </beans>
    
  • Step5:编写HTML

    1)index.html:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>首页</title>
    </head>
    <body>
    <div id="app">
        <h1>index.html</h1>
        <button @click="testAjax">测试SpringMVC处理ajax</button><br/>
        <button @click="testRequestBody">测试@RequestBody注解处理Json格式的请求参数</button>
    </div>
    </body>
    <!--引入vue-->
    <script th:src="@{/js/vue.js}"></script>
    <!--引入axios-->
    <script th:src="@{/js/axios.min.js}"></script>
    <script>
        new Vue({
            el:"#app",
            methods:{
                testAjax(){
                    /*
                    axios({
                       url:"",
                       method:"",
                       //以?和&的形式发送的数据,存储在请求行中,请求参数可以通过request.getParameter()
                       params:{},
                       //以json字符串的格式发送数据,存储在请求体中(所以请求方式必须是post、put、patch),请求参数需要序列化、反序列化获取(使用fastjson)
                       data:{}
                    }).then(resp => {
                        console.log(resp.data);
                    });
                    */
                    axios.post(
                        "/day12_springmvc//test/ajax?id=1001",
                        {username:"admin",password:"123"}
                    ).then(resp => {
                        console.log(resp.data);
                    });
                }
            }
        })
    </script>
    </html>
    

    2)success.html:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>成功页面</title>
    </head>
    <body>
    <h1>success.html</h1>
    </body>
    </html>
    
  • Step6:编写Java代码

    package com.hhxy.controller;
    
    import com.hhxy.pojo.User;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * @author ghp
     * @date 2022/10/3
     * @title
     * @description
     */
    @Controller
    public class TestAjaxController {
    
        @RequestMapping("/test/ajax")
        public void testAjax(Integer id, @RequestBody String requestBody, HttpServletResponse response) throws IOException {
            System.out.println("id = " + id);
            System.out.println("requestBody = " + requestBody);
            response.getWriter().write("hello,Ajax!");
        }
    }
    
  • Step7:测试

    image-20221004163514905

但是此时虽然适用@RequestBody注解获取到了请求体的参数,但是参数仍然是Json字符串,无法直接使用,我们需要将其转成Java对象才能直接适用,解决方案:

  • Step1:导入jaxkson依赖

            <!--jackson-->
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.13.4</version>
            </dependency>
    
  • Step2:在SpringMVC配置文件中开启注解驱动<mvc:annotation-driven/>

  • Step3:使用@RequestBody注解在控制器方法的形参位置,设置一个Json字符串要转换对象的Java对象

  • Step4:使用Response对象向前端返回响应数据

具体实现:

Controller中的代码:

    @RequestMapping("/test/RequestBody/json")
    public void testRequestBody(@RequestBody User user,HttpServletResponse response) throws IOException {
        System.out.println(user);
        response.getWriter().write("hello,RequestBody!");
    }

index.html中的代码:

testRequestBody(){
                axios.post(
                    "/day12_springmvc/test/RequestBody/json",
                    {username:"admin",password:"123",age:22,gender:"男"}
                ).then(resp => {
                    console.log(resp.data);
                });
            }

测试结果:

image-20221004164249787

除了适用实体类获取,还可以适用Map集合获取

    @RequestMapping("/test/RequestBody/json")
    public void testRequestBody(@RequestBody Map<String,Object> map, HttpServletResponse response) throws IOException {
        System.out.println(map);
        response.getWriter().write("hello,RequestBody!");
    }

image-20221004165015872

8.2 使用@ResponseBody处理AJAX请求

前面我们的控制器方法返回值是一个逻辑视图,Thymeleaf视图解析器会将他解析成一个页面,我们想要响应数据给HTML需要使用Servlet API,这显得很不方便。而通过将@ResponseBody注解加在控制器方法上,就能够让控制器方法的返回值直接以响应体数据发送给HTML

实现步骤:

  • Step1:导入jackson的依赖
  • Step2:在SpringMVC配置文件开启注解驱动<mvc:annotation-driven/>
  • Step3:给控制器方法配置@ResponseBody注解
  • Step4:直接通过方法返回值向前端返回响应数据

备注:这种方式最常用

    @RequestMapping("test/ResponseBody/json")
    @ResponseBody
    public User testResponseBodyJson(){
        User user = new User(1001,"admin","123",20);
        return user;
    }

PS:同理也可以使用Map集合或者List集合来发送响应数据,这里就不再演示了……具体代码请参考Gitee或Github仓库

image-20221004191355751

知识拓展@RestController

@RestController注解是一个复合注解,将它标识在类上相当于:给类标识了@Controller注解,给类中所有方法标识了@ResponseBody注解

9、文件下载和上传

9.1 文件下载

文件的下载需要使用到ResponseEntity,它用于控制器方法的返回值类型,该控制器方法的返回值类型就是响应给浏览器的数据。ResponseEntity对象是Spring对请求响应的封装。它继承了HttpEntity对象,包含了Http的响应码(httpstatus)、响应头(header)、响应体(body)三个部分。

html:

    <a th:href="@{/test/down}">测试文件的下载</a>

Java:

package com.hhxy.controller;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * @author ghp
 * @date 2022/10/4
 * @title 使用SpringMVC实现文件上传和下载
 * @description
 */
@Controller
public class TestResponseEntity {

    @RequestMapping("/test/down")
    public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
        //1、将图片加载到内存中
        //1.1 获取ServletContext对象
        ServletContext servletContext = session.getServletContext();
        //1.2 获取服务器中文件的真实路径
        String realPath = servletContext.getRealPath("/img/1.jpg");
        //1.3 将文件以流的形式加载内存中
        InputStream is = new FileInputStream(realPath);

        //2、将存储文件的流对象转成字节数组
        //2.1 创建字节数组
        byte[] bytes = new byte[is.available()];
        //2.2 将流读到字节数组中
        is.read(bytes);

        //3、将字节数组封装到ResponseEntity对象中
        //3.1 创建HttpHeaders对象设置响应头信息
        MultiValueMap<String, String> headers = new HttpHeaders();
        //3.2 设置要下载方式以及下载文件的名字
        headers.add("Content-Disposition", "attachment;filename=ghp.jpg");
        //3.3 设置响应状态码
        HttpStatus statusCode = HttpStatus.OK;
        //3.4 创建ResponseEntity对象
        ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);

        //4、释放资源
        is.close();

        //5、返回ResponseEntity对象,浏览器受到这个对象就会直接将该对象中的数据下载到本地
        return responseEntity;
    }

}

测试:

image-20221004200709318

9.2 文件上传

文件上传要求form表单的请求方式必须为post,并且添加属性enctype="multipart/form-data" 。SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息。

导入依赖
编写SpringMVC配置文件
编写HTML
编写Java
  • Step1:导入依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>day12_springmvc</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>war</packaging>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <dependencies>
            <!--servlet-api-->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>4.0.1</version>
                <scope>provided</scope>
            </dependency>
            <!--spring和thymeleaf整合包-->
            <dependency>
                <groupId>org.thymeleaf</groupId>
                <artifactId>thymeleaf-spring5</artifactId>
                <version>3.0.15.RELEASE</version>
            </dependency>
            <!--springMVC依赖,间接导入了spring-framework上下文-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.3.22</version>
            </dependency>
            <!--引入logback-classic,slf4j日志门面的实现包,thymeleaf需要依赖slf4j日志门面-->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.2.3</version>
            </dependency>
            <!--文件上传依赖-->
            <dependency>
                <groupId>commons-fileupload</groupId>
                <artifactId>commons-fileupload</artifactId>
                <version>1.4</version>
            </dependency>
        </dependencies>
    
    </project>
    
  • Step2:编写SpringMVC配置文件

    <!--必须通过文件解析器的解析才能将文件转换为MultipartFile对象-->
        <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
    <!--备注:还需扫描组件、配置Thymeleaf视图解析器、默认Servlet、开启注解驱动-->
    
  • Step3:编写HTML

        <form action="@{/test/up}" method="post" enctype="multipart/form-data">
            头像: <input type="file" name="photo"><br/>
            <input type="submit" value="上传">
        </form>
    

    注意:input标签的name一定要和MultipartFile类型的形参名称一样,否则报空指针异常

  • Step4:编写Java

     /**
         * 实现文件上传的控制器方法
         */
        @RequestMapping("/test/up")
        public String testUp(MultipartFile photo,HttpSession session) throws IOException {
    
            //1、设置上传文件存储在本地时的文件名(需要解决文件名重复问题,当文件名重复时后传文件的内容会覆盖前传文件的内容)
            //1.1 获取上传文件的文件名
            String fileName = photo.getOriginalFilename();
            //1.2 获取文件后缀名
            String suffix = fileName.substring(fileName.lastIndexOf("."));
            //1.3 获取一个永远不重复文件名(也可以使用时间戳实现)
            fileName = UUID.randomUUID().toString() + suffix;
    
            //2、设置上传文件存储在服务器的位置
            //2.1 获取服务器中photo目录的路径
            ServletContext servletContext = session.getServletContext();
            String photoPath = servletContext.getRealPath("photo");
            //2.2 创建File对象,用于判断路径是否存在
            File file = new File(photoPath);
            if(!file.exists()){
                //当路劲不存在时,就在target下创建photo路径(与就是文件夹)
                file.mkdir();
            }
            //2.3 获取上传文件的在服务器中的位置
            String finalPath = photoPath + File.separator + fileName;
    
            //3、将前端上传的文件的数据放在Web服务器路径下的fileName中
            photo.transferTo(new File(finalPath));
    
            //4、上传成功后,跳转到success.html页面
            return "success";
        }
    
  • Stpe5:测试

    image-20221004210639918

    可以看到上传的文件在target/photo下:

    image-20221004210527135

10、拦截器

示例

创建Maven工程
导入依赖
编写web.xml
编写SpringMVC配置文件
编写HTML
编写Java
测试
  • Step1:创建Maven工程

    image-20221004222453351

  • Step2:导入依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>day13_springmvc</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>war</packaging>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <dependencies>
            <!--servlet-api-->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>4.0.1</version>
                <scope>provided</scope>
            </dependency>
            <!--spring和thymeleaf整合包-->
            <dependency>
                <groupId>org.thymeleaf</groupId>
                <artifactId>thymeleaf-spring5</artifactId>
                <version>3.0.15.RELEASE</version>
            </dependency>
            <!--springMVC依赖,间接导入了spring-framework上下文-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.3.22</version>
            </dependency>
            <!--引入logback-classic,slf4j日志门面的实现包,thymeleaf需要依赖slf4j日志门面-->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.2.3</version>
            </dependency>
        </dependencies>
    
    </project>
    
  • Step3:编写web.xml

    配置编码过滤器、请求过滤器、前端控制器。略……

  • Step4:编写SpringMVC配置文件

    <?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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <!--扫描组件-->
        <context:component-scan base-package="com.hhxy"></context:component-scan>
    
        <!--配置thymeleaf视图解析器-->
        <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
            <!--设置优先级,让视图能够优先被thymeleaf视图解析器解析(默认优先级是Integer的最大值)-->
            <property name="order" value="1"/>
            <!--设置视图解析器的编码格式-->
            <property name="characterEncoding" value="UTF-8"/>
            <!--设置thymeleaf模板引擎-->
            <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>
    
        <!--让DefaultServlet处理静态资源-->
        <mvc:default-servlet-handler/>
    
        <!--开启注解驱动-->
        <mvc:annotation-driven/>
    
        <!--为index.html配置一个视图控制器-->
        <mvc:view-controller path="/" view-name="index"></mvc:view-controller>
    
        <!--配置SpringMVC拦截器-->
        <mvc:interceptors>
            <bean class="com.hhxy.interceptor.FirstInterceptor"></bean>
        </mvc:interceptors>
    
    </beans>
    

    知识拓展:SpringMVC拦截器相关知识

    • SpringMVC拦截器的配置方式

      • 方式一:根据类型引入Bean,直接使用class属性进行配置,如上所示

      • 方式二:使用ref引入Bean

        <!--为拦截器配置一个Bean-->
        <bean id="firstInterceptor" class="com.atguigu.interceptor.FirstInterceptor"></bean>
        <!--配置SpringMVC拦截器-->
        </mvc:interceptor>
        	<ref bean="firstInterceptor"></ref>
        </mvc:interceptor>
        
      • 方式三:使用注解默认的id引入Bean

        前提:需要先使用@Component注解标记拦截器所在类,然后再配置文件中要扫描组件

        这时候拦截器就会拥有一个默认的id,id为拦截器类名的小驼峰

        </mvc:interceptor>
        	<!--拦截器的类名为FirstInterceptor-->
        	<ref bean="firstInterceptor"></ref>
        </mvc:interceptor>
        

      方式一和方式二都是使用XML管理Bean,方式三是使用注解管理Bean,切记要开启注解驱动!

    • 拦截路径配置

      前面三种方式配置的拦截器会拦截所有的请求,可以通过以下方式配置详细的拦截路径

          <mvc:interceptors>
      <!--        <bean class="com.hhxy.interceptor.FirstInterceptor"></bean>-->
              <mvc:interceptor>
                  <!--设置拦截器的拦截路径 /* 只能拦截单层目录, /** 拦截多层目录的请求-->
                  <mvc:mapping path="/*"/>
                  <!--排除路径,虚拟目录 /abc 请求不会被拦截-->
                  <mvc:exclude-mapping path="/abc"/>
                  <!--设置拦截器(这里同样有三种方式)-->
                  <bean class="com.hhxy.interceptor.FirstInterceptor"></bean>
              </mvc:interceptor>
          </mvc:interceptors>
      
    • 拦截器的执行顺序

      • 情况一:Interceptor的preHandle方法返回都是true

        根拦截器的配置顺序有关,配置在前的拦截器的preHandle方法先执行,后面两个方法反序执行(和配置顺序相反)

            <mvc:interceptors>
                <bean class="com.hhxy.interceptor.FirstInterceptor"/>
                <bean class="com.hhxy.interceptor.SecondInterceptor"/>
            </mvc:interceptors>
        

      image-20221004231500967

      • 情况二:若某个拦截器的preHandle()返回了false(这里将First拦截器的preHandle返回值设为false)

        preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,返回false的拦截器之前的拦截器的afterCompletion()会执行

        image-20221004233943336

  • Step5:编写HTML

    1)index.html:

    <a th:href="@{/test/filter}">测试拦截器</a>
    

    2)success.html:

    <h1>success.html</h1>
    
  • Step6:编写Java

    1)编写TestController:

    package com.hhxy.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    /**
     * @author ghp
     * @date 2022/10/4
     * @title
     * @description
     */
    @Controller
    public class TestController {
    
        @RequestMapping("/test/filter")
        public String testFilter(){
            return "success";
        }
    }
    

    2)编写FirstInterceptor:

    package com.hhxy.interceptor;
    
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    /**
     * @author ghp
     * @date 2022/10/4
     * @title
     * @description
     */
    public class FirstInterceptor implements HandlerInterceptor{
    
        /**
         * 所有的拦截器执行后,Controller方法还未开始处理请求
         * @return true表示放行
         */
        @Override
        public boolean preHandle(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, Object handler) throws Exception {
            System.out.println("preHandle方法被调用了");
            return true;
        }
    
        /**
         * 必要条件:preHandle返回false
         * Controller方法处理完之后,DispatcherServlet进行视图的渲染之前
         */
        @Override
        public void postHandle(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("postHandle被调用了");
            HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
        }
    
        /**
         * 必要条件:postHandle返回false
         * DispatcherServlet进行视图的渲染之后
         */
        @Override
        public void afterCompletion(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("afterCompletion被调用了");
            HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
        }
    }
    
  • Step7:测试

    image-20221004223032489

    image-20221004223055570

11、异常处理器

SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolverHandlerExceptionResolver接口的实现类有:DefaultHandlerExceptionResolverSimpleMappingExceptionResolver

一个是SpringMVC默认的异常处理类,一个是自定义异常处理类

11.1 使用XML配置异常处理器

1)Java:

    @RequestMapping("/test/exception")
    public String testException(){
        System.out.println(1/0);
        return "success";
    }

2)SpringMVC配置文件:

    <!--设置SpringMVC异常解析器-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <!--当发生ArithmeticException异常时,直接跳转到error.html页面-->
                <prop key="java.lang.ArithmeticException">error</prop>
            </props>
        </property>
        <!--将异常信息存储到域对象中-->
        <property name="exceptionAttribute" value="exception"></property>
    </bean>

3)HTML:

index.html:

<a th:href="@{/test/exception}">测试异常处理器</a>

error.html:

<a th:href="@{/test/exception}">测试异常处理器</a>

4)测试结果:

11.3 使用注解配置异常处理器

将上面XML配置的异常处理器换成下面的代码:

package com.hhxy.controller;

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
 * @author ghp
 * @date 2022/10/5
 * @title
 * @description
 */
//@ControllerAdvice声明这个类是一个异常处理器
@ControllerAdvice
public class TestExceptionController {
	//当发生ArithmeticException异常就直接跳转到error.html页面
    @ExceptionHandler(ArithmeticException.class)
    public String testHandleException(Throwable exception,Model model){
        //将异常信息存储到域对象中
        model.addAttribute("exception",exception);
        return "error";
    }
}

测试:

image-20221005234852175

可以发现结果和前面是一致的O(∩_∩)O

备注:先注释掉前面在SpringMVC配置文件中配置的异常处理器

12、注解配置SpringMVC

Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类, 如果找到的话就用它来配置Servlet容器。 Spring提供了这个接口的实现,名为 SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配 置的任务交给它们来完成。Spring3.2引入了一个便利的WebApplicationInitializer基础实现,名为 AbstractAnnotationConfigDispatcherServletInitializer,当我们的类扩展了 AbstractAnnotationConfigDispatcherServletInitializer并将其部署到Servlet3.0容器的时候,容器会自 动发现它,并用它来配置Servlet上下文

简言之:当我们使用一个类继承 AbstractAnnotationConfigDispatcherServletInitializer类,并且是部署在Servlet3.0环境中,这时候Tomcat就会自动将该类替代web.xml

PS:这种方式可能现在看来很麻烦,但是大型项目会经常使用注解来配置SpringMVC,因为注解的出现就是为了简化

创建Mavne工程
导入依赖
编写HTML
编写Java
测试
  • Step1:创建Maven工程

    目录结构:

    image-20221006163743401

  • Step2:导入依赖

    pom.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>day14_springmvc</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>war</packaging>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <dependencies>
            <!--servlet-api-->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>4.0.1</version>
                <scope>provided</scope>
            </dependency>
            <!--spring和thymeleaf整合包-->
            <dependency>
                <groupId>org.thymeleaf</groupId>
                <artifactId>thymeleaf-spring5</artifactId>
                <version>3.0.15.RELEASE</version>
            </dependency>
            <!--springMVC依赖,间接导入了spring-framework上下文-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.3.22</version>
            </dependency>
            <!--引入logback-classic,slf4j日志门面的实现包,thymeleaf需要依赖slf4j日志门面-->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.2.3</version>
            </dependency>
        </dependencies>
    
    </project>
    
  • Step3:编写HTML

    • index.html:

      <!DOCTYPE html>
      <html lang="en" xmlns:th="http://www.thymeleaf.org">
      <head>
          <meta charset="UTF-8">
          <title>首页</title>
      </head>
      <body>
      <h1>index.html</h1>
      <hr>
      <a th:href="@{/test/firstInterceptor}">测试使用注解配置的SpringMVC的拦截器</a><br/>
      <a th:href="@{/test/exception}">测试使用注解配置的SpringMVC的异常处理器</a><br/>
      </body>
      </html>
      
    • success.html:

      <!DOCTYPE html>
      <html lang="en" xmlns:th="http://www.thymeleaf.org">
      <head>
          <meta charset="UTF-8">
          <title>成功</title>
      </head>
      <body>
      <h1>success.html</h1>
      </body>
      </html>
      
    • error.html:

      <!DOCTYPE html>
      <html lang="en" xmlns:th="http://www.thymeleaf.org">
      <head>
          <meta charset="UTF-8">
          <title>错误</title>
      </head>
      <body>
      <h1>error.html</h1>
      <p th:text="${exception}"></p>
      </body>
      </html>
      
  • Step4编写Java

    • 编写配置类

      • WebInit:

        package com.hhxy.config;
        
        import org.springframework.web.filter.CharacterEncodingFilter;
        import org.springframework.web.filter.HiddenHttpMethodFilter;
        import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
        
        import javax.servlet.Filter;
        
        /**
         * @author ghp
         * @date 2022/10/6
         * @title
         * @description 用来替代web.xml
         */
        //在Servlet3.0的环境,继承了该类,Tomcat自动会将该类替代web.xml
        public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
        
            /**
             * 设置一个配置类,用于代替Spring的配置文件
             * @return
             */
            @Override
            protected Class<?>[] getRootConfigClasses() {
                return new Class[]{SpringConfig.class};
            }
        
            /**
             * 设置一个配置类,用于代替SpringMVC的配置文件
             * @return
             */
            @Override
            protected Class<?>[] getServletConfigClasses() {
                return new Class[]{WebConfig.class};
            }
        
            /**
             * 设置SpringMVC的前端控制器DispatcherServlet的 url-pattern
             * @return
             */
            @Override
            protected String[] getServletMappings() {
                return new String[]{"/"};
            }
        
            /**
             * 设置过滤器
             * @return
             */
            @Override
            protected Filter[] getServletFilters() {
        
                //创建编码过滤器
                CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
                characterEncodingFilter.setEncoding("UTF-8");
                //让响应的数据也是用这个编码过滤器
                characterEncodingFilter.setForceEncoding(true);
        
                //设置请求方式过滤器
                HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
                return new Filter[]{characterEncodingFilter,hiddenHttpMethodFilter};
            }
        }
        
      • SpringConfig:

        package com.hhxy.config;
        
        /**
         * @author ghp
         * @date 2022/10/6
         * @title
         * @description 代替Spring的配置文件
         */
        public class SpringConfig {
        
        }
        
      • WebConfig:

        package com.hhxy.config;
        
        import com.hhxy.interceptor.FirstInterceptor;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.ComponentScan;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.web.context.ContextLoader;
        import org.springframework.web.context.WebApplicationContext;
        import org.springframework.web.servlet.HandlerExceptionResolver;
        import org.springframework.web.servlet.ViewResolver;
        import org.springframework.web.servlet.config.annotation.*;
        import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
        import org.thymeleaf.spring5.SpringTemplateEngine;
        import org.thymeleaf.spring5.view.ThymeleafViewResolver;
        import org.thymeleaf.templatemode.TemplateMode;
        import org.thymeleaf.templateresolver.ITemplateResolver;
        import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
        
        import java.util.List;
        import java.util.Properties;
        
        /**
         * @author ghp
         * @date 2022/10/6
         * @title
         * @description 代替SpringMVC的配置文件
         * 扫描组件、开启SpringMVC的注解驱动、配置视图解析器、启用默认DefaultServlet、
         * 配置视图控制器、拦截器、异常解析器、文件上传解析器
         */
        //将类标识为配置类
        @Configuration
        //扫描组件的注解(是一个数组形式的,可以扫描多个组件)
        @ComponentScan(value = {"com.hhxy.controller"})
        //开启SpringMVC的注解驱动
        @EnableWebMvc
        public class WebConfig implements WebMvcConfigurer {
        
            /**
             * 配置视图解析器,并为视图解析器注入模板引擎
             * @param templateEngine
             * @return
             */
            //@Bean注解可以将标识的方法的返回值作为Bean进行管理,Bean的默认id为方法名
            @Bean
            public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
                ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
                //设置视图解析器的编码格式
                viewResolver.setCharacterEncoding("UTF-8");
                viewResolver.setTemplateEngine(templateEngine);
                //返回视图解析器对象是为了让IOC容器进行管理
                return viewResolver;
            }
        
            /**
             * 配置模板引擎,并为模板引擎注入模板解析器
             * @param templateResolver
             * @return
             */
            @Bean
            public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
                SpringTemplateEngine templateEngine = new SpringTemplateEngine();
                templateEngine.setTemplateResolver(templateResolver);
                return templateEngine;
            }
        
            /**
             * 配置模板解析器
             * @return
             */
            @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;
            }
        
            /**
             * 启用默认DefaultServlet
             * @param configurer
             */
            @Override
            public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
                configurer.enable();
            }
        
            /**
             * 配置视图控制器
             * @param registry
             */
            @Override
            public void addViewControllers(ViewControllerRegistry registry) {
                //重定向方式的视图控制器,默认跳转到index.html页面
                registry.addViewController("/").setViewName("index");
            }
        
            /**
             * 配置拦截器
             * @param registry
             */
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                //配置一个拦截器,这个拦截器拦截出 *.jsp 这类请求以外的所有请求
                FirstInterceptor firstInterceptor = new FirstInterceptor();
                registry.addInterceptor(firstInterceptor).addPathPatterns("/**").excludePathPatterns("*.jsp");
            }
        
            /**
             * 配置异常处理器
             * @param resolvers
             */
            @Override
            public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
                SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
                Properties properties = new Properties();
                properties.setProperty("java.lang.ArithmeticException", "error");
                //当发生properties的key值类型的异常,就会跳转到error.html页面
                simpleMappingExceptionResolver.setExceptionMappings(properties);
                //将异常信息存储在域中,域对象的名称可以自定义,HTML页面可以通过该域对象获取异常信息
                simpleMappingExceptionResolver.setExceptionAttribute("exception");
                resolvers.add(simpleMappingExceptionResolver);
                //固定步骤,将配置好的异常解析器添加到resolvers对象中
                resolvers.add(simpleMappingExceptionResolver);
            }
        
            /**
             * 配置文件上传解析器(使用它记得添加jackson依赖)
             * @return
             */
        //    @Bean
        //    public CommonsMultipartResolver multipartResolver() {
        //        return new CommonsMultipartResolver();
        //    }
            
        }
        
    • 编写组件

      • Controller:

        package com.hhxy.controller;
        
        import org.springframework.stereotype.Controller;
        import org.springframework.web.bind.annotation.RequestMapping;
        
        /**
         * @author ghp
         * @date 2022/10/6
         * @title 测试类
         * @description
         */
        
        @Controller
        public class testController {
        
            /**
             * 测试使用了注解配置SpringMVC后,拦截器的效果
             * @return
             */
            @RequestMapping("/test/firstInterceptor")
            public String testFirstInterceptor(){
                return "success";
            }
        
            /**
             * 测试使用注解配置SpringMVC后,异常处理器的效果
             */
            @RequestMapping("/test/exception")
            public String testException(){
                System.out.println(1/0);
                return "success";
            }
        }
        
      • Interceptor:

        package com.hhxy.interceptor;
        
        import org.springframework.web.servlet.HandlerInterceptor;
        import org.springframework.web.servlet.ModelAndView;
        
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
        
        /**
         * @author ghp
         * @date 2022/10/6
         * @title
         * @description
         */
        public class FirstInterceptor implements HandlerInterceptor {
            /**
             * 在DispatcherServlet处理前执行该方法
             */
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                System.out.println("FirstInterceptor的preHandle方法被执行了");
                return true;
            }
        
            /**
             * 在视图渲染后执行该方法
             */
            @Override
            public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
                System.out.println("FirstInterceptor的postHandle方法被执行了");
                HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
            }
        
            /**
             * 在DispatcherServlet处理后执行该方法
             */
            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
                System.out.println("FirstInterceptor的afterCompletion方法被执行了");
                HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
            }
        }
        
  • Step5:测试

    测试拦截器:

    image-20221006162917532

    image-20221006163516337

    测试异常处理器:

    image-20221006163452960

13、SpringMVC执行流程

前置知识

  • DispatcherServlet前端控制器,不需要工程师开发,由框架提供
    作用统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求

    DispatcherServlet继承了FrameworkServlet,下面是FramworkServlet的继承关系

    image-20221006185937952

  • HandlerMapping处理器映射器,不需要工程师开发,由框架提供
    作用根据请求的url、method等信息查找Handler,即控制器方法

  • Handler处理器,需要工程师开发
    作用在DispatcherServlet的控制下Handler对具体的用户请求进行处理

  • HandlerAdapter处理器适配器,不需要工程师开发,由框架提供
    作用通过HandlerAdapteri对处理器(控制器方法)进行执行

  • ViewResolver视图解析器,不需要工程师开发,由框架提供
    作用进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectView

  • View视图
    作用将模型数据通过页面展示给用户

参考文章


  1. 静态资源:指的是在程序载入内存时对资源的一次性使用,之后就不再去访问这个资源了,比如:CSS、JS、图片、text文件、文字 ↩︎

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知识汲取者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值