SpringMVC学习笔记

文章目录

一、SpringMVC简介

1、什么是MVC

MVC是一种软件架构的思想,将软件按照按照模型、视图、控制器来划分

JavaBean分为两类:

  • 实体类Bean:专门存储业务数据的,如Study、User等
  • 业务处理Bean:指Service或者Dao对象,专门用于处理业务逻辑和数据访问

M:Model,模型层,指工程中的javaBean(业务处理Bean类),作用是处理数据

V:View,视图层,指工程中的html或jsp页面,作用是与用户进行交互,展示数据

C:Controller,控制层,指工程中的servlet,作用是接受请求和相应浏览器

MVC的工作流程:

用户通过视图层发送请求到服务器,在服务器中请求被Controller接受,Controller调用相应的Model层处理请求,处理完毕将结果返回到Controller,Controller在根据请求处理的结果找到相应的View视图,渲染数据最后响应给服务器器

2、什么是SpringMVC

SpringMVC是Spring的一个后续产品,是Spring的一个子项目

SpringMVC是Spring为表示层开发提供的一套完备的解决方案。

本质就是一个Servlet

3、SpringMVC的特点

  • 与IOC容器等基础设施无缝对接
  • Spring的配置文件和SpringMVC的配置文件一摸一样
  • 是基于原生的Servlet,对Servlet进行了封装,生成了功能强大的**DispatcherServlet前端控制器**,对请求和相应进行统一处理
  • 全方位覆盖了表示层的各个领域,提供全面的解决方案
  • 代码简洁,大幅提升开发效率
  • 内部组件化程度高,可插拔式组件即插即用(需要实现什么功能,可以直接配置进去,不需要自己写)
  • 性能好,适合大型项目

二、搭建SpringMVC框架

1、开发环境

IDE、Maven、tomcat、Spring

2、创建Maven工程

1.使用maven创建一个web工程

2.pom.xml中添加依赖

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

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

  <!-- thymeleaf和spring5 -->
  <dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    <version>3.0.12.RELEASE</version>
  </dependency>
</dependencies>

3、配置web.xml

注册SpringMVC的DispatcherServlet控制器(统一管理请求)

a>默认配置方式(不太好)

使用这种配置方式,SpringMVC的配置文件默认位于WEB-INF下,默认名称为==<Servlet-name的值>-servlet.xml==

例如:以下配置对应的SpringMVC配置文件位于WEB-INF下,名字为dispatcherServlet-servlet.xml

<!--过滤器-->
<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的前端控制器,统一处理除jsp以外的的所有请求-->
<servlet>
  <servlet-name>dispatcherServlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>dispatcherServlet</servlet-name>
  <url-pattern>/</url-pattern>
  <!-- / 可以匹配除jsp页面以外所有的请求,因为jsp有他自己的servlet程序-->
</servlet-mapping>

b>扩展配置方式(这种好)

通过init-param标签设置SpringMVC配置文件的名称和位置(在resources下创建)

通过load-on-startup标签设置SpringMVC前端控制器DispatcherServlet的初始化时间

<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的前端控制器,统一处理除jsp以外的的所有请求-->
<servlet>
  <servlet-name>dispatcherServlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <!--设置springMVC配置文件的名称和路径-->
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:springMVC.xml</param-value>
  </init-param>
  <!--设置dispatcherServlet的初始化时间,1表示在服务器启动时(可以减少用户等待时间)-->
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>dispatcherServlet</servlet-name>
  <url-pattern>/</url-pattern>
  <!-- / 可以匹配除jsp页面以外所有的请求,因为jsp有他自己的servlet程序-->
</servlet-mapping>

注:

标签中使用 / 和 /* 的区别:

  1. / 所匹配的请求是/login或.html或.js或.css的请求路径,但是/不能匹配.jsp请求路径的请求。从而可以避免访问jsp页面时,该请求被DispatcherServlet处理,从而找不到对应的页面(因为jsp页面有单独的servlet程序)
  2. /* 能够匹配所有请求,例如在使用过滤器时,若需要对所有请求进行过滤,就需要使用/*的写法

4、创建请求控制器

由于前端控制器对浏览器发送的请求进行了统一的处理,但是具体的请求有不同的处理方式,所以需要创建具体的请求控制器(一个类),然后由前端控制器去匹配请求控制器中对应的方法

请求控制器中每一个请求处理的方法称为控制器方法

因为SpringMVC的控制器由一个普通类担任,因此需要通过==@Controller注解==将其标识为一个控制层组件,交给IOC容器管理,此时SpringMVC才能够识别控制器的存在

  1. 创建一个controller包(存放请求控制器)

  2. 创建一个请求控制器的类

  3. 给类添加@Controller注解

    @Controller //spring中AOP的一个注解,说明他是一个控制器
    public class FirstController {
    
    }
    

5、配置SpringMVC的配置文件

  1. 开启扫描组件(因为使用到了Controller注解)

    <!--需要扫描的包-->
    <context:component-scan base-package="love.junqing.controller"></context:component-scan>
    
  2. 配置Thymeleaf视图解析器(可以直接复制过来)(需要添加context、mvc名称空间)

    <!-- 配置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:templateEngine的值 -->
            <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
                <property name="templateResolver">
                    <!-- 内部bean: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>
    <!-- mvc注解驱动 -->
    <mvc:annotation-driven/>
    

6、创建访问的首页页面

  1. 根据SpringMVC配置文件的视图前缀和视图后缀创建html页面

    image-20230326131938868

  2. 给页面添加thymeleaf的名称空间

    <html lang="en" xml:th="http://www.thymeleaf.org">
    
  3. 编辑页面内容(自己随便搞点)

7、配置控制器中的方法

当前我们想要的功能是,当我们访问 ==/==的时候,就会跳转到/WEB-INF/templates/index.html页面

  1. 在控制器中编写方法,返回一个String类型的数据,方法名随便

  2. 给方法添加**RequestMapping注解**,value是/

    @RequestMapping注解的功能:将请求和控制方法映射起来

    参数value:根据请求地址调用对应方法,可以省略

// 当访问 / 的时候,就跳转到/WEB-INF/templates/index.html下

@RequestMapping(value = "/")//将请求和控制方法映射起来,当访问/的时候,会调用这个方法
public String goIndex(){
    //返回视图名称(要访问的页面)
    return "index";//包名和后缀thymeleaf已经给了
}

8、配置启动tomcat服务器

9、访问指定页面的方法

  1. 创建一个新的html页面(需要加上th域)

  2. index页面添加a标签,跳转到新创建的页面

    <!--th:href="@{/名字}会把/名字发送给控制器-->
    <a  th:href="@{/target}">跳转到target页面</a>
    
  3. 控制器中创建方法,添加注解,返回值是页面的名字

    @RequestMapping(value = "/target")//a标签发送过来的值是这个
    public String goTarget(){
        //返回视图名称(要访问的页面)
        return "target";//包名和后缀thymeleaf已经给了
    }
    

10、总结

浏览器发送请求,若请求地址符合前端控制器的,该请求就会被前端控制器(DispatcherServlet)处理。前端控制器会读取SpringMVC的核心配置文件,通过扫描组件找到控制器,将请求地址和控制器中@RequestMapping注解的value属性值进行配置,若匹配成功,该注解所表示的处理器方法就是请求的方法。控制器方法返回一个字符串类型的页面名称,这个名称会被视图解析器解析,从而自动加上前缀和后缀,从而组成给页面的路径,通过Thymeleaf对视图进行渲染,最终转发到视图对应的页面

三、@RequestMapping注解

1、@RequestMapping注解的功能

@RequestMapping的作用:将请求和控制器方法关联起来

SpringMVC接收到指定的请求,就会找到映射关系中对应的控制器方法来处理这个请求

  1. 如果两个控制器的控制方法上@RequestMapping的value值一样,那么会报错

2、@RequsetMapping注解的位置

可以在类上,也可以在方法上

a.注解放到类上(层级匹配)

匹配的初始信息(一层一层匹配):先匹配发送过来的路径有没有类上的值,如果有再去匹配方法上面的

  • HTML页面:

    <a  th:href="@{/hello/testRequestMapping}">跳转到target页面</a>
    
  • 控制器:

    @Controller
    @RequestMapping(value = "/hello")//先匹配路径上有没有这个
    public class TestRequestMapping {
        @RequestMapping(value = "/testRequestMapping")//发送的请求来自这个页面,会执行goIndex方法
        public String goTarget(){
            //返回视图名称(要访问的页面)
            return "target";//包名和后缀thymeleaf已经给了
        }
    }
    

说明:因为跳转地址是 /hello/target ,所以先去控制器上看,控制器的类上有注解,先匹配是不是符合类上的地址/hello,符合,然后剩下的地址 /testRequestMapping 再去匹配类里方法上面注解的地址

b.注解放到方法上(直接匹配)

直接让浏览器发送的请求和控制器方法创建关联

@RequestMapping(value = "/testRequestMapping")//发送的过来的请求地址是/testRequestMapping,会执行goIndex方法
public String goIndex(){
    //返回视图名称(要访问的页面)
    return "index";//包名和后缀thymeleaf已经给了
}

3、@RequestMapping注解的value属性

value( ):根据**请求地址判断调用的请求,接收的参数可以是一个String类型数组**

要求:请求的请求地址和注解的value值相等

  1. @RequestMapping注解的value属性通过请求的请求地址匹配请求映射
  2. @RequestMapping注解的value属性是一个字符串类型的数组,表示该请求映射能够匹配多个请求地址所对应的请求
  3. @RequestMapping注解的**value属性必须设置**,至少通过请求地址匹配请求映射
  • 多地址跳转统一页面案例

    1. html页面

      <!--两个请求地址-->
      <a  th:href="@{/testRequestMapping}">跳转到target页面</a>
      <a  th:href="@{/gototarget}">跳转到target页面</a>
      
    2. java代码

      //请求过来的地址指向同一页面
      @RequestMapping(value = {"/testRequestMapping","/gototarget"})//设置多个路径都可一请求过来
      public String goTarget(){
          //返回视图名称(要访问的页面)
          return "target";//包名和后缀thymeleaf已经给了
      }
      

4、@RequestMapping注解的method属性

method( ):根据请求方式(post、get)判断调用的请求

说明:@RequestMapping注解的value属性是必须的,所以method是配合value一块使用的

  1. 如果不设置method属性,则任何请求方式都能匹配(前提:请求地址能和value匹配成功)

  2. 设置method的值(可以设置多个):RequestMethod.请求方式

  3. 案例:
    @RequestMapping(value = {"/testRequestMapping","/gototarget"},
                    method={RequestMethod.GET,RequestMethod.POST})//设置两种请求方式
    public String goTarget(){
        //返回视图名称(要访问的页面)
        return "target";//包名和后缀thymeleaf已经给了
    }
    
  4. 说明:
    1. 如果value属性匹配成功,但是method属性不匹配,会报错405
  5. 派生注解

    对于处理指定请求方式的控制器方法,SpringMVC种提供了@RequestMapping的派生注解

    • 处理get请求的映射—>@GetMapping
    • 处理post请求的映射—>@PostMapping
    • 处理put请求的映射—>@PutMapping
    • 处理delete请求的映射—>@DeleteMapping
    //使用案例
    @GetMapping(value="/testGetMapping")//请求方式已经给了,所以直接给一个地址就可以了
    public String testGetMapping(){
        return "target";
    }
    
  6. 常用的请求方式:get、post、put、delete

    浏览器目前只支持get、post方式的请求,如果再表单提交时method使用put或者delete,则按get方式提交

    若要发送put和delete请求,则需要通过spring提供的过滤器HiddenHttpMethodFilter。后面会讲

5.@RequestMapping注解的params属性(了解)

通过请求的参数匹配请求的方法,请求参数必须同时满足才能进行请求映射,如果匹配不上报错400

  1. 请求参数的四种形式
    • params = {“username”}:表示发送过来的请求必须以携带username属性(不管值是多少)

      @RequestMapping(value = {"/gototarget"},
              params = {"username"})
      
    • params = {“!username”}:表示发送过来的请求不能携带username参数

    • params = {“username=admin”}:表示发送过来的请求必须以携带username属性,并且值必须是admin

    • params = {“username=!admin”}:表示发送过来的请求必须以携带username属性,但是值不能是admin

6.@RequestMapping注解的headers属性(了解)

通过请求头匹配请求映射,使用方法和params一样,如果匹配不上报错404

image-20230403202044241

7.SpringMVC支持ant风格的路径

即支持模糊匹配

:表示任意的单个字符

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

**:表示任意的一层或多层目录

注意:在使用时,必须写成/ * */xxx

8.SpringMVC支持路径中的占位符(重点)

请求参数不再使用?拼接,而是将参数作为路径的一部分,直接拼接,中间使用/分开

  1. 例子:
    • 原始方式:/deluser?username=1&pwd=123
    • rest方式:/deluser/1/123
  2. 案例:
    @RequestMapping(value = {"/gototarget/{id}"})//{ }表示一个占位符,里面给传过来的参数起一个别名
    public String rest(@PathVariable("id")Integer id){//将上面的参数赋值给方法中的形参 @PathVariable只能用在形参上
        System.out.println(id);
        return "target";
    }
    
  3. 说明:
    1. 如果使用了占位符,发送请求的时候,必须要将参数发送过来
    2. { }表示一个占位符,里面给传过来的参数起一个别名
    3. @PathVariable注解只能用在形参上
    4. 如果有多个参数,使用/{别名}往后拼接即可

四、SpringMVC获取请求参数

1、通过ServletAPI获取(request对象)(用的少)

即使用HttpServletRequest

@RequestMapping(value = {"/servletAPI"})
public String servletAPI(HttpServletRequest request){//这里web.xml中的前端控制器会自动将本次对话的request对象传入
    String username = request.getParameter("username");
    String password = request.getParameter("password");
    return "target";
}

2、通过控制器方法的形参获取请求参数

只需要保证前端传过去的参数名和控制器方法中的形参名一样,就可以自动赋值

  1. 参数的名字都不一样
@RequestMapping(value = {"/servletAPI"})
public String getParam(String username,String password){//参数名需要和前端的参数名一样,参数会自动注入
    System.out.println(username+" "+password);
    return "target";
}
  1. 参数的名字有一样的(形参给一个数组即可)
@RequestMapping(value = {"/servletAPI"})
public String getParam(String [] hobby){//会将前端传过来的多个同名参数传进去
    for(String i :hobby){
        System.out.println(i);
    }
    return "target";
}

3、@RequestParam注解

