Bootstrap

44-SpringMVC(1-3章)

SpringMVC

学习目标:

1、掌握SpringMVC的执行流程,理解核心分发器的作用

2、掌握SpringMVC的使用步骤

3、掌握SpringMVC的常用的所有注解

4、掌握SpringMVC中的转发和重定向向

5、掌握Spring的静态资源处理

6、掌握SpringMVC的JSON处理

7、掌握SpringMVC的拦截器

8、掌握SpringMVC的异常处理

9、掌握Restful 风格的编码以及解决跨域问题

第1章:SpringMVC简介

传统的Servlet还需要自己去把参数封装到对象上,控制跳转也比较麻烦,做出响应也麻烦,有没有一种框架能够实现参数自动封装,控制器路径直接跳转到指定类的指定方法?做出响应,拦截过滤都比较好的解决方案呢?所以,SpringMVC诞生了。及其简化的了Web开发。

1.1 MVC的含义:

M:Model 模块

V: View 视图

C: Controller 控制器

1.2 SpringMVC的核心组件

组件介绍:

DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性。

HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。

HandlAdapter:通过扩展处理器适配器,支持更多类型的处理器,调用处理器传递参数等工作!

ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel等。

1.3 🚩🚩SpringMVC的执行流程

重要,多次在实际面试中会问的。

DispatchserServlet处于核心的位置,它负责协调和组织不同组件以完成请求处理以及返回数据工作.和大多数Web MVC框架一样,Spring MVC 通过一个前端的Servlet接收所有请求,并将这些工作委托给其他组件进行处理,DispatcherServlet 就是Spring MVC的前端Servlet。下面对Spring MVC处理请求的整体过程进行详解。

🚩🚩SpringMVC的执行流程

1、整个过程始于客户端发出的一个HTTP请求,WEB应用服务器接收到这个请求,如果匹配DispatcherServlet的映请求映射路径(注解指定的路径),则Web容器将该请求转交给DispatcherServlet处理。

2、DispatcherServlet对请求做分发处理,分发给谁呢?根据Request对象,从处理映射器集合里边找出处理映射器,也就是HandlerMapping

3、拿到处理映射器后再获取程序执行器链:HandlerExecutionChain对象,这里里边装了一个HandlerMethod对象,实际上就是我们的Controller类描述的对象。

4、然后再拿到HandlerAdapter处理器适配器对象,调用handler()方法,将HandlerMethod对象传进去,实际上就是调用Controller类中的某个对应路径的方法。

5、handle()方法执行完毕后,就可以得到ModelAndView对象,模型和视图。

6、DispatcherServlet借由ViewResolver完成逻辑视图名到真实视图对象的解析工作,得到View视图对象。

7、最终客户端得到的响应信息可能是一个普通的HTML页面,也可能是一个XML或者JSON串,甚至是一张图片或者一个PDF文档等不同的媒体形式。

1.4 组件开发实现情况(了解)

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

作用:接收请求,响应结果,相当于转发器,中央处理器。有了dispatcherServlet减少了其它组件之间的耦合度。

用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。

2、处理器映射器HandlerMapping(不需要工程师开发,由框架提供)

作用:根据请求的url查找Handler

HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。

3、处理器适配器HandlerAdapter(不需要工程师开发,由框架提供)

作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler

通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。

4、处理器Handler(需要工程师开发)其实可以理解为就是Controller

注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler

Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。

由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。

5、视图解析器View resolver(不需要工程师开发),由框架提供

作用:进行视图解析,根据逻辑视图名解析成真正的视图(view)

ViewResolver负责将处理结果生成View视图,View

Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。

springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。

一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由工程师根据业务需求开发具体的页面。

6、视图View(需要工程师开发 jsp(过时了)、H5)

View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf...)

1.5 核心分发器 DispatcherServlet

DispatcherServlet是Spring MVC的"灵魂"和"心脏",它负责接受HTTP请求并协调 Spring MVC的各个组件完成请求处理的工作。和任何Servlet一样,用户必须在web.xml中配置好DispatcherServlet。

