Spring MVC

Posted yanquhonggui

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring MVC相关的知识,希望对你有一定的参考价值。

Spring MVC

一、Spring MVC流程

Spring MVC的流程是围绕 DispatcherServlet 而工作的,所以在 Spring MVC 中 DispatcherServlet 就是其最重要的内容 。 在 DispatcherServ let 的基础上,还存在其他的组件, 掌握流程和组件就是 SpringMVC 开发的基础。 关于Spring MVC的流程如下图所示

技术图片

首先,在 Web 服务器启动的过程中,如果在 Spring Boot 机制下启 用 Spring MVC , 它就开始初始化一些重要的组件,如 DispactherServlet、 HandlerAdapter 的实现类 RequestMappingHandlerAdapter等组件对 象 。 关于这些组件的初始化,我们可以 看到 spring-webmvc-xxx.jar 包的属性文件DispatcherServlet.properties, 它定义的对象都是在 SpringMVC 开始时就初始化,并且存放在 Spring IoC容器中

其次是开发控制器(Controller)

package com.demo.controller

@Controller
@RequestMapping("/user")
public class UserController
    @AutoWired
    private UserService userService=null;
    
    @RequestMapping("details")
    public ModelAndView details(Long id)
        User user=Uservice.getUser(id);
        ModelAndView mv=new ModelAndView();
        mv.setViewName("user/details");
        mv.addObject("user",user);
        return mv;
    

@Controller 表明这是一个控制器,然后@RequestMapping 代表请求路径和控制器(或其方法)的映射关系,它会在 Web 服务器启动 Spring MVC 时,就被扫描到 HandlerMapping 的机制中存储,之后在用户发起请求被 DispatcherServlet拦截后,通过 U阳和其他的条件 , 通过 HandlerMapper机制就能找到对应的控制器(或其方法)进行响应 .是通过 HandlerMapping 返回的 是一个HandlerExecutionChain 对象

HandlerExecutionChain对象包含一个处理器(handler),这里的处理器是对控制器(controller)的包装,因为我们的控制器方法可能存在参数,那么处理器就可以读入HTTP和上下文的相关参数,传递给控制器方法。而在处理器包含了控制器方法的逻辑。此外还有处理器的拦截器(interceptor),这样就能够通过拦截器进一步的增强处理器的功能。

得到了处理器( handler ),还需要去运行,但是我们有普通 HTTP 请求,也有按 BeanName 的请求,甚至是 WebSocket 的请求,所以它还需要一个适配器去运行 HandlerExecutionChain 对象包含的处理器,这就是 HandlerAdapter 接口定义的实现类 。HttpRequestHandlerAdapter 是最常用的 HandlerAdapter 的实现类。通过请求的类型,DispatcherServlet 就会找到它来执行 Web 请求的 HandlerExecutionChain 对象包含的内容,这样就能够执行我们的处理器( handler)了 。

在处理器调用控制器时,它首先通过模型层得到数据,再放入数据模型中,最后将返回模型和视图( ModelAndView )对象,这里控制器设置的视图名称设置为“ user/details”,这样就走到了视图解析器( ViewResolver ),去解析视图逻辑名称了。

可以在application.properites.进行配置。

spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

通过修改这样的配置,就能在 Spring Boot 的机制下定制InternalResourceViewResolver 这个视图解析器的初始化,也就是在返回视图名称之后,它会以前缀( prefix )和后缀( suffix )以及视图名称组成全路径定位视图 。视图解析器定位到视图后,视图的作用是将数据模型( Model )渲染,这样就能够响应用户的请求。这一步就是视图将数据模型植染( View )出来,用来展示给用户查看。按照我们控制器的返回,就是/WEB-INF/jsp/user/details .jsp 作为我们的视图

<% @ page pageEncoding="UTF- 8" %>
<% @ taglib prefix="c" uri= "http://java.sun.com/jsp/jstl/core"%>
<html>
    <head>
        <title >用户详情</title>
    </head>
    <body>
        <center>
            <table border=”1 ” >
                <tr>
                    <td>标签</td>
                    <td>值</td>
                </tr>
                <tr>
                    <td>用户编号</td>
                    <td><c:out value="$user.id"></ c : out></td>
                </tr>
                <tr>
                    <td>用户名称</td>
                    <td><c:out value="$user.userName"></c:out></td>
                </tr>
                <tr>
                    <td>用户备注</td>
                    <td><c:out value="$user.note"></c:out></td>
                </tr>
            </table>
        </center>
    </body>
</html>

spring boot启动文件

@SpringBootApplication(scanBasePackages="com.demo")
@MappperScann(basePackages="com.demo",
    annotationClass=Repository.class)
