# 1.SpringMVC简介

Spring MVC是一个基于Java的Web应用程序开发框架,它属于Spring Framework的一部分。Spring MVC通过将应用程序分解为模型(Model)、视图(View)和控制器(Controller)三个核心组件,提供了一种基于MVC(模型-视图-控制器)设计模式的开发方式。

在Spring MVC中,模型(Model)代表应用程序的数据模型,视图(View)负责展示模型数据给用户,而控制器(Controller)处理用户请求,并根据请求的类型选择合适的视图呈现给用户。这种分层架构使得代码的组织更加清晰,并且易于扩展和维护。

Spring MVC提供了许多特性和组件,使得开发Web应用程序更加便捷。其中包括:

  1. 注解驱动开发:Spring MVC支持使用注解来配置控制器和请求映射,简化了配置和编码的过程。

  2. 强大的数据绑定和验证:Spring MVC提供了强大的数据绑定和验证功能,能够自动将请求参数绑定到模型对象上,并进行验证和转换。

  3. 灵活的视图解析:Spring MVC支持多种视图解析技术,如JSP、Thymeleaf、Freemarker等,可以根据需要选择合适的视图技术。

  4. 拦截器:Spring MVC的拦截器可以在请求处理的各个阶段进行拦截和处理,实现例如身份验证、日志记录等功能。

  5. RESTful支持:Spring MVC对于构建RESTful风格的Web服务提供了良好的支持,可以方便地处理RESTful风格的URL和资源映射。

总结来说,Spring MVC是一个成熟、灵活且功能强大的Java Web开发框架,它可以帮助开发者快速构建可扩展的Web应用程序,并且具有良好的模块化和可测试性。

# 2.SpringMVC快速入门

需求: 客户端发起请求,服务器接收请求,执行逻辑并进行视图跳转

开发步骤:

  1. 导入SpringMVC相关坐标

    <!--Spring-MVC-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.0.5.RELEASE</version>
            </dependency>
    

    ​ 当我们导入了坐标后我们就有了SpringMVC核心控制器DispatchcerServlet

  2. 在web.xml中配置SpringMVC核心控制器DispathcerServlet

    <!--
            因为这个Servlet不是我们编写的,我们无法使用注解配置,所以手动配置这个Servlet
            配置dispatcherServlet (dispatcher是调度员的意思,说明这个Servlet负责所有Servlet共有的操作,而我们只需要编写特殊的操作)
        -->
        <servlet>
            <!--        Servlet的名字-->
            <servlet-name>DispatcherServlet</servlet-name>
            <!--        指定DispatcherServlet的字节码文件所在位置-->
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <!--
                告诉DispatcherServlet,需要加载的Spring-MVC的配置文件的位置
            -->
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:spring-mvc.xml</param-value>
            </init-param>
            <!--
            当值为0或者大于0时,表示容器在应用启动时就加载这个servlet;
            当是一个负数时或者没有指定时,则指示容器在该servlet被选择时才加载。
            正数的值越小,启动该servlet的优先级越高。
            -->
            <load-on-startup>1</load-on-startup>
        </servlet>
        <!--
            配置Servlet的映射地址,/ 是缺省的标识, 因此找不到对应的请求就走这个, 而/* 表示的是所有的请求
        -->
        <servlet-mapping>
            <servlet-name>DispatcherServlet</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
  3. 创建Controller类和视图页面

    package com.snake.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    /**
     * 在web层中,给这个Controller写明对应的注解,
     */
    @Controller // 添加为Bean
    public class UserController {
        /**
         *
         * 使用@RequestMapping注解 来表明映射的方法
         * @return
         */
        @RequestMapping("/quick")
        public String save()
        {
            System.out.println("嗯哼~");
            //返回的网页,类似于重定向和请求转发?
            return "success.jsp";
        }
    }
    
  4. 使用注解配置Controller类中业务方法的映射地址

    @RequestMapping("/quick")
    public String save()
    {
        System.out.println("嗯哼~");
        //返回的网页,类似于重定向和请求转发?
        return "success.jsp";
    }
    
  5. 配置SpringMVC核心文件Spring-mvc.xml 名字任意,注意别忘了,配置Servlet时,配置这个初始化文件的位置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--
    配置包扫描,也可以在applicationContext.xml配置文件中配置,但是为了拆分,我们应该新建一个文件来写
-->
    <!--
    可以扩大扫描范围,并且自定义扫描的注解和不扫描的注解,一般都是指定不扫描什么..
    exclude-filter: 不扫描的
    include-filter: 扫描的
            com.xxx.xxxx为不想被扫描的包
<context:component-scan base-package="com.snake">
        <context:exclude-filter type="annotation" expression="com.xxx.xxxx"/>
    </context:component-scan>
-->
    
    <context:component-scan base-package="com.snake.controller"></context:component-scan>