1.5.1 DispatcherServlet继承关系

DispatcherServlet是前端控制器设计模式的实现,提供spring Web MVC的集中访问点,而且负责职责的分派,而且与Spring IoC容器无缝集成,从而可以获得Spring的所有好处。

1.5.2 DispatcherServlet主要职责

1. 文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析;

2. 通过HandlerMapping,将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器);

3. 通过HandlerAdapter支持多种类型的处理器(HandlerExecutionChain中的处理器);

4. 通过ViewResolver解析逻辑视图名到具体视图实现;

5. 本地化解析;

6. 渲染具体的视图等;

7. 如果执行过程中遇到异常将交给HandlerExceptionResolver来解析。

1.5.3 DispatcherServlet核心代码
//前端控制器分派方法 
protected void doDispatch(HttpServletRequest request, HttpServletResponse response)  throws Exception { 
        HttpServletRequest processedRequest = request; 
        HandlerExecutionChain mappedHandler = null; 
        int interceptorIndex = -1; 
        try { 
            ModelAndView mv; 
            boolean errorView = false; 
            try { 
                //检查是否是请求是否是multipart(如文件上传),如果是将通过MultipartResolver解析 
                processedRequest = checkMultipart(request); 
                //步骤2、请求到处理器(页面控制器)的映射,通过HandlerMapping进行映射 
                mappedHandler = getHandler(processedRequest, false); 
                if (mappedHandler == null || mappedHandler.getHandler() == null) { 
                    noHandlerFound(processedRequest, response); 
                    return; 
                } 
                //步骤3、处理器适配,即将我们的处理器包装成相应的适配器(从而支持多种类型的处理器) 
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 
                // 304 Not Modified缓存支持 
                //此处省略具体代码 
                // 执行处理器相关的拦截器的预处理(HandlerInterceptor.preHandle) 
                //此处省略具体代码 
                // 步骤4、由适配器执行处理器(调用处理器相应功能处理方法) 
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 
 
                // Do we need view name translation? 
                if (mv != null && !mv.hasView()) { 
                    mv.setViewName(getDefaultViewName(request)); 
                } 
 
                // 执行处理器相关的拦截器的后处理(HandlerInterceptor.postHandle) 
                //此处省略具体代码 
            } 
            catch (ModelAndViewDefiningException ex) { 
                logger.debug("ModelAndViewDefiningException encountered", ex); 
                mv = ex.getModelAndView(); 
            } 
            catch (Exception ex) { 
                Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); 
                mv = processHandlerException(processedRequest, response, handler, ex); 
                errorView = (mv != null); 
            } 
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 
            //步骤5 步骤6、解析视图并进行视图的渲染 
            //步骤5 由ViewResolver解析View(viewResolver.resolveViewName(viewName, locale)) 
            //步骤6 视图在渲染时会把Model传入(view.render(mv.getModelInternal(), request, response);) 
            if (mv != null && !mv.wasCleared()) { 
                render(mv, processedRequest, response); 
                if (errorView) { 
                    WebUtils.clearErrorRequestAttributes(request); 
                } 
            } 
            else { 
                if (logger.isDebugEnabled()) { 
                    logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() + 
                            "': assuming HandlerAdapter completed request handling"); 
                } 
            } 
            // 执行处理器相关的拦截器的完成后处理(HandlerInterceptor.afterCompletion) 
            //此处省略具体代码 
        catch (Exception ex) { 
            // Trigger after-completion for thrown exception. 
            triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex); 
            throw ex; 
        } 
        catch (Error err) { 
            ServletException ex = new NestedServletException("Handler processing failed", err); 
            triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex); 
            throw ex; 
        } 
        finally { 
            if (processedRequest != request) { 
                cleanupMultipart(processedRequest); 
            } 
        } 
    } 

第2章:SpringMVC具体使用流程(了解,重点是Spring Boot)

2.1 创建maven空白项目

2.2 修改项目编码为UTF-8

2.3 修改pom文件添加依赖:

主要是修改源码的UTF-8编码和maven的编译使用JDK1.8