public class DemoApplication
    public static void main(String []args)
        SpringApplication.run(DemoApplication.class,args);
    

二、处理器映射器

果 Web 工程使用 了 Spring MVC , 那么它在启动阶段就会将注解@RequestMapping 所配置的 内 容保存到处理器映射( HandlerMapping ) 机制 中去 , 然后等待请求的到来,通过拦截请求信息与 HandlerMapping 进行匹配,找到对应的处理器(它包含控制器的逻辑) , 并将处理器及其拦截器 保 存 到 HandlerExecutionChain 对 象中 , 返回给 DispatcherServlet ,这样DispatcherServlet 就可以运行它们 了。 从论述 中可以看 到, HandlerMapping 的主要任务是将请求定位到具体的处理器上 。

@RepuestMapping的源码

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.web.bind.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target(ElementType.TYPE, ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping 
    //配置请求映射名称
    String name() default "";
    
    //通过路径映射
    @AliasFor("path")
    String[] value() default ;
    
    //通过路径映射回path配置项
    @AliasFor("value")
    String[] path() default ;
    
    //限定只响应HTTP请求类型,如GET、POST、HEAD、OPTIONs、PUT、TRACE等
    //默认的情况下,可以相应所有的请求类型
    RequestMethod[] method() default ;

    //等存在对应的HTTP参数时才响应请求
    String[] params() default ;

    //限定请求头存在对应的参数才响应
    String[] headers() default ;
    
    //限定HTTP请求提交类型,如“application/json”,"text/html"
    String[] consumes() default ;
    
    //限定返回类型,仅当HTTP请求头中的(Accept)类型中包含指定参数类型时才返回
    String[] produces() default ;

  • value和path来设置请求的URL
  • method限定HTTP的请求类型,为简化method配置项的配置,新增了@GetMapping 、@PostMapping、@PatchMappiing、@PutMapping、@DeleteMapping。可以看出@GetMapping对应的是HTTP的GET方法,@PostMapping 对应的是 Hπp 的 POST 方法。

三、获取控制器参数

处理器是对控制器的包装 ,在处理器运行的过程中会调度控制器的方法,只是
它在进入控制器方法之前会对 HTTP 的参数和上下文进行解析,将它们转换为控制器所需的参数。

1.在无注解下获取参数

在没有注解的情况下,springMVC 也可以获取参数,且允许参数为空,唯一的要求是参数名称和HTTP请求的参数名称保持一致。

package com.demo.cotroller;

@RequestMapping("/my")
@Controller
public class MyController
    @GetMapping("/no/annotation")
    @ResponseBody
    public Map<String,Object> noAnnotation(Integer intVal,Long longVal,String str)
        Map<String,Object> paramsMap=new HashMap();
        paramsMap.put("intVal",intVal);
        paramsMap.put("longVal",longVal);
        paramsMap.put("str",str);
        return paramsMap;
    

浏览器输入

http : //localhost : 8080lmylnolannotation ?intVal=10&longVal=200

从代码中可以看出控制器方法参数中还有一个字符串参数 str,但因为参数在默认的规则下可以为空 ,所以这个请求并不会报锚,因为方法标注了@ResponseBody ,所以控制器返回的结果就会转化为 JSON 数据集。

2.使用@RequestParam获取参数

SpringMVC提供了@RequestParam来确定前后端参数的映射关系

package com.demo.cotroller;

@RequestMapping("/my")
@Controller
public class MyController
    @GetMapping("/annotation")
    @ResponseBody
    public Map<String,Object> requestParam(
        @RequestParam("int_val")Integer intVal,
        @RequestParam("long_val")Long longVal,
        @RequestParam("str_val")String str)
        Map<String,Object> paramsMap=new HashMap();
        paramsMap.put("intVal",intVal);
        paramsMap.put("longVal",longVal);
        paramsMap.put("str",str);
        return paramsMap;
    

在浏览器地址栏输入

http://localhost:8080/my/annotation?int_val=l & long_va1=2 & str_val=str

就能够看到请求的结果了 。 但如果把 3 个 HTTP 参数中的任意一个删去,就会得到异常报锚的信息,因为在默认的情况下@RequestParam 标注的参数是不能为空的

3.传递数组

SpringMVC中可以传递数组

@GetMapping("/requestArray")
@ResponseBody
public Map<String,Object> requestArray(
    int[] intarr,
    Long[] longArr,
    String[] strArr)
    Map<String,Object> paramsMap=new HashMap();
    paramsMap.put("intarr",intarr);
    paramsMap.put("longArr",longArr);
    paramsMap.put("strArr",strArr);
    return paramsMap;
h忧p ://localhost:8080/my/requestArray?intArr= l ,2,3& longArr=4 , 5,6&strr= str 1 ,str2,str3 

可以 看 到 需要传递数组参数时, 每个参数的数组元素只需要通过逗号分隔即可 。

4.传递JSON(@RequestBody)

在当前前后端分离 的趋势下 ,使用 JSON 已经是十分普遍了 。 对于前端的页面或者于机应用,可以通过请求后端获取 JSON 数据集,这样它们就能很方便地将数据渲染到视图中 。 有时前端也需要提交较为复杂的数据到后端,为了更好组织和提高代码的可读性 , 可 以将数据转换为 JSON 数据集 ,通过 HTTP 请求体提交给后端 , 对此 SpringMVC 也提供了良好的支持 。

先搭建一个表单

<%@ page pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <heand>
        <meta charset="UTF-8">
        <title>新增用户</title>
        <script src="https://code.jquery.com/jquery-3.2.0"></script>
        <script type="text/javascript">
            $(document).ready(function()
                $("#submit").click(function()
                    var userName=$("#userName").val();
                    var note=$("#note").val();
                    if($.trim(userName)=='')
                        alert("用户名不能为空")
                        return;
                    
                    var params=
                        userName:userName,
                        note:note
                    ;
                    $.post(
                        url="./insert",
                        contentType:"application/json";
                        data:JSON.Stringify(params),
                        successs:function(result)
                            if(result==null||result==null)
                                alert("插入失败")
                                return;
                            
                            alert("插入成功")
                        
                    )
                );
            );
        </script>
    </heand>
    <body>
        <div style="margin:20px 0;"></div>
        <form id="insertForm">
            <table>
                <tr>
                    <td>用户名称</td>
                    <td><input id="userName" name="userName"</td>
                </tr>
                <tr>
                    <td>备注</td>
                    <td><input id="note" name="note"</td>
                </tr>
                <tr>
                    <td>用户名称</td>
                    <td align=right><input id="submit" type="button" value="提交"</td>
                </tr>
            </table>
        </form>
    </body> 
</html>

这里定义了 一个简易的表单,它使用了 jQuery 进行 Ajax 提交。注意到加粗的代码,它指定了提交的请求地址 Curl )、数据( data )、提交类型( contentType )和事后事件( success ) 。 从脚本来看,这里先组织了一个 JSON 数据集, 而且把提交类型也设置为了 JSON 类型 ,然后才提交到控制器。这样控制器就可以得到一个 JSON 数据集的请求体了