</beans>
  1. 客户端发起请求测试

img

​ 注意,如果浏览器返回500,可能是jsp依赖,没有把范围指定为provide

# 3.SpringMVC流程图

img

# 4.SpringMVC框架内部执行流程图

img

  1. 用户发送请求至前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用HandlerMapping处理器映射器
  3. 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果 有则生成)一并返回给DispatcherServlet
  4. DispatcherServlet调用HandlerAdapter处理器适配器
  5. HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)
  6. Controller执行完成返回ModelAndView
  7. HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
  8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器
  9. ViewReslover解析后返回具体View
  10. DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)
  11. DispatcherServlet响应用户

# 5.Spring注解解析

  1. @RequestMapping

    ​ (1)作用:用于建立请求URL和处理请求方法之间的对应关系

    ​ (2)位置:

    • 类上,请求URL的第一级访问路径,此处不写的话,就相当于应用的根路径

      //当我们没有在返回的网页前加'/',它就默认这个网站的资源在我们这个方法的访问资源的位置下,加了就是webapp的根目录下
      return "success.jsp";
      

      没加上'/',它会去@questMapping映射的位置找

      img

      如果我们加上了'/',资源就能正确的去找webapp下的success.jsp'

      img

    • 方法上,请求Url的第二级访问路径,与类上的使用@RequestMapping标注的一级目录组成访问虚拟路径

​ (3)属性:

  • value:用于指定请求的url.它和path属性的作用是一样

  • method:用于指定请求的方式, 例如get,post等,多个请求方式,用一个数组接收

    @RequestMapping(value="/quick",method = {RequestMethod.GET,RequestMethod.POST},params = {"username"})
    
  • params:用于指定限制请求参数的条件.他支持简单的表达式,要求请求参数key和value必须和配置的一模一样

    例如

    • params = {"accountName"}, 表示请求的参数必须有accountName

      img

    • params = {"money!100"},表示请求参数中money不能是100

# 6.手动配置SpringMVC内部组件的部分属性

  1. 视图解析器

    SpringMVC有默认组件配置,默认组件都是DispatcherServlet.properties配置文件中配置的,该配置文件地址 org/springframework/web/servlet/DispatcherServlet.properties,

    该文件中配置了默认的视图解析器,如下: org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.I nternalResourceViewResolver

    翻看该解析器源码,可以看到该解析器的默认设置,如下:

    REDIRECT_URL_PREFIX = "redirect:" --重定向前缀

    FORWARD_URL_PREFIX = "forward:" --转发前缀(默认值)

    我们可以在return的字符串,里面添加转发或者重定向的前缀,(转发的前缀是默认的,我们不写这个前缀就是转发)

    这样就会给浏览器发送对应的响应

    prefix = ""; --视图名称前缀

    suffix = ""; --视图名称后缀

我们可以设置Spring容器中的视图解析器 InternalResourceViewResolver,他有两个属性 prefix = ""; --视图名称前缀 和 suffix = ""; --视图名称后缀,

这两个属性有set方法,所以我们可以通过Spring容器进行设置

通过设置这两个前后缀的内容,我们可以,省去一些路径的书写,

<bean id="ViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

经过上面的配置,

return "success"; 等同于 return "/jsp/success.jsp";

注意:重定向时,并不会拼接前缀和后缀

# 7.SpringMVC的数据响应

# 1.页面跳转

  1. return "xxx.html" 默认是请求转发

    @RequestMapping(value="/quick")
    public String save()
    {
        System.out.println("嗯哼~");
        //当我们没有在返回的网页前加'/',它就默认这个网站的资源在我们这个方法的访问资源的位置下,加了就是webapp的根目录下
        return "redirect:/jsp/success.jsp";
    }
    
  2. return ModelAndView 对象,

    这个对象可以封装数据和视图 ModelAndView方式封装的数据 在request域中保存

    /**
     * 通过返回 ModelAndView对象来跳转页面
     	Model 模型用来封装数据
     	View  视图用来展示数据
     * @return
     */
    @RequestMapping(value="/quick2")
    public ModelAndView save2()
    {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("success"); //封装一个视图 /jsp/success.jsp
        modelAndView.addObject("username","阿尔托莉雅"); //保存的数据在request域中,我们在jsp页面中可以通过el表达式来获取
        //返回ModelAndView
        return modelAndView;
    }
    

# 2.返回数据

# (1).返回普通格式的数据

/**
 * 返回数据
 * 方式1: 通过Spring注入获取HttpServletResponse来获取输出流,输出一个字符串.返回值设置成void
 * 中文乱码问题:
 * 方式1.将tomcat升级至8之后的版本
 * 方式2.设置编码:
 *          告诉浏览器用UTF-8编码来解码
 *         response.setContentType("text/html;charset=utf-8");
 *          设置输出流的编码为UTF-8
 *         response.setCharacterEncoding("UTF-8");
 */