用于处理前端传过来的参数名和后端的形参名不一致

  1. 例子:

    前端传过来的参数名是user_name,后端的参数名是username

  2. 案例:@RequestParam中的参数名是前端参数的名字
    @RequestMapping(value = {"/servletAPI"})
    public String getParam(@RequestParam("user_name")String username){//@RequestParam中的参数名是前端参数的名字
        System.out.println("username");
        return "target";
    }
    
  3. @RequestParam的参数
    • value:参数名字(必须要写
    • require:是否必须要有这个参数值(默认是true)(可以忽略不写
    • defaultvalue:默认值(给参数一个默认的值)(可以忽略不写

4、@RequestHeader注解

将请求头信息和控制器方法的形参创建映射关系

参数信息和使用方法与上面的@RequestParam一样

5、@CookieValue注解

将cookie数据和控制器方法的形参创建映射关系

参数信息和使用方法与上面的@RequestParam一样

6、通过pojo获取请求参数

可以在控制器方法的形参位置传入一个pojo对象,如果浏览器传输的请求参数的参数名和pojo中的属性名一一对应,那么请求参数就会自动给这个属性赋值

适用于参数很多的环境下

  • htm代码(name属性要和pojo对象的属性名一样)
    <form th:action="@{/testBean}" method="get">
        用户名:<input type="text" name="username"><br/>
        密码:<input type="password" name="password"><br/>
        年龄:<input type="text" name="age"><br/>
        <input type="submit" value="提交">
    </form>
    
  • 控制器方法(对象的属性名要和表单中属性的name一样)
    @RequestMapping(value = {"/testBean"})
    public String testBean(User user){
        System.out.println(user);
        return "target";
    }
    

7、过滤器处理请求参数乱码问题

前提说明:

  • 获取参数之后再设置编码格式是没有效果的,所以应该在获取参数之前就设置编码格式
  • get方式没有编码错误问题(tomcat的本地配置中设置过的),只有post才有编码错误问题
  • 因为配置文件中load-on-startup已经设置为服务器启动时了,所以需要一种比servlet还提早启动的技术实现编码的设置
  1. 启动顺序

    (servlet-context)监听器、过滤器、servlet

  2. 配置过滤器

    web.xml文件中进行如下配置:

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

五、域对象添加数据

必须知道的三种域对象:request(一次请求)、session(一次会话)、application(服务器启动时创建,关闭时销毁)

1.向request域添加数据(5种方式)

1、通过servletAPI添加域数据

  • java代码
    @RequestMapping("/")
    public String goIndex(HttpServletRequest request){
        request.setAttribute("key1","123");//设置request域对象:键,值
        return "index";//请求转发到index页面
    }
    
  • html代码

    request域:直接通过键获得

    session域:通过session.键获得

    servletContext域:通过application.键获得

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org"><!--添加thymeleaf的th标签-->
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    this is index page
    <!--thymeleaf通过键获得值,会报错不用管他-->
    <p th:text="${key1}"></p>
    </body>
    </html>
    

2、使用ModelAndView向request域对象共享数据(建议使用)

ModelAndView有Model和View两个功能

  • Model:主要用于向请求域共享数据
  • View:主要用于设置视图(实现页面跳转)
@RequestMapping("/")
public ModelAndView goIndex(){//需要返回一个ModelAndView对象
    ModelAndView mav = new ModelAndView();
    //往域中添加数据
    mav.addObject("testkey","yfj");//将数据添加到域中
    //设置跳转界面
    mav.setViewName("index");
    return mav;//将对象返回
}

3、使用Model向request域对象共享数据

@RequestMapping("/")
public String goIndex(Model model){
    //往域中添加数据
    model.addAttribute("testkey","zyj");//
    //跳转界面
    return "index";
}

4、使用map向request域对象共享数据

@RequestMapping("/")
public String goIndex(Map<String,Object> map){//键是String类型,值根据实际情况定
    //往域中添加数据
    map.put("testkye","zyj");
    //跳转界面
    return "index";
}

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

@RequestMapping("/")
public String goIndex(ModelMap modelMap){
    //往域中添加数据
    modelMap.addAttribute("testkey","hello,modelmap");
    //跳转界面
    return "index";
}

6、Model、ModelMap、Map的关系

Model、ModelMap、Map类型的参数本质上都是BindingAwareModelMap类型的,最后都会封装到ModelAndView中

2.向Session域添加数据

推荐使用Servlet原生API

  • java代码
    @RequestMapping("/")
    public String goIndex(HttpSession Session){
        //往域中添加数据
        Session.setAttribute("testkey","hello,modelmap");
        //跳转界面
        return "index";
    }
    
  • html代码(使用session.key值)
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org"><!--添加thymeleaf的th标签-->
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    this is index page
    <!--thymeleaf通过键获得值,会报错不用管他-->
    <p th:text="${session.testkey}"></p>
    </body>
    </html>
    

3.向application域添加数据

通过session对象获取application对象

  • java代码
    @RequestMapping("/")
    public String goIndex(HttpSession Session){
        ServletContext servletContext = httpSession.getServletContext();//通过httpsession获取application域
        //往域中添加数据
        servletContext.setAttribute("testkey","hello,application");
        //跳转界面
        return "index";
    }
    
  • html代码
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org"><!--添加thymeleaf的th标签-->
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    this is index page
    <!--thymeleaf通过键获得值,会报错不用管他-->
    <p th:text="${application.testkey}"></p>
    </body>
    </html>
    

六、SpringMVC的视图

SpringMVC的视图是View接口,视图的作用是渲染数据,将模型Model中的数据展示给用户

SpringMVC视图的种类很多,默认有转发视图(InternalResourceView)和重定向视图(RedirectView)

其他视图种类:

  • 当引入jstl依赖时,**转发视图**会自动==转化为jstlView==
  • 若使用的视图技术为Thymeleaf,在SpringMVC的配置中配置了Themeleaf的视图解析器,由此视图解析器解析之后得到的时ThymeleafView

创建视图的对象种类只和对象名称有关:如果没有前缀创建的就是 ThymeleafView;如果前缀是 Redirect:,就会创建RedirectView(重定向视图);如果前缀是forward: ,就会创建InternalResourceView(转发视图)

1.Thymeleaf(用来跳转页面)

当控制器方法设置的视图名称没有任何前缀,此时的视图名称会被SpringMVC的配置文件中配置的Themeleaf的视图解析器解析,视图名称拼接视图前缀和视图后缀从而得到最总路径,会通过转发的方式实现跳转用于跳转到Thymeleaf解析的页面,不能用于跳转到下一个请求

@RequestMapping("/")
public String goIndex(){
    return "index";
}

2.转发视图(用来跳转请求)(用的少)

当控制器方法设置的视图名称前缀是**forward:,会创建InternalResourceView视图,视图把前缀去掉,然后转发到后面的请求(只能跳转请求**)

//请求转发
@RequestMapping("/")
public String forward(){
    return "forward:/test";//会跳转到/test请求
}

3.重定向视图(用来跳转请求)(不同功能的跳转)

当控制器方法设置的视图名称前缀是**redirect:,会创建RedirectView视图,视图把前缀去掉,然后重定向到后面的请求(只能跳转请求和WEB-INF以外的界面**)

//请求重定向
@RequestMapping("/")
public String forward(){
    return "redirect:/text";//重定向到/test请求
}

4.视图控制器view-controller(仅用于实现页面跳转)

使用条件:当跳转路径不需要其他处理,可以直接跳转到相应页面的情况下使用

不需要在控制器方法中再去跳转

当SpringMVC中设置任何一个view-controller时,其他控制器中的请求映射将全部失效,此时需要在SpringMVC配置文件中添加mvc注解驱动标签

  1. 添加mvc名称空间
  2. 添加标签,配置跳转路径(path:表示当前路径,view-name:表示需要跳转的页面)
    <mvc:view-controller path="/" view-name="index"></mvc:view-controller><!--报错但是可以正常使用-->
    
  3. 开启mvc的注解驱动(不开启会导致后面的请求映射全部失效
    <mvc:annotation-driven/>
    

5.兼容jsp页面

  1. SpringMVC配置文件中添加标签

    <mvc:annotation-driven/>
    <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.RESTFul简介

REST:Representational State Transfer 表现层资源状态转移

RESTFul是一种代码风格,制定一个规则,让前后端统使用同一个路径获取资源,根据请求方式调用不同方法

  • 资源的表述

    资源的表现形式(例如:html、css、xml、纯文本、图片、视频、音频等)

  • 状态转移

    请求路径相同,根据请求方式,调用不同操作

2.模拟RESTFul

GET(获取)、POST(增加)PUT(修改)DELETE(删除)

请求参数从前到后使用 / 分开,不使用问号键值对携带请求参数,而是将数据作为url请求地址的一部分

使用了上面笔记中的占位符操作

操作传统方式REST风格
查询操作getUSerById?id=1user/1—>get请求方式
保存操作saveUSeruser—>post请求方式
删除操作deleteUser?id=1user/1—>delete请求方式
更新操作updateUseruser—>put请求方式

1.模拟get和post方法

  • HTML代码

    <a th:href="@{/user}">查询所有用户信息</a><br/>
    <a th:href="@{/user/1}">查询id=1的用户信息</a><br/>
    <form th:action="@{/user}" method="post">
        用户名:<input type="text" name="username">
        密码:<input type="password" name="password">
        <input type="submit" value="添加">
    </form>
    
  • Java代码

    //查询所有用户信息--》get方式
    @RequestMapping(value="/user",method = RequestMethod.GET)
    public String getUsers(){
        System.out.println("查询所有用户信息");
        return "success";
    }
    //根据id查询单个用户信息---》get方式
    @RequestMapping(value="/user/{id}",method = RequestMethod.GET)//{}:是占位符,里面是参数名
    public String getUser(@PathVariable("id") Integer id){//@PathVariable用于把参数名注入形参
        System.out.println("查询id="+id+"的用户信息");
        return "success";
    }
    //添加用户信息--》post方式
    @RequestMapping(value="/user",method = RequestMethod.POST)
    public String addUser(String username,String password){//也可以使用一个pojo对象直接注入
        System.out.println("添加用户信息用户名:"+username+"\t密码:"+password);
        return "success";
    }
    

2.模拟put方法

  1. 首先需要**配置下面的HiddenHttpMethodFilter过滤器**
  2. 然后设置**表单的方法为post**
  3. 设置一个名字为**_method的隐藏域,值是delete或者put**
  • HTML代码

    <form th:action="@{/user}" method="post">
        <input type="hidden" name="_method" value="put">
        用户名:<input type="text" name="username">
        密码:<input type="password" name="password">
        <input type="submit" value="修改">
    </form>
    
  • Java代码

    @RequestMapping(value="/user",method = RequestMethod.PUT)
    public String putUser(String username,String password){//也可以使用一个pojo对象直接注入
        System.out.println("修改的用户信息用户名:"+username+"\t密码:"+password);
        return "success";
    }
    

2.模拟delete方法

  1. 因为删除和修改一般都是超连接,所以超链接里面的地址需要用下面的方式拼接

     <a th:href="@{/user/}+${参数}">查询id=参数的用户信息</a>
    
  2. 因为超链接使用的是get方法,所以需要创建一个表单,内容是一个隐藏域

    <form method="post"><!--提交地址让js赋值-->
        <input type="hidden" name="_method" value="delete">
    </form>
    
  3. mvc配置文件开放对静态资源的访问

    <mvc:defaylt-servlet-handler><!--防止js被前端控制器处理-->
    
  4. 当点击超链接时,我们使用js控制表单提交

    <!--只是一个演示,不知道对不对-->
    超链接的名字.function(event){
    var deleteForm=document.getElementbyid("表单的名字");
    <!--超链接的地址给表单的action-->
    deleteForm.action=event.target.href;
    deleteForm.submit();<!--提交表单-->
    event.preventDefault();<!--取消默认行为-->
    }
    

3、HiddenHttpMethodFilter的配置

用来实现delete和put方法,本质是一个过滤器,所以需要在web.xml中配置

要放在CharacterEncodingFilter后面

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

八、RESTFul案例

1.创建Maven的web工程

2.导入maven依赖

暂时没有使用数据库

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

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

  <!-- thymeleaf和spring5 -->
  <dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    <version>3.0.12.RELEASE</version>
  </dependency>
</dependencies>

3.配置web.xml文件(标配:两个过滤器、一个控制器)

<!--编码过滤器-->
<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>

<!--隐藏域过滤器(用来实现delete和put方法)-->
<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>

<!--配置SpringMVC的前端控制器,统一处理除jsp以外的的所有请求-->
<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--设置springMVC配置文件的名称和路径-->
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springMVC.xml</param-value>
    </init-param>
    <!--设置dispatcherServlet的初始化时间,1表示在服务器启动时(可以减少用户等待时间)-->
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
    <!-- / 可以匹配除jsp页面以外所有的请求,因为jsp有他自己的servlet程序-->
</servlet-mapping>

4.创建、配置springMVC配置文件

  1. 开启扫组件
  2. 开启thtmeleaf视图解析器
  3. 如果开启了view-controller,还需要开启mvc注解驱动
<!--需要扫描的包-->
<context:component-scan base-package="love.junqing.controller"></context:component-scan>

<!-- 配置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:templateEngine的值 -->
        <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
            <property name="templateResolver">
                <!-- 内部bean: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>

<mvc:defaylt-servlet-handler><!--开放对静态资源的访问-->
<mvc:annotation-driven/><!--开启mvc注解驱动-->

5.创建控制器类

使用注解@Controller

6.创建bean类

使用注解@Reponsitory,开启之后service层可以根据类型使用@Autowired自动注入

7.编写前端界面

  • 修改数据
    1. 点击超链接,将id发送到后端控制器

    2. 后端控制器根据id查询单条数据

    3. 将数据封装到Model中,然后跳转到前端页面显示原来的信息,等待用户修改数据

      <!--回显输入的数据-->
      <input type="text" name="email" th:value="${employee.email}"
      <!--回显单选框-->
      <input type="text" name="sex" th:field="${employee.sex}"
      
    4. 用户点击修改后,将表单提交到后台控制器(两个隐藏域:put方式和id值),根据id值查询对象,然后把对象的数据修改,重定向到所有数据的页面

8.配置tomcat

九、HttpMessageConverter

报文信息转化器:将请求报文转换为java对象,或者将java对象转换为响应报文

HttpMessageConverter提供了两个注解和两个类型(两个关于请求报文的、两个关于响应报文的):@RequestBody(请求报文转换为java对象)、@ResponseBody(将java对象转换为响应报文)、RequestEntity(可以接受整个请求报文)、ResponseEntity(可以接受整个响应报文)

1、@RequestBody

  • @RequestBody可以获取请求体(请求体就是请求参数,所以post才有请求体)
  • 在控制器方法中设置一个形参,使用@RequestBody进行表示,当前请求的请求体就会为形参自动赋值
  • html代码

    <form th:action="@{/test}" method="post">
        <input type="text" name="username">
        <input type="password" name="password">
        <input type="submit" value="提交">
    </form>
    
  • java代码

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

2、RequestEntity

封装整个请求报文的一种类型

  • java代码

    @RequestMapping("/test")
    public String test(RequestEntity<String> requestEntity){
        System.out.println(requestEntity.getHeaders());//获取请求头信息
        System.out.println(requestEntity.getBody());//获取请求体信息
        return "success";
    }
    

3、@ResponseBody

响应浏览器数据

功能类似于:response.getwriter().writer(“hello,world”);

  • html代码

    <a th:href="@{/test}">测试</a>
    
  • java代码

    @RequestMapping("/test")
    @ResponseBody
    public String test(){
        return "hello,world!";//响应体信息,屏幕上只会显示这一句话
    }
    

4、ResponseEntity

  • 响应实体

  • 用在控制器方法的返回值类型上

  • 就是自定义的响应报文

  • 可以干什么:文件下载(看笔记的下载)

5、Spring处理json

@ResponseBody如果返回一个对象,浏览器不能解析,所以需要使用json

  1. 导入json的依赖
    <dependency>
     <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.12.1</version>
    </dependency>
    
  2. 开启mvc注解驱动
    <mvc:annotation-driven/><!--view-controller 静态资源、json对象都使用到了-->
    
  3. 在控制器方法上添加@ResponseBody注解,将对象作为欸返回值

    会自动转换为json格式的字符串

    @RequestMapping("/test")
    @ResponseBody
    public User test(){
        return new User("yfj","123455",22);
    }
    

6、SpringMVC处理ajax

ajax:页面不发生跳转,和后台发生数据交换

使用vue+ajax

jquery也可以但是忘了

前端数据发送到后端,并回传

  1. 导入vue和axios的js文件
  2. html文件中导入js文件
    <script type="text/javascript" th:src="@{static/js/axios.min.js}"></script>
    <script type="text/javascript" th:src="@{static/js/vue.js}"></script>
    
  3. 给超链接一个点击事件
    <a @click="testAxios" th:href="@{/testAjax}">测试Ajax</a>
    
  4. 编写js代码
    <script type="text/javascript">
        //1.new Vue对象
        new Vue({
            el:"#app",//表示容器的id
            method:{//功能
                testAxios:function (event) {//点击超链接出触发的功能,event表示当前事件
                    axios({
                        method: "post",//请求方式
                        url:event.target.href,//触发事件的地址
                        params:{//传输的数据
                            username="admain",
                            password="123"
                        }
                    }).then(function (response) {//表示处理成功之后
                        alert(response.data);//显示响应的数据
                    });
                    event.preventDefault();//取消默认事件
                }
            }
        });
    </script>
    
  5. 后端
    @RequestMapping("/test")
    @ResponseBody
    public String test(String username ,String password){
        System.out.println("前端发送过来的数据:"+username+password);
        return "回传给后端的数据";
    }
    

7、@RestController注解(重要)

  • 这个注解加在类上(因为微服务每一个控制器方法都需要加上@ResponseBody注解)
  • 这个注解是一个复合注解:@ResponseBody和@Controller
  • 加上之后说明:每一个控制器方法的返回值都作为响应体

十、文件下载和上传

1.文件下载

使用ResponseEntity实现下载文件的功能

@RequestMapping("/testDown")
public ResponseEntity<byte[]> testResponseEntity(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()];//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<>(bytes, headers, statusCode);
    //关闭输入流
    is.close();
    return responseEntity;
}

2.文件上传

  • 上传只能使用post方式
  1. 依赖

    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.1</version>
    </dependency>
    
  2. 配置上传解析器

    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
    
  3. 前端代码

    form表单需要设置属性:enctype=“multipart/form-data”

    <form th:action="@{/upload}" method="post" enctype="multipart/form-data">
        头像:<input type="file" name="photo">
        <input type="submit" value="提交">
    </form>
    
  4. 后端代码

    @RequestMapping("/upload")
    public String testUp(MultipartFile photo, HttpSession session) throws IOException {
        //获取上传的文件的文件名
        String fileName = photo.getOriginalFilename();
        //处理文件重名问题
        //截取最后一个.之前的内容
        String hzName = fileName.substring(fileName.lastIndexOf("."));
        //获取随机uuid
        String uuid= UUID.randomUUID().toString() ;
        //拼接名字
        fileName =uuid+hzName;
        //获取服务器中保存photo资源的目录
        ServletContext servletContext = session.getServletContext();
        String photoPath = servletContext.getRealPath("photo");//photo资源保存的真实路径
        File file = new File(photoPath);
        if(!file.exists()){
            //如果不存在创建目录
            file.mkdir();
        }
        String finalPath = photoPath + File.separator + fileName;//文件保存路径
        //实现上传功能
        photo.transferTo(new File(finalPath));//设置文件上传的位置
        return "success";
    }
    

十一、拦截器

拦截器的英文:interceptor

作用于控制器方法

一共有三种:一个在控制器执行之前,一个在控制器执行之后,一个在视图渲染完毕之后

1.拦截器的配置

第一种:拦截所有的控制器方法

  1. SpringMVC配置文件中配置

    <!--拦截器-->
    <mvc:interceptors>
        <!--bean 表示哪个类是拦截器-->
        <bean class="love.junqing.controller.FirstInterceptor"></bean>
    </mvc:interceptors>
    
  2. 创建一个类(放在interceptors包下)
  3. 实现接口HandlerInterceptor
  4. 重写里面的三个方法(Ctrl+O)
    public class FirstInterceptor implements HandlerInterceptor {
        //控制器方法执行之前
        //功能是拦截:返回true表示放行,返回false表示拦截
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("FirstInterceptor--->preHandle");
            return true;//true表示放行请求
        }
        //控制器方法执行之后
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("FirstInterceptor--->postHandle");
        }
        //视图渲染完成之后
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("FirstInterceptor--->afterCompletion");
        }
    }
    