为了打开这个表单,需要在 UserController 中编写一个 add 方法,它将返回一个字符串 , 映射到这个表单上,这样就能通过视图解析器( ViewResolver)找到它了 。然后再写一个相应新增用户的请求 insert 方法,它将从 HTTP 请求体中读出这个 JSON

package com.deml.controller

@Controller
@RequestMapping("/user")
public class UserController
    @Autowired
    private UserService userService=null;
    
    @GetMapping("/add")
    public String add()
        return "/user/add";
    
    
    @PostMapping("/insert")
    @ResponseBody
    public User insert(@RequestBody User user)
        userService.insettUser(user);
        return user;
    

接着录入表单 ,点击提交按钮, 这样通过 JavaScript 脚本提交 JSON 消息 , 就可以请求到控制器的 insert 方法 。 这个方法的参数标注为@RequestBody , 意味着它将接收前端提交的 JSON 请求体,而在 JSON 请求体与 User 类之间的属性名称是保持一致的,这样 Spring MVC 就会通过这层映射关系将 JSON 请求体转换为 User 对象

@RequestBody 标注在参数上,表示接收的是前端的JSON请求,同时会实现Json请求体到实参的转化

5.通过URL传递参数(@PathVariable)

SpringMVC 对此也提供 了 良好的支持 ,可以通过处理器映射和注解@PathVariable 的组合获取 URL 参数。首先通过处理器映射可以定位参数的位置和名称,而@PathVariable 则可以通过名称来获取参数

@GetMapping("/id")
@ResponseBody
public User get(@PathVariable("id")Long id)
    return userService.getUser(id);

代码中首先通过@GetMapping 指定一个 URL , 然后用 {...}来标明参数的位置和名称。这里指定名称为 id , 这样 Spring MVC 就会根据请求去匹配这个方法。@PathVariable 配置的字符串为 id

6.获取格式化数据

在一些应用中往往需要格式化数据,其中最为典型的当属日期和货币。

springMVC也对此提供了支持@DataTimeFormat和@NumberFormat

@GetMapping ("/format/form")
public String showFormat () 
    return "/format/formatter";