以及添加SpringMVC和mybatis的整合,为了后续方便,这里我一口气把要用的的包,都添加进来了。单纯创建SpringMVC的话,只需要spring-web、javax.servlet-api、jsp、jackson-databind这四个就够了。

<properties>
    <project.build.sourceEnconding>UTF-8</project.build.sourceEnconding>
    <!--        这个步骤是让你的项目下次打开之后不会还原到默认的1.5环境-->
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>

<!-- 糊涂工具 -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-core</artifactId>
    <version>5.3.5</version>
</dependency>

<!-- mvc的配置 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.4.RELEASE</version>
</dependency>

<!-- druid数据源 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.22</version>
</dependency>
<!--  mybatis的原生包-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.4</version>
</dependency>
<!--        还需要一个整合spring-mnybatis包-->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.20</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>
<!-- 切面编程 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>

<!-- @RequestBody需要用到这个依赖 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.10.3</version>
</dependency>
<!-- jstl 如果是传统的xml文件配置的时候可以加 -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>
<!-- jstl 如果是传统的xml文件配置的时候可以加 -->
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.2</version>
    <scope>provided</scope>
</dependency>
<!-- jstl 如果是传统的xml文件配置的时候可以加 -->
<dependency>
    <groupId>javax.servlet.jsp.jstl</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>
<!-- jstl 如果是传统的xml文件配置的时候可以加 --><!-- jstl 如果是传统的xml文件配置的时候可以加 --> -->
<dependency>
    <groupId>taglibs</groupId>
    <artifactId>standard</artifactId>
    <version>1.1.2</version>
</dependency>

<!-- 文件上传 -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>
<!-- 公共的io依赖包 -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>
<!-- kaptcha验证码 -->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>
</dependencies>

2.4 增加项目框架支持web项目

2.5 创建applicationContext.xml配置文件

内容为:

2.6 创建springMVC-servlet.xml配置文件,文件名称老式项目很可能叫做dispatcherServlet.xml

2.7 修改web.xml文件:内容

<?xml version="1.0" encoding="UTF-8"?>
<web-app>
    <display-name>Archetype Created Web Application</display-name>
    <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>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <!--   ①-->
        <servlet-name>springMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--SpringMVC配置文件的名字  <servlet-name>-servlet.xml
            默认位置:src / resources
            如果放在了 src/resources(maven)
                    contextConfigLocation:classpath:文件名即可!
                    Web-INF/xx.xml
                    contextConfigLocation:/WEB-INF/xx.xml
        -->
        <!--    ②-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/springMVC-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!-- 访问DispatcherServlet对应的路径 -->
    <servlet-mapping>
        <servlet-name>springMVC</servlet-name>
        <url-pattern>/</url-pattern> <!--/不过滤jsp防止死循环-->
    </servlet-mapping>
</web-app>

2.8 使用内嵌tomcat,零配置文件,完全注解方式搭建SpringMVC web项目(了解,为Spring Boot做铺垫)

第3章:🚩🚩常用注解

3.1 @RequestMapping注解

- 加在Controller类上: 给模块添加根路径(所有方法统一拥有前缀路径)

- 加在Controller方法上: 方法具体的路径

3.1.1 @RequestMapping注解value属性

@RequestMapping包含一个value属性!该属性也是默认属性,value属性主要指定的是方法被访问的具体路径!

例如:

/**
 * Java
 */
@Controller
@RequestMapping("/back/user")// 加在类上,那么所有方法访问都要加上这个前缀,是统一前缀的作用
public class UserController {
    @RequestMapping(value = "/list")// 加在方法上,表示具体方法路径,value是默认值,那么value可以不用写:@RequestMapping("list")
    String list() {
        return "pages/back/user/user-list";
    }
}
3.1.2 @RequestMapping注解method属性

method指明的是该方法只能够被某种请求所访问,比如GET或者POST,不是这个请求类型的就会被拒绝访问

 @RequestMapping(value = "/helloworld" , method =RequestMethod.GET)

 @RequestMapping(value = "/helloworld" , method =RequestMethod.POST)

