• 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;
    }
    
  • 相关阅读:
    IDEA插件和快捷设置
    漫谈虚拟内存
    漫谈进程和线程
    漫谈计算机语言
    初识Python
    数据库物理设计
    漫谈计算机体系
    数据库逻辑设计
    NLP中几种分词库的简单使用(Python)
    ML————朴素贝叶斯原理和SKlearn相关库
  • 原文地址:https://www.cnblogs.com/yanquhonggui/p/11110447.html
Copyright © 2020-2023  润新知