//获取提交参数
@PostMapping( "/format/commit")
@ResponseBody
public Map<String,Object> format(
    @DateTimeFormat(iso=ISO.DATE) Date date ,
    @NumberFormat(pattern ="# ,### .##") Double number ) 
    Map<String , Object> dataMap =new HashMap<>() ;
    dataMap .put ("date", date) ;
    dataMap . put ("number", number) ;
    return dataMap;
 

四、自定义参数转换规则

SpringMVC 提供的处理器会先以一套规则来实现参数的转换,在开发自定义转换规则时,就很有必要掌握这套转换规则了 。 而实际上处理器的转换规则还包含控制器返回后的处理,只是这节先讨论处理器是如何获取和转换参数的内容,其他的则留到后面再讨论,到时会揭开为什么使用注解@ResponseBody标注方法后,就能够把控制器返回转变为 JSON 数据集的秘密。

1.处理器获取参数逻辑

当一个请求来到时,在处理器执行的过程中,它首先会从HTTP请求上下文环境来得到参数。如果是简单的参数它会以简单的转发器进行转换,而这些简单的转发器是Spring MVC自身已经提供了的。但是如果是转化HTTP请求体(Body),它就会调用HttpMessageConverter接口的方法对请求体的信息进行转换。首先会先判断是否能对请求体进行转化,如果可以就会转化为Java类型

package org.springframework.http.converter;

import java.io.IOException;
import java.util.List;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;

public interface HttpMessageConverter<T> 
    //是否可读,其中var1为Java类型,var2为HTTP请求类型
    boolean canRead(Class<?> var1, @Nullable MediaType var2);

    //判断var1类型能否转化为var2类型,其中var1为Java类型,var2为HTTP请求类型
    boolean canWrite(Class<?> var1, @Nullable MediaType var2);

    //可支持的媒体类型列表
    List<MediaType> getSupportedMediaTypes();

    //当canRead验证通过后,读入HTTP请求信息
    T read(Class<? extends T> var1, HttpInputMessage var2) throws IOException, HttpMessageNotReadableException;
    
    //当canWrite方法验证通过后,写入响应
    void write(T var1, @Nullable MediaType var2, HttpOutputMessage var3) throws IOException, HttpMessageNotWritableException;

之前代码中控制器方法的参数标注了@RequestBody,所以处理器会采用请求体( Body )的 内容进行参数转换,而前端的请求体为 JSON 类型,所以首先它会调用 canRead 方法来确定请求体是否可读 。 如果判定可读后,接着就是使用 read 方法,将前端提交的用户 JSON 类型的请求体转换为控制器的用户( User ) 类参数,这样控制器就能够得到参数了。

上面的 HttpMessageConveter 接口只是将 HTTP 的请求体转换为对应的 Java 对象,而对于 HTTP参数和其他内容,还没有进行讨论

为了讨论自定义的参数规则,很有必要先了解处理器转换参数的过程 。 在 Spring MVC 中, 是通过 WebDataBinder 机制来获取参数的 ,它的主要作用是解析 HTTP 请求的上下文, 然后在控制器的调用之前转换参数并且提供验证的功能,为调用控制器方法做准备 。 处理器会从 HTTP 请求中读取数据,然后 通过 三 种接口来进行各类参数转换,这 三 种接口是 Converter 、 Formatter 和GenericConverter 。 在 Spring MVC 的机制中这三种接 口的实现类都采用了注册机的机制, 默认的情况下 SpringMVC 己经在注册机内注册了许多的转换器,这样就可以实现大部分的数据类型的转换 , 所以在大部分的情况下无须开发者再提供转换器,这就是在上述章节中可以得到整型( Integer ) 、 长整型( Long )、字符串 C String )等各种各样参数的原因。同样地,当需要自定义转换规则时, 只需要在注册机上注册自己的转换器就可以了

WebDataBinder 机制还有一个重要的功能,那就是验证转换结果。关于验证机制,后面会再讨论。有了参数的转换和验证,最终控制器就可 以得到合法的参数。得到这些参数后,就可以调用控制器的方法了 。下图展示的是 HTTP 请求体( Body )的消息转换全流程图。

技术图片

对于数据类型转换, SpringMVC 提供了 一个服务机制去管理,它就是 ConversionService 接口 。在默认的情况下,会使用这个接口的子类 DefaultFormattingConversionService 对象来管理这些转换类, 即注册机

在 Spring Boot 中还提供了特殊的机制来管理这些转换器。 Spring Boot 的 自动配置类 WebMvcAutoConfiguration 还定义了 一个内部WebMvcAutoConfigurationAdapter 其源码为