@RequestMapping(value="/quick6")
public void save6(HttpServletResponse response) throws IOException {
    response.setContentType("text/html;charset=utf-8");
    response.setCharacterEncoding("UTF-8");
    response.getWriter().write("你就是我的星球");
}

/**
 * 返回数据
 * 方式2: 通过注解 @ResponseBody
 * 告诉Spring容器我们返回的是输出到网页的字符串,而不是一个跳转的网页
 * 中文乱码问题:
 * 方式1.将tomcat升级至8之后的版本,未测试不知道是否有效...
 * 方式2. 因为是通过注解来返回的字符串,而不是流,所以我们直接获取流来设置编码不起作用??
 *          我们在注解中设置 produces属性
 *          @RequestMapping (value="/quick7",produces = {"text/html;charset=utf-8"})
 */
@RequestMapping(value="/quick7",produces = {"text/html;charset=utf-8"})
@ResponseBody
public String save7() throws IOException {
    return "啊哈哈~~诶嘿嘿~~";
}

# (2.)返回JSON类型数据

1. 使用第三方的jar包的json工具类,手动的转换数据,再返回

/**
 * 返回JSON类型数据,
 * 可以手动拼接,但是太麻烦,
 * 我们通过导入fastjson坐标,使用里面的工具类来转换对象为JSon格式的数据
 * 通过注解 @ResponseBody
 * 告诉Spring容器我们返回的是输出到网页的字符串,而不是一个跳转的网页
 */
@RequestMapping(value="/quick8",produces = {"text/html;charset=utf-8"})
@ResponseBody
public String save8() throws IOException {
    //获取User对象
    User user = new User("阿尔托莉雅",18);
    //将User对象转换成json格式的数据
    String jsonString = JSON.toJSONString(user);
    return jsonString;
}

2. 通过配置HandlerAdapter处理器适配器,来帮我们自动转换成JSON数据

  1. Spring-MVC配置文件
<!--
    配置处理适配器,帮助我们自动转换JSON数据
-->
    <bean id="HandlerAdapter" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"/>
            </list>
        </property>
    </bean>
  1. java代码
/**
 * 通过配置处理适配器,让它帮我们自动帮我们处理JSON数据
 * 注意! 我没有使用老师使用的json转换工具Jackson,我用的是fastjson
 * 所以配置时,传入的Bean对象用的是fastJSON的FastJsonHttpHttpMessageConverter
 * fastJson的
 * Bean: com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter
 *
 * 如果没有导入老师使用的json转换工具Jackson的坐标,直接用老师的那个转换Bean对象会抛出异常!!
 * 老师的Bean:
 * org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
 */
@RequestMapping(value="/quick9",produces = {"text/json;charset=utf-8"})
@ResponseBody //不能省略,因为传的还是字符串数据
public User save9() throws IOException {
    //获取User对象
    User user = new User("阿尔托莉雅",18);
    //因为我们配置了处理适配器,直接返回对象,就可以转换成JSON数据
    return user;
}
# 使用MVC命名空间的一个功能,帮我们自动配置处理器适配器,(它也会配置处理映射器),而且它的里面就帮我们配置好了JSON数据的自动转换
<!--
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"
    使用命名空间的mvc,自动帮我们配置处理适配器和处理映射器
    其中处理适配器,帮我们配置好了自动转换JSON数据,注意,它需要Jackson的工具jar包,fastjson的不行
-->
    <mvc:annotation-driven/>

以后写SpringMVC的项目,直接就把这个命名空间写上

  • 如果浏览器 的状态码为 406, 错误的意思是:说是指定的资源已经找到,但它的MIME类型和客户在Accpet头中所指定的不兼容,我们应该设置返回的类型为 application/json

# (3).数据在域中的保存

3.1Request对象中保存数据(不推荐)

3.2、Session域中保存数据

3.3、ServletContext域中保存数据

3.4、Map或Model或ModelMap形式保存数据在request域中

3.5、ModelAndView方式保存数据到request域中

3.6、@SessionAttributes保存数据到Session域中(不推荐使用)

3.7、@ModelAttribute注解

# 8.SpringMVC的接收请求参数

客户端请求参数的格式是: name=value&name=value...

服务器端要获得请求的参数,有时还需要进行数据的封装,SpringMVC可以接收如下类型的参数:

  • 基本类型参数
  • POJO类型参数
  • 数组类型参数
  • 集合类型参数

# 8.1 获取基本类型参数

​ Controller中的业务方法的参数名要与请求参数的name一致,这样SpringMVC就会帮你自动映射匹配

img

/**
 * 接收浏览器通过get方式发送的参数
 * Controller中的业务方法的参数名要与请求参数的name一致,
 * 这样SpringMVC就会帮你自动映射匹配
 */