第二种:拦截所有的控制器方法

  1. 创建一个控制器类添加注解
    @Component
    public class FirstInterceptor implements HandlerInterceptor {
        //控制器方法执行之前
        //功能是拦截:返回true表示放行,返回false表示拦截
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("FirstInterceptor--->preHandle");
            return true;//true表示放行请求
        }
        //控制器方法执行之后
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("FirstInterceptor--->postHandle");
        }
        //视图渲染完成之后
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("FirstInterceptor--->afterCompletion");
        }
    }
    
  2. SpringMVC配置文件中配置
    <!--拦截器-->
    <mvc:interceptors>
        <!--所在包需要被扫描到-->
        <ref bean="firstInterceptor"></ref>
    </mvc:interceptors>
    

第三种:拦截部分控制器的方法

<!--拦截器-->
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/*"/><!--拦截路径:一个*表示一层路径;**表示所有路径-->
        <mvc:exclude-mapping path="/"/><!--排除需要拦截的路径(即:不需要拦截的路径)-->
        <bean class="love.junqing.controller.FirstInterceptor"></bean><!--bean 和 ref 标签都行-->
    </mvc:interceptor>
</mvc:interceptors>

2.拦截器的三个抽象方法

preHandle:控制器方法执行之前拦截,返回true表示放行,就会调用控制器方法,返回false表示拦截,就不会调用控制器方法

postHandle:控制器方法执行完成之后

afterCompletion:视图渲染完成之后

3.多个拦截器的执行顺序

  • 若每个拦截器的preHandle()都返回true

    拦截器的执行顺序与在SpringMVC的配置文件的配置顺序有关:

    1. preHandle()会按照配置的顺序执行

    2. postHandle()和afterComplation()会按照配置的反序执行

  • 若某个拦截器的preHandle()返回了false
    1. preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行

    2. 返回false的拦截器之前的拦截器的afterComplation()会执行

十二、异常处理器

SpringMVC提供的一个处理控制器方法执行过程中出现的异常的接口:HandlerExceptionResolver

接口的实现类有两个:

  • DefaultHandlerExceptionResolver(默认异常处理类(返回一个新的ModelAndView))
  • SimpleMappingExceptionResolver(自定义异常处理类(可以实现自己的功能))

1.基于配置的异常处理

  1. 配置SpringMVC,添加一个bean标签

    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
            <property name="exceptionMappings">
                <props><!--键值对注入数据-->
                    <!--key:是异常的全类名-->
                    <!--里面的值是产生异常后跳转的视图(遵循视图创建规则)-->
                    <prop key="java.lang.ArithmeticException">error</prop>
                </props>
            </property>
            <!--将异常信息保存到请求域中(可以不设置),前端界面用ex作为键输出-->
            <property name="exceptionAttribute" value="ex"></property>
        </bean>
    
  2. 前端页面

    <a th:href="@{/test}">数学运算异常</a>
    
  3. 后端控制器(故意产生一个数学运算错误)

    @RequestMapping("/test")
    public String test(){
        System.out.println(1/0);//故意抛出错误
        return "success";
    }
    
  4. 发生异常后跳转的界面(error界面)

    <body>
    运算错误
    <!--输出域中的异常信息-->
    <p th:text="${ex}"></p>
    </body>
    

2.基于注解的异常处理

  1. 创建一个类添加@ControllerAdvice
  2. 创建方法添加@ExceptionHandler(value值是异常的class对象)
    @ControllerAdvice
    public class ExceptionController {
        @ExceptionHandler(value = {ArithmeticException.class,NullPointerException.class})
        public String testException(Exception ex, Model model){//Exception 异常信息 ,model用来往请求域中添加数据
            model.addAttribute("ex",ex);//将异常信息放到请求域中
            return "error";//当发生异常后跳转的界面
        }
    }
    

十三、注解配置SpringMVC

使用配置类和注解代替web.xml和SpringMVC配置文件

当我们的类扩展了AbstractAnnotationConfigDispatcherServletInitializer并将其部署到**Servlet3.0容器**的时候,容器会自动发现它,并用它来配置Servlet上下文。

1.创建一个类继承AbstractAnnotationConfigDispatcherServletInitializer类,实现他的三个方法

public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
    /**
     * 指定Spring的配置类
     * @return
     */
    protected Class<?>[] getRootConfigClasses() {
        return new Class[0];
    }
    /**
     * 设置SpringMVC的配置类
     * @return
     */
    protected Class<?>[] getServletConfigClasses() {
        return new Class[0];
    }
    /**
     * 指定DispatcherServlet的映射规则,即url-pattern(拦截路径)
     * @return
     */
    protected String[] getServletMappings() {
        return new String[0];
    }
}