public void addFormatters(FormatterRegistry registry) 
    Iterator var2 = this.getBeansOfType(Converter.class).iterator();

    while(var2.hasNext()) 
        Converter<?, ?> converter = (Converter)var2.next();
        registry.addConverter(converter);
    

    var2 = this.getBeansOfType(GenericConverter.class).iterator();

    while(var2.hasNext()) 
        GenericConverter converter = (GenericConverter)var2.next();
        registry.addConverter(converter);
    

    var2 = this.getBeansOfType(Formatter.class).iterator();

    while(var2.hasNext()) 
        Formatter<?> formatter = (Formatter)var2.next();
        registry.addFormatter(formatter);
    

通过这个方法,可以看到在 Spring Boot 的初始化中 , 会将
对应用户自定义的 Converter、 Formatter 和 GenericConverter 的实现类所创建的 Spring Bean 自动地注册到 DefaultForma忧ingConversionService 对象中 。 这样对于开发者只需要自定义 Converter 、 Formatter和 GenericConverter 的接口 的 Bean, Spring Boot 就会通过这个方法将它们注册到 ConversionService对象中

2.一对一转换器(Converter)

Converter是一对一转发器,也就是从一种类转化为另一种类型.接口为

package org.springframework.core.convert.converter;

import org.springframework.lang.Nullable;

@FunctionalInterface
public interface Converter<S, T> 
    @Nullable
    //S为源类型,T为目标类型
    T convert(S var1);

自定义转换器

package com.demo.converter

@Component
public class StringToUserConverter implements Converter<String ,user>
    @Override
    public User convert(String userStr)
        User user=new User();
        String []strArr=userStr.split("-");
        Long id=Long.praseLong(strArr[0]);
        String userName=strArr[1];
        String note=StrArr[2];
        user.setId(id);
        user.setUserrName(userName);
        user.setNote(note);
        return user;
     

类标注为@Component,并且实现了 Converter 接口,这样 Spring 就会将这个类扫描并装配到 IoC 容器中 。 对于 Spring Boot,之前分析过它会在初始化时把这个类 自动地注册到转换机制中,所以注册这步并不需要人工再处理。 这里泛型指定为 String 和 User,这样 SpringMVC 就会通过 HTTP的参数类型(String)和控制器的参数类型( User)进行匹配,就可以从注册机制中发现这个转换类,这样就能够将参数转换出来.

### 3.GenericConverter 集合和数组转换

GenericConverter 是数组转换器。因为 Spring MVC 自身提供了 一些数组转换器,需要自定义 的并不多 ,所以这里只介绍 SpringMVC 自定义的数组转换器。假设需要 同时新增多个用户,这样便需要传递一个用户列表 C List<User>)给控制器。此时SpringMVC 会使用 StringToCollectionConverter 转换它,这个类实现了 GenericConverter 接 口,并且是 Spring MVC 内部己经注册的数组转换器 。 它首
先会把字符串用逗号分隔为一个个的子宇符串,然后根据原类型泛型为 String、目标类型泛型为 User类,找到对应的 Converter 进行转换,将子字符串转换为 User 对象

五、数据模型

在 Spring MVC 流程中,控制器是业务逻辑核心内 容 ,而控制器的核心内容之一就是对数据的处理 。 SpringMVC 全流程的学习,可以看到允许控制器自定义模型和视图( ModelAndView ),其中模型是存放数据的地方,视图则是展示给用户 。

数据模型的作用是绑定数据。为后面的视图渲染做准备。SpringMVC使用的模型接口和类进行探讨

技术图片

类 ModelAndView 中存在一个 Mode!Map 类型的属性, Mode!Map 继承
了 Linked.HashMap 类 , 所以它具备 Map 接口的一切特性,除此之外它还可以增加数据属性 。 在 SpringMVC 的应用中,如果在控制器方法的参数中使用 ModelAndView 、 Model 或者 ModelMap 作为参数类型, SpringMVC 会自动创建数据模型对象 .

package com.demo.controller;

@RequestMapping("/data")
@Controller
public class DataModelController
    // 注入用户服务类
    @Autowired
    private UserService userService=null;
    
    //Model
    @GetMapping("/model")
    public String useModel(Long id, Model model)
        User user=userService.getUser(id);
        model.addAttribute("user",user);
        return "data/user";
    
    
    //ModeMap
    @GetMapping("/modelMap")
    public ModelAndView userModelMap(Long id,ModelMap modelMap)
        User user =userService.getUser(id);
        ModelAndView mv=new ModelAndView();
        mv.setViewName("data/user");
        modelMap.put("user",user);
        return mv;
    
    
    //ModelAndView
    @GetMapping("/mav")
    public ModelAndView useModelAndView(Long id,ModelAndView mv)
        User user=userService.getUser(id);
        mv.addObject("user",user);
        mv.setViewName("data/user");
        return mv;
    