@RequestMapping(value="/quick12",produces = {"application/json;charset=utf-8"})
@ResponseBody
public void save12(String username,int age) throws UnsupportedEncodingException {
    //写明了@ResponseBody注解,并且返回值为void,说明我们不返回数据
    /*
        只要方法形参的名字与get方式,发过来的参数名字匹配就等自动映射匹配
        如果,形参的类型为数字的类型,从url中获取的参数是String的,SpringMVC会自动将数字类型转换成对应的类型
        但是如果是字母字符串,无法转换成数字的话就会抛出异常

     */
    /*
      解除中文乱码
     */
    String encode = URLEncoder.encode(username, "ISO-8859-1");
    String decode = URLDecoder.decode(encode, "UTF-8");
    System.out.println(decode);
    System.out.println(age);
}

# 8.2请求数据POJO类型参数,或者说参数要封装成对象

img

/**
 * 接收的数据通过SpringMVC直接给我们封装成对象
 *  如果请求参数的名称符合POJO类对象的set方法中的首字母大写就匹配的要求
 *  就会将参数传递到set方法进行封装,所以要求封装的类写了set方法
 */
@RequestMapping(value="/quick13",produces = {"application/json;charset=utf-8"})
@ResponseBody
public void save13(User user) throws UnsupportedEncodingException {
    //通过Spring帮我们封装参数的数据,变成一个对象
    //我们应该在Set方法里面对数据进行处理,例如数据的验证,以及编码解码等等
    System.out.println(user);
}

# 8.3请求携带的参数为数组类型数据

img

/**
 * 获得数组类型数据
 */
@RequestMapping(value="/quick14",produces = {"application/json;charset=utf-8"})
@ResponseBody
public void save14(String[] strs) throws UnsupportedEncodingException {
    /**
     * 由于数组打印是地址,我们借助集合工具类帮我们打印
     */
    System.out.println(Arrays.asList(strs));
    //System.out.println(Arrays.toString(strs));
}

# 8.4 请求携带的参数为集合类型数据

如果前端发送数据的方式是GET,数据放在 请求url里面 表单放在from-data里面

使用时要做后端加上@RequestParam注解代表接收get方式的数据,它会从这两个地方拿数据

img

如果前端发送的数据方式POST,并且是JSON字符串,这样的话数据存在请求体中,我们注意Ajax的请求必须设置 contextType为 application/json,不过我用axios不设置好像也可以,axios默认发送json格式数据

我们必须使用@RequestBody注解,表明到请求体里面拿数据!!!!

request.getParameter() 能接受这两种方式的数据,以及post方式的数据,

但是无法接受json类型的数据!!!

具体参考https://www.cnblogs.com/blogtech/p/11172168.html

前端:,本例使用的axios,也可以使用原生的AJAx

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
    //准备集合数据
    var userList = new Array();
    userList.push({
        username:"saber",
        age:18
    });
    userList.push({
        username:"white",
        age:18
    });
    //使用axios发送请求
    axios({
        method:"post",
        url:"http://localhost:8080/Spring-demo-web/user/quick15",
        data:userList
    })


</script>

后端代码:

/**
     * 获取集合类型数据
     *  创建一个VO对象(Value Object),里面有个属性是一个集合,之后重写set方法
     *   我们通过让Spring通过这个set方法,帮我们向集合中添加元素
     *
     *   @RequestParam 该注解的代表接收get方式的数据
     *   @RequestBody 该注解代表接收放在请求体里面的数据,所以必须是post请求才行!!
     *   前端发送集合数据的方式:
     *   1.在表单中,手动写一个集合中的多个对象属性的输入框, 麻烦...
     *          <form action="${pageContext.request.contextPath}/user/quick15" method="post">
     *         <input type="text" name="userList[0].username"><br>
     *         <input type="text" name="userList[0].age"><br>
     *         <input type="text" name="userList[1].username"><br>
     *         <input type="text" name="userList[1].age"><br>
     *         <input type="submit" value="提交">
     *     </form>
     *  2. 使用Ajax发送JSON类型数据, 接收的为集合类型对象,而不是封装后的VO对象
     */
    @RequestMapping(value="/quick15",produces = {"application/json;charset=utf-8"})
    @ResponseBody
    public void save15(@RequestBody(required = false) List<User> userList) throws IOException {
        //使用方式1:
//        System.out.println(Arrays.asList(vo.getUserList()));
        //使用方式2:
        System.out.println(userList);
    }

# 8.5.接收数据时的注解

  1. @RequestParam注解

    img

    img

# 8.6 获得Restful风格的参数

​ Restful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务 器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。