2.创建SpringConfig类,使用@Configuration注解

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

3.修改初始化类的getRootConfigClasses方法,将Spring配置类放进数组

protected Class<?>[] getRootConfigClasses() {
    return new Class[]{SpringConfig.class};
}

4.创建拦截器类,实现HandlerInterceptor接口,实现三个方法

public class TestInterceptor implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("testpreHandle");
        return true;
    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

5.创建SpringMVC的配置类

/**
 * SpringMVC的配置文件需要的功能:
 * 1.扫描组件 2.视图解析器 3.view-controller(不需要处理的页面跳转) 4.default-servlet-handler(静态资源处理)
 * 5.mvc注解驱动 6.文件上传解析器 7.异常处理 8.拦截器
 */
@Configuration//配置类的注解
@ComponentScan("love.junqing.controller")//开启注解扫描
@EnableWebMvc//开启mvc注解驱动()
public class SpringMVCConfig implements WebMvcConfigurer {//实现这个接口用来实现其他插件
    /**
     * 4.default-servlet-handler(静态资源处理)
     */
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    /**
     * 8.拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        TestInterceptor testInterceptor = new TestInterceptor();//创建拦截器对象
        registry.addInterceptor(testInterceptor).addPathPatterns("/**");//添加拦截路径(/** 是所有请求 /* 是拦截一层)
        //excludePathPatterns() 排除拦截路径
    }

    /**
     * 3.view-controller(不需要处理的页面跳转)
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/success").setViewName("success");//当访问/suceess路径时访问success界面
    }

    /**
     * 6.文件上传解析器
     */
    public MultipartResolver multipartResolver(){
        CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
        return commonsMultipartResolver;
    }