六、视图与视图解析器

视图是渲染数据模型展示给用户的组件 , 在 Spring MVC 中又分为逻辑视图和非逻辑视图。逻辑视图是需要视图解析器( ViewResolver ) 进行进一步定位的 。 例如, 之前的例子所返回 的字符串之所以能找到对应 的 JSP , 就是因为使用了逻辑视 图 , 经 由视图解析器 的定位后 ,才能找到视图将数据模型进行渲染展示给用户查看。对于非逻辑视图 ,则并不需要进一步地定位视图 的位置 , 它只需要直接将数据模型渲染出来即可

在实际的工作中视图解析器 Interna!ResourceViewResolver 是 比较常用的

1.视图设计

对于视图,除了JSON和JSP视图之外,还有其他类型的视图如Excel、PDF。他们都会实现Spring MVC 定义的视图接口View,其源码如图所示。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.web.servlet;

import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;

public interface View 
    //响应状态属性
    String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
    //路径变量
    String PATH_VARIABLES = View.class.getName() + ".pathVariables";
    //选择内容类型
    String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
    //相应类型
    @Nullable
    default String getContentType() 
        return null;
    
    //渲染方法
    void render(@Nullable Map<String, ?> var1, HttpServletRequest var2, HttpServletResponse var3) throws Exception;
  • getContentType 方法是获取 HTTP 响应类型的 ,它可以返回的
    类型是文本、 JSON 数据集或者文件等
  • render方法则是将数据模型渲染到视图的,这是视图的核心方法,在它 的参数中 , model 是数据模型,实际就是从控制器(或者由处理器自动绑定)返回的数据模型,这样 render 方法就可以把它渲染出来。渲染视图是比较复杂的过程,为了简化视图渲染的开发,在 SpringMVC 中已经给开发者提供了许多开发好的视图类 , 所以在大部分的情况下并不需要自己开发自己的视图
    技术图片

七、文件上传

1.Spring MVC对文件上传的支持

首先,DispatcherServlet会使用适配器模式,将HttpServletRequest接口转化为MultipartHttpServletRequest对象。MultipartHeepSrvletRequest接口扩展了HttpServletRequest接口的所有方法。而且定义了一些操作文件的方法。这样通过这些方法就可以实现对上传文件的操作

技术图片

对于文件上传的场景,spring MVC 将HttpServletRequest对象转化为MultipartHttpServletRequest对象。MultipartHttpServletRequest接口存在许多的方法用来处理文件。在使用时放需要配置Spring MVC上传文件时,还需要配置MultipartHeepServletRequest,这个任务时通过MultipartResolver接口实现的,它包含两个实现类StandardSrvletMultipartResolver和CommonsMultipartResolver,这两个类都可以实现文件上传,但是推荐使用StandardServletMultipartResolver.

在Spring boot 中如果你没有自定义的MultipartResover对象。那么自动配置机制会自动创建MultipartResolver对象。

application.properties中关于文件上传的配置

#MULTIPART(MultiartProperties)


#是否启用SpringMVC多分部上传gongn
spring.servlet.multipart.enable=true

#将文件写入磁盘的阈值。值可以使用后缀”MB“或者”KB“来表示兆字节大小
spring.servlet.multipart.file-size-threshold=0

#指定默认上传的文件夹
spring.servlet.multipart.location=

#限制单个文件最大大小
spring.servlet.multipart.max-file-size=1MB

#限制所有文件最大大小
spring.servlet.multipart.max-request-size=10MB

#是否延迟多部件文件请求的参数和文件的解析
sping.servlet.multipart.resolve-lazily=false

根据配置Spring Boot会自动生成StandardServletMultipartResolver对象,这样就能够对上传的文件进行配置。对于文件的上传可以使用Servlet API 提供的Part接口或者Spring Mvc提供的MultipartFile接口作为参数。其实无论使用哪种类都是允许的。推荐使用Part

2.开发上传功能

applicaiton.properties配置

spring.servlet.multipart.location=d:/demo
spring.servlet.multipart.max-file-size=524880
spring.servlet.multipart.max-request-size=20Mb

上传文件的JSP页面

<%@ page language="java" contentType="text/html";charset="UTF-8”
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD html 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dta">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html";charset="UTF-8">
        <tiltle>文件上传</tiltle>
    </head>
    <body>
        <form method="post" action="./request" enctype="multipart/form-data"> 
            <input type="file" name="file" value="请选择上传的文件">
            <input type="submit" value="提交" />
        </form>
    </body>
</html>

注意

表单声明为multipart/form-data,如果没有这个声明,Spring MVC就会解析文件请求出错。从而导致上传文件失败。

上传控制器