Restful风格的请求是使用“url+请求方式”表示一次请求目的的,HTTP 协议里面四个表示操作方式的动词如下:

  • GET:用于获取资源

  • POST:用于新建资源

  • PUT:用于更新资源

  • DELETE:用于删除资源

    例如:

    • /user/1 GET : 得到 id = 1 的 user
    • /user/1 DELETE: 删除 id = 1 的 user
    • /user/1 PUT: 更新 id = 1 的 user
    • /user POST: 新增 user

前端也可以通过ajax的url发送占位符中的数据

后端获取数据时设置@RequestMapping注解的method = RequestMethod.POST等

演示以Get动词请求的方式

img

/**
 *  演示Restful风格参数,浏览器发送url+数据
 *   @RequestMapping (value="/quick15/{name}") 注解中的{name}是占位符,将url中这个位置的数据传输到@PathVariable注解中 value="name"的方法形参
 *  @PathVariable (value="name",required = true)  这个注解后面跟着哪个参数,就会把这个占位符中的数据传给哪个形参, required = true代表这个占位符的数据,浏览器必须传过来
 *  注解自动
 * @throws IOException
 */
@RequestMapping(value="/quick16/{name}")
@ResponseBody
public void save16(@PathVariable(value="name",required = true) String name) throws IOException {
    System.out.println(name);
}

# 9.开放静态资源访问

使用SpringMVC时为什么要开启静态资源??

原因:

默认的servlet的匹配路径是/,当用户访问资源时,没有资源的路径与其匹配就会访问到这个默认路径/ 也就是说会访问这个Servlet,同时这个Servlet会放行静态资源,当我们用SpringMVC的dispatchServlet配置了匹配路径/时就会,替代这个默认的Servlet,同时所有的静态资源的访问也都会被拒绝,需要我们手动去配置静态资源访问权限,或者说将静态的访问资源返回给默认的Servlet

具体情况:(60条消息) url-pattern配置为"/"和"/*"的区别_木子易子的博客-CSDN博客_ (opens new window)

如果我们的前端页面中用到了服务器中的一些js,css,图片等等资源,我们需要给这些文件开启访问权限, 如果发现没有开放资源但是依然返回正常的状态码,说明浏览器没有清空缓存,还保留着这个文件的数据

方式1: 在SpringMVC 中开放资源的访问权限

<!--
    开放服务器中js文件夹下的访问权限
    mapping="/js/**"代表 用户访问时输入的url
    location="/js/" 代表文件存在的实际位置
-->
    <mvc:resources mapping="/js/**" location="/js/"/>

方式2: 将SpringMVC的静态访问权限交给TomCat去处理

<!--转交给默认的Servlet既下面这个代码-->
<mvc:default-servlet-handler/> 

​ TomCat好像是把所有的资源文件都看成 Servlet,我们交给它处理就能找到 资源

方式3: 使用Servlet配置映射 在web.xml中配置

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.js</url-pattern>
</servlet-mapping>

参考:web项目如何通过servlet访问静态资源 (opens new window)

# 10.请求参数乱码问题,配置全局过滤器

当post请求时,数据会出现乱码(奇怪的是我并没有出现乱码,而老师的课程里却出现了乱码),我们可以设置一个过滤器来进行编码的过滤。

在Web.xml中配置一个全局的过滤器