    /**
     * 7.异常处理
     */
    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
        SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
        Properties properties = new Properties();
        properties.setProperty("java.lang.ArithmeticException","error");//第一个参数时异常类型,第二个参数时跳转页面
        simpleMappingExceptionResolver.setExceptionMappings(properties);
        simpleMappingExceptionResolver.setExceptionAttribute("ex");//将异常信息放到ex中,控制器可以将ex作为键获取信息
        exceptionResolvers.add(simpleMappingExceptionResolver);
    }

    /**
     * 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;
    }
} 

6.修改初始化类的getServletConfigClasses方法,将SpringMVC的配置类放进去(和3一样)

7.将DispatcherServlet的拦截路径放进去

protected String[] getServletMappings() {
    return new String[]{"/"};
}

8.设置过滤器(重写方法)

@Override
protected Filter[] getServletFilters() {
    //1.编码过滤器
    CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
    characterEncodingFilter.setEncoding("utf-8");//设置编码格式
    characterEncodingFilter.setForceRequestEncoding(true);//设置响应编码
    //2.隐藏域过滤器(用来实现delete和put方法)
    HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();

    return new Filter[]{characterEncodingFilter,hiddenHttpMethodFilter};
}

十四、SpringMVC执行流程

1、SpringMVC常用组件

  • DispatcherServlet前端控制器不需要工程师开发,由框架提供

    作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求

  • HandlerMapping控制器映射器,不需要工程师开发,由框架提供

    作用:根据请求的url、method等信息查找Handler(将请求和方法进行映射),即控制器方法

  • Handler控制器,需要工程师开发

    作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理

  • HandlerAdapter控制器适配器,不需要工程师开发,由框架提供

    作用:通过HandlerAdapter对处理器(控制器方法)进行执行

  • ViewResolver视图解析器,不需要工程师开发,由框架提供

    作用:进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectView

  • View视图

    作用:将模型数据通过页面展示给用户

2、DispatcherServlet初始化过程

DispatcherServlet本质就是一个Servlet,所以遵循Servlet的生命周期

1.初始化WebApplicationContext

protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext =
        WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                // The context has not yet been refreshed -> provide services such as
                // setting the parent context, setting the application context id, etc
                if (cwac.getParent() == null) {
                    // The context instance was injected without an explicit parent -> set
                    // the root application context (if any; may be null) as the parent
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // No context instance was injected at construction time -> see if one
        // has been registered in the servlet context. If one exists, it is assumed
        // that the parent context (if any) has already been set and that the
        // user has performed any initialization such as setting the context id
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        // 创建WebApplicationContext
        wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        // Either the context is not a ConfigurableApplicationContext with refresh
        // support or the context injected at construction time had already been
        // refreshed -> trigger initial onRefresh manually here.
        synchronized (this.onRefreshMonitor) {
            // 刷新WebApplicationContext
            onRefresh(wac);
        }
    }

    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        // 将IOC容器在应用域共享
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}

2.创建WebApplicationContext

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    Class<?> contextClass = getContextClass();
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException(
            "Fatal initialization error in servlet with name '" + getServletName() +
            "': custom WebApplicationContext class [" + contextClass.getName() +
            "] is not of type ConfigurableWebApplicationContext");
    }
    // 通过反射创建 IOC 容器对象
    ConfigurableWebApplicationContext wac =
        (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    // 设置父容器
    wac.setParent(parent);
    String configLocation = getContextConfigLocation();
    if (configLocation != null) {
        wac.setConfigLocation(configLocation);
    }
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

3.DispatcherServlet初始化策略

FrameworkServlet创建WebApplicationContext后,刷新容器,调用onRefresh(wac),此方法在DispatcherServlet中进行了重写,调用了initStrategies(context)方法,初始化策略,即**初始化DispatcherServlet的各个组件**

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

3、DispatcherServlet调用组件处理请求

1.processRequest()

FrameworkServlet重写HttpServlet中的service()和doXxx(),这些方法中调用了processRequest(request, response)

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {

    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;

    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);

    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    initContextHolders(request, localeContext, requestAttributes);

    try {
		// 执行服务,doService()是一个抽象方法,在DispatcherServlet中进行了重写
        doService(request, response);
    }
    catch (ServletException | IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }

    finally {
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }
        logResult(request, response, failureCause, asyncManager);
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

2.doService()

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    logRequest(request);

    // Keep a snapshot of the request attributes in case of an include,
    // to be able to restore the original attributes after the include.
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
        attributesSnapshot = new HashMap<>();
        Enumeration<?> attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {
            String attrName = (String) attrNames.nextElement();
            if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
    }

    // Make framework objects available to handlers and view objects.
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, 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 {
        // 处理请求和响应
        doDispatch(request, response);
    }
    finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
        if (requestPath != null) {
            ServletRequestPathUtils.clearParsedRequestPath(request);
        }
    }
}

3.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 {
        ModelAndView mv = null;
        Exception dispatchException = null;

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

            // Determine handler for the current request.
            /*
            	mappedHandler:调用链
                包含handler、interceptorList、interceptorIndex
            	handler:浏览器发送的请求所匹配的控制器方法
            	interceptorList:处理控制器方法的所有拦截器集合
            	interceptorIndex:拦截器索引,控制拦截器afterCompletion()的执行
            */
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
           	// 通过控制器方法创建相应的处理器适配器,调用所对应的控制器方法
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }
			
            // 调用拦截器的preHandle()
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            // 由处理器适配器调用具体的控制器方法,最终获得ModelAndView对象
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(processedRequest, mv);
            // 调用拦截器的postHandle()
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        // 后续处理:处理模型数据和渲染视图
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                               new NestedServletException("Handler processing failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

4.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) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

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

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

    if (mappedHandler != null) {
        // Exception (if any) is already handled..
        // 调用拦截器的afterCompletion()
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

4、SpringMVC的执行流程

  1. 用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获。
  2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:
    • 不存在
      1. 再判断是否配置了mvc:default-servlet-handler
      2. 如果没配置,则控制台报映射查找不到,客户端展示404错误
      3. 如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示404错误
    • 存在
      1. 根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。
      2. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。
      3. 如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法【正向】
      4. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:

        a) HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息

        b) 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等

        c) 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等

        d) 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中

      5. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象。
      6. 此时将开始执行拦截器的postHandle(…)方法【逆向】。
      7. 根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。
      8. 渲染视图完毕执行拦截器的afterCompletion(…)方法【逆向】。
      9. 将渲染结果返回给客户端。