package com.demo.controller

@Controller
@RequestMapping("/file")
public class FileController
    //打开文件上传请求页面
    @GetMapping("/upload/page")
    public String uploadPage()
        return "/file/upload";
    
    
    //处理文件上传结果
    private Map<String,Object> dealResultMap(boolean sucess,String msg)
        Map<String ,Object> result=new HashMap<String,Object>();
        result.put("success",success);
        result.pur("msg",msg);
        return result;
    
    
    //使用HttpServletRequest作为参数
    @PostMapping("/upload/request")
    @ResponseBody
    public Map<String,Object> uploadRequest(HttpServletRequest request)
        boolean flag=false;
        MutipartHttpServlet=mreq=null;
        //强制转换为MultipartHttpServletRequest对象
        if(request instance of MultipartHttpServletRequest)
            mreq = (MultipartHttpServletRequest) request;
        else
            return dealResultMap(false,"上传失败");
        
        //获取MultipartFile文件信息
        MultipartFile mf=mreq.getFile("file");
        //获取源文件名称
        String fileName=mf.getOriginalFilename();
        File file=new File(fileName);
        try
            //保存文件
            mf.transferTo(file);
        catch(Exception e)
            e.printStrackTrace();
            return dealResultMap(false,"上传失败")
        
        return dealResultMap()
    
    
    //使用Spring MVC的MMultipartFile类作为参数
    @PostMapping("/upload/multipart")
    @ResponseBody
    public Map<String,Object> uploadMultipartFile(MultipartFile)
        Sting fileName=file.getOriginalFilename();
        File dest=new File(fileName);
        try
            //保存文件
            file.transferTo(dest);
        catch(Exception e)
            e.printStrackTrace();
            return dealResultMap(false,"上传失败")
        
        return dealResultMap()
    
    
    //使用Part作为参数
    @PostMapping("/upload/part")
    @ResponseBody
    public Map<String,Object> uploadPart(Part file)
        String fileName=file.getSubmittedFileName();
        try
            //写入文件
            file.write(fileName);
        atch(Exception e)
            e.printStrackTrace();
            return dealResultMap(false,"上传失败")
        
        return dealResultMap()
    

八、拦截器

当请求来到DispatcherServlet时,他会根据HandlerMapping的机制找到处理器,这样就会返回一个HandlerExecutionChain对象,这个对象包含处理器和拦截器。这里的拦截器会对处理器进行拦截,这样通过拦截器就可以增强处理器的功能。

1.拦截器的设计

首先所有的拦截器都需要实现HandlerInterceptor接口。

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;

public interface HandlerInterceptor 
    //处理器执行前方法
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception 
        return true;
    
    //处理器执行后方法
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception 
    
    //所有内容完成后执行方法,一般在视图处理之后
    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception 
    

2.开发拦截器

自定义拦截器

package com.demo.interceptor

public class Interceptor1 extends HandlerInterceptor
    @Override
    public boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception
        System.out.println("处理前方法")
        return true;//如果返回False不在执行处理器执行链以后的方法。
    
    
    @override
    public void postHandle(HttpServletRequest request,HttpServletResponse response Object handler,ModelAndView)throws Exception
        System.out.println("处理器后方法")
    
    
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception 
        System.out.println("处理器完成方法");
    

注册拦截器

需要在配置文件中实现WebMvcConfigurer接口,覆盖addInterceprors方法,进行拦截器注册

package com.demo.main

@Configuration
@SpringBootApplication(scanBasePackages="com.demo")
public class DemoApplication implements WebMvcConfigurer
    public static void main(String[] args)
        SpringApplication.run(DemoApplication.class,args);
    
    
    @Override
    public void addInterceptors(InterceptorRegistry registry)
        //注册拦截器到Spring MVC机制,然后它会返回一个拦截器注册
        InterceptorRegistration ir=registry.addInterceptor(new Interceptor1());
        //指定拦截匹配模式,限制拦截器拦截请求
        ir.addPathPatterns("/intercepror/*")
    

控制器

package com.springboot.controller;

@Controller
@RequestMapping("/interceptor")
public class InterceptorController
    @GetMapping("/start")
    @ResponseBody
    public String start()
        System.out.println("执行处理器逻辑");
        return "/welcome"
    

3.多个拦截器

责任链模式的规则,对于处理器前方法采用先注册先执行,而处理器后方法和完成
方法则是先注册后执行的规则。

处理器前( preHandle )方法会执行,但是一旦返回 false ,则后续的拦截器、 处理器和l所有拦截器的处理器后( postHandle ) 方法都不会被执行

九、其他

1.ResponseBody转换为JSON