<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <!--使用SpringMVC提供的过滤器-->
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <!--设置里面的encoding属性的值为对应的编码-->
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <!--过滤路径,设置为拦截全局-->
    <url-pattern>/*</url-pattern>
</filter-mapping>

# 11.自定义请求参数的类型转换器

这个转化器是将 前端发送过来的数据映射到参数上时进行的转化, 不会将返回的数据进行转化

  • SpringMVC 默认已经提供了一些常用的类型转换器,例如客户端提交的字符串转换成int型进行参数设置。

  • 但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如:日期类型的数据就需要自 定义转换器。

自定义类型转换器的开发步骤:

  1. 定义转换器类实现Converter接口

    package com.snake.converter;
    import org.springframework.core.convert.converter.Converter;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    /**
     * 自定义的日期转换器,
     * 让Spring接收字符串类型的日期时,
     * 自动帮我们转换我们想要的日期格式,而不是它转换的格式
     *
     * 步骤:
     * 1.定义转换类,实现Converter接口, 不要导错包!!!!!  org.springframework.core.convert.converter.Converter;
     *      Converter<String, Date> 代表,将字符串转换成日期类型, 前端发过来参数都是字符串类型的
     * 2.在SpringMVC配置文件中,将转换器加入到,转换器的集合中去
     */
    public class DateConverter implements Converter<String,Date> {
        public Date convert(String source) {
            //获得格式转换类
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            try {
                Date date = simpleDateFormat.parse(source);
                return date;
            } catch (ParseException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    
  2. 在SpringMVC配置文件中声明转换器

    <!--
        将自定义的日期转换器加入到ConversionServiceFactoryBean中的list集合去
    -->
        <bean id="converterService" class="org.springframework.context.support.ConversionServiceFactoryBean">
            <property name="converters">
                <list>
                    <bean class="com.snake.converter.DateConverter"/>
                </list>
            </property>
        </bean>
    
  3. 在SpringMVC配置文件中引用转换器

    <!--
        使用命名空间的mvc,自动帮我们配置处理适配器和处理映射器
        其中处理适配器,帮我们配置好了自动转换JSON数据,注意,它需要Jackson的工具jar包,fastjson的不行
    
        conversion-service="converterService" 是告诉它,我们配置了我们自己的转换器
    -->
    <mvc:annotation-driven conversion-service="converterService"/>
    

# 12.获得Servlet相关API

SpringMVC支持使用原始ServletAPI对象作为控制器方法的参数进行注入,常用的对象如下:

  • HttpServletRequest
  • HttpServletResponse
  • HttpSession
@RequestMapping("/quick16")
@ResponseBody
public void quickMethod16(HttpServletRequest request,HttpServletResponse response,HttpSession session){
				System.out.println(request);
				System.out.println(response);
				System.out.println(session);
}

# 13.获得请求头的数据

  1. @RequestHeader

    使用@RequestHeader可以获得请求头信息,相当于web阶段学习的request.getHeader(name) @RequestHeader注解的属性如下:

    • value:请求头的名称

    • required:是否必须携带此请求头

/**
 * 获取浏览器请求头的数据
 * 使用@RequestHeader注解
 * @RequestHeader ("User-Agent") 括号里面的字符串对象请求头里面的键值对
 */
@RequestMapping(value="/quick18")
@ResponseBody
public void save18(@RequestHeader("User-Agent") String user_agent) throws IOException {
    System.out.println(user_agent);
}

2.精确的获取请求头中Cookie里面的某个键值对

使用@CookieValue可以获得指定Cookie的值

@CookieValue注解的属性如下:

  • value:指定cookie的名称

  • required:是否必须携带此cookie

    /**
     * 同样是获取请求头里面的数据
     * 但是里面有一个是Cookie有一些特殊,因为Cookie里面也可以有键值对,
     * 如果通过直接使用@RequestHeader的方式获取Cookie的数据,就会获得一行 包含着许多键值对的数据
     * 我们可以通过@CookieValue注解精确的获得 Cookie中的字符串的某个键对应的数据
     *
     * 我们以获取Cookie中的JSESSIONID(Session与浏览器会话时用到的唯一标识id)为例
     */
    @RequestMapping(value="/quick19")
    @ResponseBody
    public void save19(@CookieValue("JSESSIONID") String jsessionid) throws IOException {
        System.out.println(jsessionid);
    }
    

# 14.文件上传

img

前端:

​ 文件上传客户端需要的三要素

  • 表单项的input标签的type="file"

  • 表单的提交方式是POST

  • 表单的enctype属性是多部分表单形式,及enctype="multipart/form-data",

    ​ 默认的表单是enctype="application/x-www-form-urlencoded",意思就是键值对的形式

# 1.单文件上传

步骤:

  1. 导入fileupload和io的坐标, commons-fileupload包里面 好像已经依赖了 commons-io包 ,所以不需要引入commons-io了

    <!--
        文件上传需要的依赖
        fileupload和io
    -->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.3</version>
    </dependency>
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.3</version>
    </dependency>
    
  2. 在SpringMVC中配置文件上传解析器

    <!--
         配置文件上传解析器
    id:multipartResolver
    id不能变,应该是要靠这个id来加载文件上传解析器
    -->
        <bean id="multipartResolver"
              class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--        上传文件总大小-->
            <property name="maxUploadSize" value="5242800"/>
    <!--        上传单个文件的大小-->
            <property name="maxUploadSizePerFile" value="5242800"/>
    <!--        上传文件的编码类型-->
            <property name="defaultEncoding" value="UTF-8"/>
        </bean>
    
  3. 编写文件上传代码

    前端代码:

    <form action="${pageContext.request.contextPath}/user/quick20" method="post" enctype="multipart/form-data">
        姓名:<input type="text" name="name"><br>
        文件:<input type="file" name="file"><br>
        <input type="submit" value="提交">
    </form>
    

    后端服务器:

/**
 * 练习文件上传,
 * 注意前端的表单上传的参数名字,
 * 需要与方法的形参的名字对应,这样SpringMVC才能帮我们正确的赋值,或者使用@RequestParm注解,进行映射
 * @throws IOException
 */
@RequestMapping(value="/quick20")
@ResponseBody
public void save20(@RequestParam("name") String username,@RequestParam("file") MultipartFile uploadFile) throws IOException {
    System.out.println("上传者:"+username);
    //获取文件上传时的名字
    String originalFilename = uploadFile.getOriginalFilename();
    //通过里面的transferTo方法,帮我们调用io流将文件保存到磁盘
    uploadFile.transferTo(new File("src/main/webapp/file/"+originalFilename));
    System.out.println("上传成功!");
}

# 2.多文件上传

​ 和单文件上传同理

​ 导入fileupload和io坐标,然后配置上传文件解析器,之后开始编写代码

在前端上传多个文件时,如果以file1,file2,file3..命名. 我们也需要再形参里定义MultipartFile file1,file2,file3

这样有些麻烦,但是如果前端是以同名的多个文件上传,我们在后端直接以MultipartFile 数据或者List集合接收就行

前端代码:

<form action="${pageContext.request.contextPath}/user/quick20" method="post" enctype="multipart/form-data">
    姓名:<input type="text" name="name"><br>
    文件1:<input type="file" name="file"><br>
    文件2:<input type="file" name="file"><br>
    文件3:<input type="file" name="file"><br>
    <input type="submit" value="提交">
</form>

后端代码:

/**
 * 练习多文件上传,第二个参数我们以集合或者数组的方式进行接收
 * @param username
 * @param uploadFile
 * @throws IOException
 */
@RequestMapping(value="/quick21")
@ResponseBody
public void save21(@RequestParam("name") String username,@RequestParam("file") MultipartFile[] uploadFile) throws IOException {
    System.out.println("上传者:"+username);
    for(MultipartFile file:uploadFile)
    {
        //获取文件上传时的名字
        String originalFilename = file.getOriginalFilename();
        //通过里面的transferTo方法,帮我们调用io流将文件保存到磁盘
        file.transferTo(new File("src/main/webapp/file/"+originalFilename));
        System.out.println("上传成功!");
    }
}

# 15.SpringMVC拦截器

# 15.1 拦截器(interceptor)的作用

SpringMVC的拦截器类似于Servlet开发中的过滤器Filter,用于处理器进行预处理后处理

将拦截器按一定的顺序联结成一条链,这条链称为拦截器链(Interceptor Chain)。在访问被拦截的方 法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。拦截器也是AOP思想的具体实现。

# 15.2拦截器和过滤器区别

img

# 15.3 拦截器快速入门

自定义拦截器很简单,只有如下三步

接口所在包:spring-webmvc-5.0.5.RELEASE.jar

  • 创建拦截器类,并实现HandlerInterceptor接口

    package com.snake.Interceptor;
    import org.springframework.lang.Nullable;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    /**
     * 自定义一个拦截器,并实现HandlerInterceptor接口
     * 重写三个方法,接口的三个方法都是default的,里面都是空的方法体
     * 即使我们不重写方法也不会报错,就是没有任何操作
     */
    public class myInterceptor implements HandlerInterceptor {
        /**
         * 这个方法在内部资源(方法)被访问之前调用, 进行拦截
         *  当返回true时,运行资源访问,既放行
         *  当返回false时,阻止资源的访问
         */
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            return true;
        }
        //这个方法在内部资源(方法)被访问后,要返回视图或者数据时之前,调用,执行一些操作
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        }
        //在返回视图或数据之后,调用,执行一些操作
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        }
    }
    
  • 配置拦截器

    <!--
        配置我们自定义的拦截器
    -->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--拦截路径, 拦截全局必须设置成'/**', 而不能设置成'/'!!-->
            <mvc:mapping path="/**"/>
            <!--自己配置的拦截器的字节码文件所在位置-->
            <bean class="com.snake.Interceptor.myInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
    
  • 测试拦截器的拦截效果