十五、SpringMVC快速使用总结

1.请求路径的设置

@RequestMapping注解:如果放在方法上用于设置当前控制器方法的请求访问路径,如果放在类上用于设置当前控制器的请求访问前缀路径

2.请求参数的接收【非json类型】

  1. 无论是GET请求还是POST请求,普通参数可以直接在方法里面接收【只要方法的参数名和前端传递的参数名一样就行】

    //例如:
    public void getParam(String name,String password){}//这里的意思就是传过来了name和password
    

    这里说的是名称一样不加注解可以直接接收,但是如果名字不一致那么不加注解就不会被接收到

  2. 如果前后端的参数名不一致可以使用@RequestParam注解,他的值是前端的参数名。

    //例如:前端传过来的参数是username,而我们方法中的参数名是name
    public void getParam(@RequestParam("username")String name,String password){}//这个意思就是前端的username参数用name接收
    
  3. 如果前端的属性名和后端方法中对象的属性一样,那么可以自动注入

    //例如:
    public void getParam(User user){}//前后端参数名一样的话,参数可以自动注入到user对象的属性中
    
  4. 如果数据需要用数组接收那么直接收就行,可是如果数据需要用List集合接收就需要@RequestParam注解

    //例如:
    public void getParam(String [] likes){}//可以直接接收,但是要求前端传过来的参数也是likes
    //例如:
    public void getParam(List<String> likes){}//这个就是错误的,因为他不知道,需要用下面的方法
    public void getParam(@RequestParam List<String> likes){}//这个就可以接前端的likes集合
    

3.请求参数的接收【json类型】

后端想要接收前端的json数据,必须要有jackson依赖,同时需要在配置类上使用@EnableWebMvc注解开启json转java对象【SpringMVC才这么做】

因为数据是请求体中的,所以方法的参数上直接加@RequestBody注解就可以【对象和集合都一样】

//例如:
public void getParam(@RequestBody User user){}//这个意思就是接收前端传过来的json数据直接封装到对象中【前端的参数和后端的对象的属性名一致】