注意: 如果不指定method,可以接收任何类型的请求!如果指定但是访问类型不对会出现405错误!

3.2  获取请求参数的方式

准备工作(客户端):可以编写表单页面,可以浏览器路径传参,可以ajax请求,可以使用Postman,为了后续演示效果更好,本次使用Postman实现模拟请求

Postman需提前[下载安装](Postman,Postman将是我们后期主要模拟请求的工具。

准备工作(服务器端):准备一个用户控制器,模拟接收参数

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
 * java
 */
@Controller
@RequestMapping("/pages/back/user")// 加在类上,那么所有方法访问都要加上这个前缀,是统一前缀的作用
public class UserController {
    @RequestMapping(value = "list")// 加在方法上,表示具体方法路径,value是默认值,那么value可以不用写:@RequestMapping("list")
    String list() {
        return "pages/back/user/user-list";
    }
}
3.2.1 用@RequestParam获取
/**
 * java
 */
@Controller
@RequestMapping("/pages/back/user")// 加在类上,那么所有方法访问都要加上这个前缀,是统一前缀的作用
public class UserController {
    @RequestMapping(value = "list")
// 加在方法上,表示具体方法路径,value是默认值,那么value可以不用写:@RequestMapping("list")
    String list(@RequestParam("userName") String userName) {
        System.err.println(userName);
        return "pages/back/user/user-list";
    }
}

获取参数值,只需要在对应的方法中添加参数即可,如果参数名与请求传参的name值相同即可直接赋值,不需要@RequestParam,注意:对应类型很重要,如果是普通的输入框,使用字符串即可,如果是多选框,可以使用List类型的参数接值!

如果参数名和name值相同,无需使用@RequestParam注解!

注意: 将基本类型转化成包装类型!!

例如:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
 * java
 */
@Controller
@RequestMapping("/pages/back/user")// 加在类上,那么所有方法访问都要加上这个前缀,是统一前缀的作用
public class UserController {
    @RequestMapping(value = "list")
// 加在方法上,表示具体方法路径,value是默认值,那么value可以不用写:@RequestMapping("list")
    String list(String userName, Integer age) {// 前端传的参数名称,完全相同的时候,不需要@RequestParam
        System.err.println(userName);
        return "pages/back/user/user-list";
    }
}

所以强烈建议直接使用跟前端请求参数名称相同的参数名称接收参数值,如果要使用不同的参数名称获取,就需要@RequestParam注解

例如:前端的参数名称为name,控制器参数名称为userName

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
 * java
 */
@Controller
@RequestMapping("/pages/back/user")// 加在类上,那么所有方法访问都要加上这个前缀,是统一前缀的作用
public class UserController {
    @RequestMapping(value = "list")
// 加在方法上,表示具体方法路径,value是默认值,那么value可以不用写:@RequestMapping("list")
    String list(@RequestParam("name") String userName, Integer age) {// 将前端的name参数,映射到userName
        System.err.println(userName);
        return "pages/back/user/user-list";
    }
}

千万要注意的一点就是,参数的类型要正确,特别是如果前端传递的是一个文件,那么参数类型就要注意了,后面再说。

3.2.2 方法参数设置默认值,(@RequestParam中的defaultValue属性)
@RequestMapping("/list")
public  String list(@RequestParam(defaultValue = "1") Integer currentPage , @RequestParam(defaultValue = "10") Integer pageSize){// 设置分页大小为每页展示10条
	//设置默认值,如果不传递使用参数的默认值
     System.out.println("currentPage = " + currentPage);
     System.out.println("pageSize = " + pageSize);
     return  "list";
 }
3.2.3 用一个对象中去接收,这个对象中的成员名称要和参数名称一致

例如:用一个User对象去接收名字和年龄:

先准备一个实体类,演示只用了三个字段:

public class User implements Serializable {
    private String userName, phone;
    private Integer age;
    // setter、getter略
}

控制器方法:

@Controller
@RequestMapping("/pages/back/user")// 加在类上,那么所有方法访问都要加上这个前缀,是统一前缀的作用
public class UserController {
    @RequestMapping(value = "list")// 加在方法上表示具体方法路径,value是默认值,那么value可以不用写:@RequestMapping("list")
    String list(@RequestParam("name") String userName, Integer age) {// 将前端的name参数,映射到userName
        System.err.println(userName);
        return "pages/back/user/user-list";
    }
    @RequestMapping("add")
    String add(User user) {// 用一个实体类去接收前端传递的参数
        System.err.println(user);
        return "pages/back/user/user-addPre";
    }
}

3.2.4 用 @RequestBody 接收单个对象

首先:需要引入jackson-databind依赖,否则会报content-type不被支持的错误。

<!-- @RequestBody需要用到这个依赖 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.10.3</version>
</dependency>

然后在控制器方法中就可以使用@RequestBody 来接收json字符串了

 @RequestMapping(value = "add")
    String add(@RequestBody User user) {// 用一个实体类去接收前端传递的参数
        System.err.println(user);
        return "pages/back/user/user-addPre";
 }

这里需要注意的是前端需要修改content-type为application/json,并且传递json字符串

postman还需要在body中选择raw,json格式

注意:如果是ajax请求,那么需要调用JSON.stringify()把json对象变成json字符串如下:

$.ajax({
   url: "/pages/back/user/add",
   data: JSON.stringify({phone:'18223170162'}),// 这里需要把json对象编程字符串
   type: "post",
   dataType: "json",
   contentType: 'application/json;charset=utf-8',
   success: function (res) {
                     
   },
   error: function (e) {
  
    }
});
3.2.5 用@RequestBody接收集合或者数组

客户端

控制器方法:用集合接收

@RequestMapping(value = "add")
    String add(@RequestBody List<User> users) {// 用集合接收一组实体对象
        System.err.println(users);
        return "pages/back/user/user-addPre";
    }

控制器方法:也可以用数组接收

@RequestMapping(value = "add")
    String add(@RequestBody User[] users) {// 用数组接收一组实体对象
        System.err.println(users);
        return "pages/back/user/user-addPre";
    }

ajax代码:

 let users=[];// 定义一个数组
                users.push({phone:'18223170162'});// 加一个数据
                users.push({phone:'18223170163'});// 再加一个数据
              $.ajax({
                  url: "/pages/back/user/add",
                  data: JSON.stringify(users),
                  type: "post",
                  dataType: "json",
                  contentType: 'application/json;charset=utf-8',
                  success: function (res) {

                  },
                  error: function (e) {

                  }
              });
3.2.6 用 @PathVariable 接收路径参数

客户端:假设修改用户id是888的这个用户

3.2.7 用HttpServletRequest的getParameter()方法

先导入依赖:

<dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
 </dependency>

客户端:

控制器方法:

@RequestMapping("delete")
    String delete(HttpServletRequest request) {
        String userId = request.getParameter("userId");
        System.err.println(userId);
        return "pages/back/user/user-addPre";
 }

* 小结: * 前端传递数据到控制器接收参数的方式: * 1、前端是表单提交的时候 (content-type:application/x-www-form-urlencoded),基本数据类型和String , * 只需形式参数跟前端传的参数名称一致即可。 * 2、当多个参数的时候,content-type:application/x-www-form-urlencoded),那么就用对象去接收,对象中的属性需要跟参数名称一致 * 3、当路径传参的时候,那么路径需要用{变量} 定义,且需要使用@PathVariable 注解 接收,且形式参数名称跟定义的参数名称一致。 * 4、当前端是前后端分离:axios请求:content-type:application/json的时候,前端此时请求的是JSON字符串,那么后端控制器的方法 * 就需要加上@RequestBody注解来接收参数,且类型是对象或者集合。 * 5、当参数中有日期类型的时候,如果不配置任何转换方式,会出现400,所以对日期处理,需要进行转换配置 * 6、当参数中有文件的时候,后面专讲。