​ 控制台输出

img

# 15.4 多个拦截器的执行

​ 当同时配置同一个路径的多个拦截器时,按照在配置文件的声明的先后顺序,来指向,

如果拦截器1的声明在拦截器2的之前,则执行顺序为

<!--
    配置我们自定义的拦截器
    多个拦截器时,通过声明顺序决定谁先拦截,并且按照拦截器链的顺序执行对应的方法
-->
<mvc:interceptors>
    <!--        拦截器1-->
    <mvc:interceptor>
        <!--拦截路径, 拦截全局必须设置成'/**', 而不能设置成'/'!!-->
        <mvc:mapping path="/**"/>
        <!--自己配置的拦截器的字节码文件所在位置-->
        <bean class="com.snake.Interceptor.MyInterceptor1"/>
    </mvc:interceptor>
    <!--        拦截器2-->
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.snake.Interceptor.MyInterceptor2"/>
    </mvc:interceptor>
</mvc:interceptors>

各个方法的执行顺序 控制台输出 :

preHandle1... preHandle2... 内部资源被访问... postHandle2... postHandle1... afterCompletion2... afterCompletion1...

# 15.5 拦截方法说明

img

# 16.SpringMVC异常处理

# 1.1异常处理的思路

img

# 1.2异常处理两种方式

  • 使用SpringMVC提供的简单异常处理器SimpleMappingExceptionResolver

    <!--
       配置简单映射异常处理器
    -->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <!--        当抛出异常,但是异常处理器没有匹配的异常解决视图对应时,就会执行这个默认错误的处理
            value对应的是发生这个异常后,请求转发的视图, 前缀'/'和后缀'.jsp'被视图解析器给补上了
         -->
        <property name="defaultErrorView" value="defaultError"/>
        <!--
            我们手动设置具体匹配的异常如果抛出,返回的视图
        -->
        <property name="exceptionMappings">
            <map>
                <!--如果出现类型转换异常,返回到classCastError.jsp页面-->
                <entry key="java.lang.ClassCastException" value="classCastError"/>
                <!--我自定义的异常,如果抛出,返回到myError.sjp页面-->
                <entry key="com.itheima.exception.MyException" value="myError"/>
            </map>
        </property>
    </bean>
    
  • 实现Spring的异常处理接口HandlerExceptionResolver自定义自己的异常处理器

    1. 创建异常处理器类实现HandlerExceptionResolver

      /**
       * 自定义的异常处理器实现类
       */
      public class MyExceptionResolver implements HandlerExceptionResolver {
          public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
              //我们使用instanceof来判断异常对象 e 是否是某个异常的子类, 既异常e是否属于我们判断的异常
              ModelAndView modelAndView = new ModelAndView();
              if(e instanceof ClassCastException)
              {
                  //我们在request域中存在信息,到错误页面去显示
                  modelAndView.addObject("errorInfo","类型转换异常");
              }else if(e instanceof MyException)
              {
                  modelAndView.addObject("errorInfo","自定义异常");
              }else{
                  modelAndView.addObject("errorInfo","默认异常");
              }
              //设置视图
              modelAndView.setViewName("error");
              return modelAndView;
          }
      }
      
    2. 配置异常处理器

      <!--
          使用自定义的异常处理器, 只有将处理器配置成Bean加入到Spring容器就行
      -->
          <bean id="exceptionResolver" class="com.itheima.exceptionResolver.MyExceptionResolver"/>
      
    3. 编写异常页面

      <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      <html>
      <head>
          <title>Title</title>
      </head>
      <body>
      <h1>自定义异常处理器: ${errorInfo}</h1>
      </body>
      </html>
      
    4. 测试异常调整