4.请求参数的接收【日期类型参数】

  1. 日期类型的参数如果是标准格式可以直接用Date类型接收【只需要前后端名字一致】【标准类型格式(就是中间用斜杠分开):2000/10/03】

  2. 如果是非标准类型的时间可以使用@DateTimeFormat注解,然后设置pattern属性

    //例如:
    public void getParam(@DateTimeFormat(pattern="yyyy-MM-dd") Date date){}//这样就可以接收前端以-分开的时间date
    
    public void getParam(@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") Date date){}//这样就可以接收年月日时分秒
    

5.响应数据给前端

@ResponseBody注解的作用:设置当前控制器方法的返回值作为响应体【集合、对象会自动转为Json】

  • 响应页面:如果后端需要将页面响应给前端,那么就直接返回页面,方法的返回类型是String

    说明:不要加@ResponseBody注解或者@RestController注解

    //例如:	
    @RequestMapping("/index")
    public String toJump(){//返回页面一定不要加@ResponseBody或者@RestController注解
    	return "index.jsp";
    }
    
  • 响应字符串:如果需要给前端响应字符串,需要加@ResponseBody,不然会认为返回页面

    //例如:
    @RequestMapping("/text")
    @ResponseBody		//返回数据要加@ResponseBody
    public String toJump(){
    	return "hello world";
    }
    
  • 响应Json数据:如果需要给前端响应json数据,只需要加@ResponseBody,然后将数据封装到pojo中返回即可【得有jackon的坐标】

     ```java
     //例如:
     @RequestMapping("/json")
     @ResponseBody		//返回json数据要加@ResponseBody
     public User toJump(){
     	return new User();	//集合类型的也是这样
     }
     ```
    

6.Rest风格

  1. 传统风格资源描述形式:http://localhost/user/getById?id=1
    REST风格的描述形式:http://localhost/user/1

  2. 按照REST风格访问资源

    http://localhost/users		//查询全部用户信息 GET
    http://localhost/users/1	//查询指定用户信息 GET
    http://localhost/users		//添加用户信息 POST
    http://localhost/users		//修改用户信息 PUT
    http://localhost/user/1		//删除用户信息DELETE
    
  3. 接收路径中的参数:如果参数直接以/的方式拼接到了路径中,那么我们后端的请求路径要用占位符,同时方法的参数列表要用@PathVariable注解。

    说明:占位符需要和参数列表中的参数名一样

    //例如:前端的请求路径http://localhost/users/1,参数是1
    //后端的方式就是:
    @GetMapping("/users/{id}")	//占位符需要和参数列表中参数的名一致
    public void getUser(@PathVariable Integer id){//参数需要用@PathVariable注解
        
    }
    
  4. REST风格引出的派生注解

    @RestController  //是@Controller和@ResponseBody的合成注解
    @GetMapping
    @PutMapping
    @PostMapping
    @DeleteMapping
    

7.资源控制器

资源拦截器可以拦截资源请求,例如访问某个资源是否让访问。

  • 使用方法

    1. 继承WebMvcConfigurationSupport,然后使用@Configuration注解将类加入到容器中

    2. 然后重写addResourceHandlers方法

    3. 调用registry对象的addResourceHandler方法设置拦截路径

    4. 调用registry对象的addResourceLocations方法设置映射路径

  • 使用案例:访问/page下的所有页面的时候,你就去对应的地方找

@Configuration
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    	registry.addResourceHandler("/page/**").//拦截的请求
            addResourceLocations("/page/");//让他去/page/下面找
    }
}

8.拦截器(Interceptor)

  • 拦截器的作用:是一种动态拦截方法调用的机制,可以在指定方法调用的前后执行预先设定的方法,能够阻止原始方法的执行

  • 拦截器和过滤器的区别:过滤器能够对所有的访问进行拦截,而拦截器只能针对于SpringMVC的访问进行拦截

  • 拦截器的创建方法

    1. 创建一个类,实现HandlerInterceptor接口(这个接口有三个方法:前置拦截,后置拦截,和最终拦截
    2. 根据需要实现其中的方法,然后返回true放行,返回false终止访问。
    3. 然后在资源拦截器中重写addInterceptors方法,用来设置设置资源拦截器拦截的路径。
    4. 然后将拦截器对象注入到资源拦截器中。
    5. 调用registry的addInterceptor方法指定是为哪个拦截器配置的,然后调用addPathPatterns方法指定拦截路径(这一步一个方法里可以指定多个registry的addInterceptor)
    6. 最后还可以调用excludePathPatterns方法执行排除哪些路径
  • 使用案例:JWT为例

    @Component
    public class JwtTokenUserInterceptor implements HandlerInterceptor {
        @Autowired
        private JwtProperties jwtProperties;
        /**
         * 前置校验:校验jwt
         */
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            //判断当前拦截到的是Controller的方法还是其他资源
            if (!(handler instanceof HandlerMethod)) {
                //当前拦截到的不是动态方法,直接放行
                return true;
            }
            //1、从请求头中获取令牌
            String token = request.getHeader(jwtProperties.getUserTokenName());
            //2、校验令牌
            try {
                log.info("jwt校验:{}", token);
                Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
                Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
                log.info("当前用户的id:{}", userId);
                BaseContext.setCurrentId(userId);
                //3、通过,放行
                return true;
            } catch (Exception ex) {
                //4、不通过,响应401状态码
                response.setStatus(401);
                return false;
            }
        }
    }
    
    @Configuration
    @Slf4j
    public class WebMvcConfiguration extends WebMvcConfigurationSupport {
        @Autowired
        private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;//注入拦截器对象
        /**
         * 注册自定义拦截器
         * @param registry
         */
        protected void addInterceptors(InterceptorRegistry registry) {
            log.info("开始注册自定义拦截器...");
            registry.addInterceptor(jwtTokenAdminInterceptor)
                    .addPathPatterns("/admin/**")//作用于哪些路径
                    .excludePathPatterns("/admin/employee/login");//不拦截哪些路径
        }
    }
    

9.异常处理

9.1异常出现的常见位置和原因:

  • 框架内部抛出的异常:因使用不合规导致

  • 数据层抛出的异常:因外部服务器故障导致(如:服务器访问超时)

  • 业务层抛出的异常:因业务逻辑书写错误导致(如:用户传了一个0过来)

  • 表现层抛出的异常:因数据收集、校验等规则导致

  • 工具类抛出的异常:因工具类书写不规范

9.2异常处理的思路

因为异常可能不处于同一层,所以出现异常都往上抛,都抛出到表现层,然后统一处理

9.3异常处理器:

可以集中的、统一的处理项目中出现的异常

  • 异常处理器的使用方法

    1. 先创建一个异常处理类,然后类上添加@RestControllerAdvice注解
    2. 然后写异常处理的方法,方法的参数是Exception(可以是Exception的子类)的class对象表示处理的异常。
    3. 方法上加@ExceptionHandler告诉方法你处理的是哪一种异常。
    4. 方法体中处理过后也要返回Result对象。
  • 使用案例

    @RestControllerAdvice
    @Slf4j
    public class GlobalExceptionHandler {
    	//使用方法一
        @ExceptionHandler
        public Result exceptionHandler(BaseException ex){
            log.error("异常信息:{}", ex.getMessage());
            return Result.error(ex.getMessage());
        }
        //使用方法二
        @ExceptionHandler(Excecption.class)
    	public Result exceptionHandler(Exception ex){
    		return Result.error(ex.getMessage());
    	}
    }
    

9.4项目异常分类:

  1. 业务异常:规范的用户行为出现的异常和不规范的用户行为出现的异常

    处理方法:发送对象的消息给用户,提醒规范操作

  2. 系统异常:项目运行过程中可预计且无法避免的异常

    处理方法:发送固定的消息给用户,安抚用户,然后将消息发送给运维人员,提醒维护,同时记录日志

  3. 其他异常:编程人员未预期到的异常

    处理方法:发送固定消息传递给用户,安抚用户,然后将消息发送给编程人员,提醒维护,同时记录日志

9.5自定义异常的方法:

  1. 先创建一个异常类,继承RuntimeException【因为他的异常都可以自动往上抛】,

  2. 然后添加一个code属性作为异常编号,方便后面识别异常类型。

  3. 然后直接快捷键重写构造方法【需要几种生成几种】

  • 使用案例:

    public class BaseException extends RuntimeException {
        //这里给了两个构造器
        public BaseException() {
        }
    
        public BaseException(String msg) {
            super(msg);
        }
    
    }
    

9.6自定义异常的使用方法:

直接抓异常throw自定义异常,然后配合异常处理器处理异常【给用户发消息,通知维护人员,记录日志】

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值