在控制器拿到HttpServletRequest对象的三种方式:🚩🚩

1、如上,直接在方法参数中声明,即可获取

2、在控制器自动注入

3、🚩🚩

HttpServletRequest request=((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();// 这个方法一定要求掌握

3.3 ant风格的路径

ant风格资源地址支持3种匹配符:

?:匹配文件路径名中的任意一个字符,只能1层目录

*:匹配文件路径名中的任意多个字符,只能1层目录

**:匹配多层路径,多个任意字符

如:

package boss.zkt.controller.ant风格的路径;

import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author:牧牛者 说明:
 */
@RestController("b")// b是名称,跟路径没关系
public class BController {

    // 访问路径: http://ip:端口/上下文/a/一个任意字母/f/d
    @RequestMapping("/a/?/f/d")
    public Object mm() {
        return "哈哈哈";
    }

    @RequestMapping("/b/*/f/d")
    public Object mm1() {
        return "哈哈哈2";
    }

    @RequestMapping("/c/**/d")
    public Object mm2() {
        return "哈哈哈3";
    }
}

3.4 🚩参数中有日期会出现400的解决方案

因为外国人的日期跟我们中国的有差异,所以在解析日期的时候,会出现参数400无法解析,这个时候需要配置转换器。

解决方式1:在控制器利用@InitBinder 设置属性编辑器PropertyEditor

属性编辑器就是遇到什么类型的属性,就怎么解析成对应的数据类型

@InitBinder
    public void initBinderXXX(WebDataBinder binder) {
        DateFormatter f1=new DateFormatter("yyyy-MM-dd");
        // 也可以用自定义日期处理类
//        CustomDateEditor customDateEditor=new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"),true);
        FormatterPropertyEditorAdapter f2=new FormatterPropertyEditorAdapter(f1);
        binder.registerCustomEditor(Date.class, f2);
    }

以上方式不推荐。

可以用转换器来配置:主流

实现WebMvcConfigurer接口,注意,这个接口以后几乎百分之百会覆写,添加配置。

覆写addFormatters 方法,增加转化器。转换器的意思是入参是什么类型,想要转换的目标类型。很显然对于前端参数传递是String到Date类型的转换。

@Override
public void addFormatters(FormatterRegistry registry) {
    registry.addConverter(new Converter<String, Date>() {
        @Override
        public Date convert(String s) {
            if (!StringUtils.isEmpty(s)) {
                try {
                    return DateUtil.parse(s).toJdkDate();// 转换成java.util.Date
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
    });
}

但是返回JSON对象的时候,日期又出问题了,此时日期是时间戳,需要注意的是,自己配置的MappingJackson2HttpMessageConverter SpringMVC不会用,需要在配置中删除,加入自己的:



import cn.hutool.core.date.DateUtil;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

/**
 * @author:牧牛者 说明:
 */
@Configuration //申明该类为配置类
@ComponentScan(basePackageClasses = WebApplicationConfig.class)//扫描指定类所在包及子包中的spring组件
@EnableWebMvc //提供mvc支持
public class WebApplicationConfig implements WebMvcConfigurer {

    //设置响应信息编码集
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        StringHttpMessageConverter stringHttpMessageConverter = (StringHttpMessageConverter) converters.get(1);
        stringHttpMessageConverter.setDefaultCharset(Charset.forName("utf-8"));

        Iterator<HttpMessageConverter<?>> iterator = converters.iterator();
        while (iterator.hasNext()) {
            HttpMessageConverter<?> next = iterator.next();
            if(next.getClass().isAssignableFrom(MappingJackson2HttpMessageConverter.class)){
                iterator.remove();
            }
        }
        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = httpMessageConverter();
        System.out.println(converters);
        converters.add(mappingJackson2HttpMessageConverter);
    }
    @Bean
    public MappingJackson2HttpMessageConverter httpMessageConverter() {
        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addSerializer(Date.class, new JsonSerializer<Date>() {
            @Override
            public void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
                String pattern = "yyyy-MM-dd";// 默认返回日志的格式样子
                try {
                    Field declaredField = gen.getCurrentValue().getClass().getDeclaredField(gen.getOutputContext().getCurrentName());
                    JsonFormat annotation = declaredField.getAnnotation(JsonFormat.class);
                    if (annotation != null) {
                        pattern = annotation.pattern();
                    }
                } catch (NoSuchFieldException ignored) {
                    System.out.println(ignored);
                }
                gen.writeString(DateUtil.format(value, pattern));
            }
        });
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
        objectMapper.registerModule(module);
        mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
        return mappingJackson2HttpMessageConverter;
    }
    //提供静态资源的支持
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //客户端以/html/开始的请求,访问类路径下static/html
        registry.addResourceHandler("/html/**").addResourceLocations("classpath:/static/html/");
    }


    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addFormatter(new Formatter<Date>() {
            SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd");

            @Override
            public String print(Date date, Locale locale) {
                return s.format(date);
            }

            @Override
            public Date parse(String s, Locale locale) throws ParseException {
                return DateUtil.parse(s).toJdkDate();
            }
        });

    }


    /**
     * 一定对 ajax返回数据才有用
     *
     * @return
     * @ResponseBody标记的方法 或者 @RestController
     */
//    @Bean
//    public ObjectMapper jacksonObjectMapper() {
//        ObjectMapper objectMapper = new ObjectMapper();
//        objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,false);
//        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);// 禁用将日期转换成时间戳
//        objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);// 调整日期到时区
//        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);//  未知的属性直接忽略不赋值给对象的属性,否则会报错的
//        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);// 为null的属性不传到前端去
//
//        SimpleModule module=new SimpleModule();
        JavaTimeModule module = new JavaTimeModule();//  创建JDK时间模块
//
//        // 增加 序列化器 java对象转换成Json字符串
//        module.addSerializer(Long.class, ToStringSerializer.instance);// Long类型转换成String解决丢失精度
//        module.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy/MM-dd"));
//
//        /**
//         * 增加 返回json数据的时候日期的序列化器
//         * 序列化 从控制器 ======>>>>> 前端
//         */
//        module.addSerializer(Date.class, new JsonSerializer<Date>() {
//            @Override
//            public void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
//                String pattern = "yyyy-MM-dd";// 默认返回日志的格式样子
//                try {
//                    Field declaredField = gen.getCurrentValue().getClass().getDeclaredField(gen.getOutputContext().getCurrentName());
//                    JsonFormat annotation = declaredField.getAnnotation(JsonFormat.class);
//                    if (annotation != null) {
//                        pattern = annotation.pattern();
//                    }
//                } catch (NoSuchFieldException ignored) {
//                    System.out.println(ignored);
//                }
//                gen.writeString(DateUtil.format(value, pattern));
//            }
//        });
//
//        /**
//         * // 前端 json字符串 ====== >>>>> 解析成对象
//         * 增加一个反序列化的 解析器(这才是最好的方法,没有之一!)
//         * 这是 增加 @RequestBody的 日期 时间转换 参数解析方式!
//         */
//        module.addDeserializer(Boolean.class, new JsonDeserializer<Boolean>() {
//            @Override
//            public Boolean deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
//                String value = jsonParser.getText();// 日期字符串取出来
//                return "true".equals(value) || "1".equals(value);
//            }
//        });
//
//
//        /**
//         * 增加一个反序列化的 解析器(这才是最好的方法,没有之一!)
//         * 这是 增加 @RequestBody的 日期 时间转换 参数解析方式!
//         */
//        module.addDeserializer(Date.class, new JsonDeserializer<Date>() {
//            @Override
//            public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
//                String date = jsonParser.getText();// 日期字符串取出来
//                DateTime parse = null;
//                try {
//                    parse = DateUtil.parse(date);// 所有 只要长得像 日期的字符串都会成功被解析成Date对象。2020/12-1
//                } catch (Exception e) {
//                    System.err.println(e.getMessage());
//                    return null;
//                }
//                if (parse == null) {
//                    return null;
//                }
//                return parse.toJdkDate();
//            }
//        });
//        // 解析 json字符串的配置,返序列化的配置
//        objectMapper.registerModule(module);
//        return objectMapper;
//    }

}

;