# 17. @RequestParam , @RequestBody ,@PathVariable 注解

我在controller层传的参数是List时报No primary or single unique constructor found for interface java.util.List, 这是因为 List是一个接口, 不能实例化, 需要 使用@RequestParam注解, 进行修饰, 告诉他,帮我封装值就可以,
注解 支持的类型 支持的请求类型 支持的Content-Type 请求示例
@PathVariable url GET 所有 /test/{id}
@RequestParam url GET 所有 /test?id=1
@RequestBody Body POST/PUT/DELETE/PATCH json {“id”:1}

# 18. 前端传入字符串格式时间, 后端报错(字符串无法转换为Date)

需要在 接收时间的属性, 上添加 @DateTimeFormat注解, 并且 Date的类型 必须是 java.util.date下的, 注意这个注解是帮我们自动转换 GET方式, 也就是url上的时间参数, 如果是 请求体 中 JSON格式中的时间参数, 我们需要时 @JsonFormat 注解帮我们进行转换

img

# 19. Controller 使用Lombok的 @AllArgsConstructor 的注解 与 @Value 注解不能共用的问题

我们知道, 在一个Bean中 注入其他的Bean, 可以通过set方法注入, 构造的方法注入, 字段注入, 字段注入已经不被推荐使用原因是, 字段注入使用的是反射, 反射比构造器或者set慢一些.

我们通过 Lombok的 @AllArgsConstructor 注解, 可以帮我们快速生成这些字段的全参数构造器(猜测会帮我们在上面使用@Autowired 注解), 就变成了 构造的方法注入, 我们就没有必要给每一个字段使用@Autowired或者@Resource注解, 但是如果我们还需要注入 String类型时, 例如从Springboot的yaml文件中配置的值, 我们需要使用@value注解

但是@value 注解 与 @AllArgsConstructor 注解共同使用时报错, 说是找不到一个 String类型的Bean, 注入失败.

解决方法:

改用 Lombok 的 @RequiredArgsConstructor 注解, 这个注解只会为final修饰的字段属性,进行注入, 所以我们只要在String类型上使用@Value注解, 其他的类型则使用final进行修饰.

# 20. 在拦截器中返回消息乱码的问题

当我们使用拦截器的 HttpServletResponse 对象 getWriter().write() 方法返回消息时, 如果数据是包含中文的就会乱码

解决方式: 使用setCharacterEncoding() 方法,或者setContentType()方法. 注意, 编码格式不要写 成 utf-8, 会报错!

img

更新时间: 2024年5月26日星期日下午4点47分