当想把某个控制器的返回转变为 JSON 数据集时 , 只需要在方法上标注
@Respons巳Body 注解即可,在进入控制器方法前 , 当遇到标注的@ResponseBody 后,处理器就会记录这个方法的响应类型为 JSON 数据集。当执行完控制器返回后,处理器会启用结果解析器( ResultResolver)去解析这个结果,它会去轮询注册
给 Spring MVC 的 HttpMessageConverter 接口的实现类 。 因为 MappingJackson2HttpMessageConverter这个实现类己经被 Spring MVC 所注册 ,加上 Spring MVC 将控制器的结果类型标明为 JSON ,所以就匹配上了 , 于是通过它就在处理器内部把结果转换为了 JSON 。当然有时候会轮询不到匹配的
HttpMessageConverter ,那么它就会交由 S pring MVC 后 续流程去处理。如果控制器返回结果被MappingJackson2HttpMessageConverter 进行了转换,那么后续的模型和视图( Mode!AndView )就返回 null,这样视图解析器和视图渲染将不再被执行 ,

技术图片

2.重定向

重定向( Redirect )就是通过各种方法将各种网络请求重新定个方向转到其他位置 。

通过 以“ redirect:”开头的字符串,然后后续的字符串指向 shouUser方法请求的 URL

sprngMVC提供RedirectAttributes ,可以将它作为控制器的参数,它扩展了 ModelMap 的接口,它有一个 addFlashAttribute 方法,这个方法可以保存需要传递给重定位的数据 。

@GetMapping("/redirect")
public ModelAndRiew redirectConroller(ModelAndView)
    mv.setViewName("redirect:/user/show")


@GetMapping("/redirect2")
public String redirect2(RedirectAttriutes ra)
    ra.addFlashAttribute("num:",1)
    return "redirect:/user/show"

3.操作会话对象

在 W巳b 应用中,操作会话( HttpSession )对象是十分普遍的,对此 Spring MVC 也提供了支持 。主要是两个注解用来操作 H即Session 对象,它们是@SessionAttribute 和@SessionAttributes 。

  • @SessionAttribute 应用于参数,它的作用是将 HttpSession 中的属性读出,赋予控制器的参数——————取
  • @SessionAttributes 则 只能用于类的注解,它会将相关数据模型的属性保存到 Session 中 。 ——————存
package com.demo.controller;

@SessionAttributes(names="user",types=Long.class)
@Controller
@RequestMapping("/session")
public class SessionController
    @Autowired
    private UserService userService=null;
    
    @GetMapping("/test")
    public String test(@SessionAttribute("id")Long id ,Model model)
        Model.addAttribute("id_new",id);
        return "session/test";
    

4.给控制器添加增强

Spring MVC 可以给控制器添加增强,用于在控制器方法的前后和异常发生时去执行不同的处理。

  • @ControllerAdvice 定义一个控制器的通知类 类似于@Aspect
  • @InitBinder 定义控制器参数绑定规则
  • @ExceptionHander: 定义控制器发生异常后的操作
  • @ModelAttribute:可以在控制器方法执行执行之前,对数据模型进行操作
package com.demo.controller.advice

@ControllerAdvice(
    //指定拦截的包
    basePackages="com.demo.advice.test.*"
    //限定被标注为@Controller的类才被拦截
    anontations=Controller.class
)
public class MyControllerAdvice
    //绑定格式化、参数转换规则和增加验证器
    @InitBinder
    public void initDataBinder(WebDataBinder binder)
        //自定义日期编辑器,限定格式为yyyy-MM-dd,且参数不允许为空
        CustomDateEditor dateEditor=new CustomDateEditor(new SimpleDataForm("yyyy-MM-dd"),false);
        binder.registerCustomEditor(Date.class,dateEditor);
    
    
    //在执行控制器前先执行,可以初始化数据模型
    @ModelAttribute
    public void projectModel(Model model)
        model.addAttribute("Project_name","demo")
    
    
    @ExceptionHandler(value=Exception.class)
    public String exception(Model model,Exception ex)
        model.addAttrbute("exception_message",ex.getMessage());
        return "exception";
    

5.获取请求头参数

可以使用@RequestHander

@RequestHander:通过@RequestHander接收请求头

@PostMapping("/header/user")
@ResponsseBody
public User headerUser(@RequestHeader("id") Long id)
    User user=userService.getUser(id);
    return user;

以上是关于Spring MVC的主要内容,如果未能解决你的问题,请参考以下文章

Spring BootSpring MVCSpring有什么区别

Java 之Spring MVCSpring MVC面试题

Java 之Spring MVCSpring MVC面试题

MVCSpring WebFlux

Spring-回顾Servlet

Spring-回顾Servlet