Java【SpringMVC:响应封装、REST、异常处理】

文章目录

问答:
	请求携带日期类型的数据如何接收?
    请求参数名与形参名不一致时如何接收参数?
    如何获取请求携带的cookie的值?
    如何处理post请求的乱码问题?
    SpringMVC生成响应的方式有哪些?
    SpringMVC生成响应时如何共享数据?
    @RequestBody和@ResponseBody的区别?
    静态资源过滤的三种方式?
    什么是SpringMVC的统一异常处理机制? 如何实现?
    文件上传前端三要素是什么?
    
编程:
	响应的多种方式实现
    响应的多种方式返回值实现
    ajax请求与响应实现
    文件上传
    RestFul风格的路径实现
    

一、响应与封装返回值

SpringMVC返回值的方式可以分为两大类: 
 1.同步请求的响应:
请求转发 或 重定向

2.异步请求的响应
异步指的是前端的请求的方式:ajax
ajax请求直接返回响应结果字符串

1、SpringMVC中响应的多种方式

pom.xml配置

 <packaging>war</packaging>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.8</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.8</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.8</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- tomcat7插件,运行命令: mvn tomcat7:run -DskipTests -->
            <!-- 配置Tomcat插件 -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <uriEncoding>UTF-8</uriEncoding>
                    <port>8080</port>
                    <path>/</path>
                </configuration>
            </plugin>
        </plugins>
    </build>

web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
		  http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
           version="3.0">

    <!--配置springmvc提供的中文乱码过滤器-->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>
            org.springframework.web.filter.CharacterEncodingFilter
        </filter-class>
        <!-- 设置过滤器中的属性值 -->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
   <!--     <init-param>
            <param-name>forceResponseEncoding</param-name>
            <param-value>true</param-value>
        </init-param>-->
    </filter>
    <!-- 过滤所有请求 -->
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!--配置核心控制器(前端控制器/调度控制器/总控制器) servlet-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--配置springmvc资源的路径,dispatcherServlet初始化,就加载springmvc.xml资源-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <!--配置servlert加载时机-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!--配置映射规则-->
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!--拦截所有请求(除了jsp后缀请求外)-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

springmvc.xml

 <!--扫包加载bean资源-->
    <context:component-scan base-package="com.ahcfl"/>

    <!--配置视图解析器:将逻辑视图转换成物理视图-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--配置资源前缀-->
        <property name="prefix" value="/WEB-INF/pages/"/>
        <!--配置后缀-->
        <property name="suffix" value=".jsp"/>
    </bean>
    <!--开启mvc注解驱动-->
    <!--
        开启注解驱动后,可以使用springmvc扩展的注解
           @DateTimeFormat
           @RequestBody
           @ResponseBody
           。。。。。
    -->
    <mvc:annotation-driven/>

    <!--使用springmvc提供的默认决绝方式-->
    <mvc:default-servlet-handler/>

【1】原生API与SpringMVC【实现请求转发】
package com.ahcfl.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Controller
public class ForwardController01 {

    /**
     * 请求转发与重定向的区别:
     *      请求转发是一次请求的延续
     *      请求转发可以共享Request对象
     *      请求转发是服务器内部行为,只能转发到服务器内部的资源上
     *      请求转发时地址栏地址不变
     *      请求转发是Request的行为
     *
     *      重定向,改变方向发送多次请求
     *      重定向不可以共享Request对象
     *      重定向可以跳转到任何路径上
     *      重定向时浏览器地址栏显示最后一次请求的路径
     *      重定向是Response对象的行为
     *
     * SpringMVC的响应:
     *      同步响应:
     *          请求转发: 一次请求的延续,浏览器地址栏地址不会发生改变
     *              原生API: HttpServletRequest
     *              forward:物理视图路径
     *              逻辑视图 + 视图解析器
     *          重定向:
     *              原生API: HttpServletResponse
     *              redirect:重定向的物理逻辑
     *
     *      以流的形式写回给浏览器
     *              原生API: HTTPServletResponse
     *              @ResponseBody + String
     *                  @RequestMapping(value = "/handler07",
     *                  produces = "text/html;charset=utf-8")
     */
     
   /**1、原生API实现请求转发*/
    @RequestMapping("/forward01")
    public void basic(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("basic forward---->run");
        request.getRequestDispatcher("/m01").forward(request,response);
    }


    /**2、springmvc+视图解析器[默认的请求方式:请求转发]*/
    @RequestMapping("/forward02")
    public String forward02(){
        System.out.println("forward02---->run");
        return "success2";
    }


    /**3、forward:+路径
     *      路径的方式:
     *          逻辑视图
     *          物理视图的路径
     * */
    @RequestMapping("/forward03")
    public String forward03(){
        System.out.println("forward03-->run");
        return "forward:/forward04";  // "forward:/success3"; 逻辑转发只能转发给处理器,不能转发到页面
    }   
    
    @RequestMapping("/forward04")
    public String forward04(){
        System.out.println("forward04-->run");
        return "forward://WEB-INF/pages/success4.jsp";
    }



【2】原生API与SpringMVC【实现重定向】
package com.ahcfl.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Author: cfl
 * @CreateTime: 2021-05-30 15:19
 * @Description: 重定向
 * 1、原生API实现重定向   response.sendRedirect("/index.jsp");//jsp资源在WEB-INF之外
 * 2、redirect:+资源(可以是服务器内部资源+也可以是其他服务资源)
 *   方式1:服务器内部的jsp要在WEB-INF之外  return "redirect:/index.jsp";
 *   方式2:重定向到某一个handler下的某个访问方 return "redirect:/test06";
 *   方式3:重定向的外部服务器下 return redirect:http://www.baidu.com
 */
@Controller
public class RedirectController01 {

    @RequestMapping("redirect01")
    public void redirect01(HttpServletResponse response) throws IOException {
        System.out.println("redirect01-->run");
        response.sendRedirect("/success5.jsp");
    }

    @RequestMapping("redirect02")
    public String redirect02(){
        System.out.println("redirect02 run...");
        //方式1:服务器内部的jsp要在WEB-INF之外
        return "redirect:/success5.jsp";

    }

    @RequestMapping("redirect03")
    public String redirect03(){
        System.out.println("redirect03 run...");
        //方式2:重定向到某一个handler下的某个访问方法
        return "redirect:/redirect02";
    }

    @RequestMapping("redirect04")
    public String redirect04(){
        System.out.println("redirect04 run...");

        //方式3:重定向的外部服务器下
        return "redirect:http://www.baidu.com";
    }


}

    }
【3】原生API与SpringMVC 【以流的形式响应字符串】
    /**
     * 以流的形式写回字符串
     * mime常用类型:
     *          *.html  text/html
     *          *.js    text/JavaScript
     *          *.png   image/png
     *          *.json  application/json
     * 以流的形式写回字符串
     *      SpringMVC方式:
     *          @ResponseBody + String
     *  RequestMapping参数:
     *      produces: 设置响应参数的mime类型
     */
package com.ahcfl.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Author: cfl
 * @CreateTime: 2021-05-30 15:21
 * @Description:
 *  设置响应数据点的类型
 *  设置纯文本的格式
 *  response.setContentType("text/plain;charset=UTF-8");
 *  设置html格式
 *  response.setContentType("text/html;charset=UTF-8");
 *  设置json格式
 *  response.setContentType("application/json;charset=UTF-8");
 */
@Controller
public class ResponseStringController01 {

    /**
     * @return void
     * @Param [response]
     * @Description:
     * 使用远程API实现响应的字符串 【了解】
     */
    @RequestMapping(value = "/m01")
    public void method01(HttpServletResponse response) throws IOException {
        // 处理响应乱码
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write("<h1>你若安好,便是晴天霹雳</h1>");
    }

    /**
     * @Param:[]
     * @return:java.lang.String
     * @Description:
     * 使用springmvc实现响应字符串【开发主流方式】
     * produces : 指定响应字符串的数据类型,告诉浏览器解析字符串的规则
     */
    @RequestMapping(value = "/m02",produces = "text/html;charset=utf-8")
    public String method02() {
        System.out.println("method---->run");
        return "success1";
    }
}

了解几个常用的数据类型:

常用的Content-Type类型:
【text/html  :HTML格式】 
【text/plain :纯文本格式】      
text/xml   :XML格式

image/gif  :gif图片格式    
image/jpeg :jpg图片格式 
image/png  :png图片格式

application/xml     : XML数据格式
【application/json    : JSON数据格式】
application/pdf     : pdf格式  
application/msword  : Word文档格式
application/octet-stream : 二进制流数据(如文件下载)

【application/x-www-form-urlencoded : 
    <form encType="">中默认的encType,
    form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)】

【multipart/form-data : 表单上传文件】

2、响应时数据共享问题

请求转发:  数据都会存放到Request域中
重定向:    将共享的数据拼接在请求路径的后面
    
说明:
ModelAndViewModel是框架给我们准备好的,我们直接拿过来使用的即可,
我们称这些对象为【隐式对象】。
【1】使用request域对象封装响应结果
从http请求到服务器处理结束的整个过程中,多次请求转发过程中request域下的变量共享

**使用这种方式需要导入servlet的包 **
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.0.1</version>
</dependency>
public String demo1(HttpServletRequest request){
    request.setAttribute("user","admin");
    return "success";
}
【2】使用Model封装结果
public String demo2(Model model){
    model.addAttribute("user","admin");
    return "success";
}
【3】使用ModelAndView封装结果
public ModelAndView demo3(ModelAndView modelAndView){
    modelAndView.addObject("user","admin");
    modelAndView.setViewName("success");
    return modelAndView;
}
【4】代码演示

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>${msg}</h1>
</body>
</html>
【1】转发响应时共享数据

通过Request域,Model、ModelAndView都可实现;

package com.ahcfl.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;

@Controller
@RequestMapping("/params")
public class Demo3Controller {
    /**
     * 请求转发:
     *      携带数据给目标资源 -- HttpServletRequest
     * @return
     */
    @RequestMapping("/test01")
    public String test01(HttpServletRequest request){
        System.out.println("test01方法执行了...");
        request.setAttribute("msg","如今的现在早已不是曾经说好的以后");
        return "success";
    }

    /**
     * 请求转发:
     *      SpringMVC携带共享的资源
     *      Model : model对象是SpringMVC内置对象,当前对象可以直接使用
     *          当我们向model中存放数据后,最终model中的数据会转存到Request域中
     * @return
     */
    @RequestMapping("/test02")
    public String test02(Model model){
        System.out.println("test01方法执行了...");
        model.addAttribute("msg","你若安好,便是晴天");
        return "success";
    }

    /**
     * 请求转发:
     *      SpringMVC携带共享的资源
     *      ModelAndView: 封装视图和转发共享的数据
     * @return
     */
    @RequestMapping("/test03")
    public ModelAndView test03(ModelAndView modelAndView){
        System.out.println("test01方法执行了...");
        // 封装转发携带的数据
        modelAndView.addObject("msg","情绪是智慧不够的产物!");
        // 封装视图
        modelAndView.setViewName("success");
        return modelAndView;
    }
}
【2】重定向时共享数据

数据存放到session域下,会占用服务器内存空间;【不可取】

重定向时,request域下的数据不能共享,

但是使用Model或者ModelAndView可以自动将数据以参数的方式拼接到请求路径中。

	/**
     * 重定向-共享数据:
     *      如果需要重定向,SpringMVC会自动将Model中的数据
     *      拼接到重定向的路径后面
     * @param model
     * @return
     */
    @RequestMapping("/test04")
    public String test04(Model model){
        System.out.println("test04方法执行了...");
        model.addAttribute("msg","你若安好,便是晴天");
        return "redirect:/pages/success.jsp";
    }

    @RequestMapping("/test05")
    public ModelAndView test05(ModelAndView modelAndView){
        System.out.println("test05方法执行了...");
        // 封装转发携带的数据
        modelAndView.addObject("msg1","情绪是智慧不够的产物!");
        modelAndView.addObject("msg2","哈哈哈哈");
        // 封装视图
        modelAndView.setViewName("redirect:/pages/success.jsp");
        return modelAndView;
    }

3、异步请求的响应

测试环境:vue+springmvc

1、 使用Response 原生API响应结果
/**
 * 原生API生成响应信息
 * @param name
 * @param age
 * @param response
 * @throws IOException
 */
@RequestMapping("/demo1")
public void demo1(String name, Integer age, HttpServletResponse response)
        throws IOException {
    response.setContentType("application/json;charset=utf-8");
    System.out.println("demo1方法执行了.."+name+" : "+age);
    response.getWriter().print("hello..."+name);
}
2、使用@ResponseBody注解响应结果
【1】响应普通字符串(逻辑视图名)

避免视图解析,直接响应字符串数据(返回普通字符串)

    /**
     * 使用原生API响应前端的异步请求[了解]
     * @param response
     */
    @RequestMapping("/demo01")
    public void demo01(HttpServletResponse response) throws IOException {
        response.setContentType("text/plain;charset=utf-8");
        response.getWriter().write("一直敲一扇不愿意为你开的门是不礼貌的");
    }

    /**
     * springmvc实现
     * @param
     * @throws IOException
     */
    @RequestMapping(value = "/demo02",produces = "text/plain;charset=utf-8")
    @ResponseBody
    public String demo02() {
        return  "一直敲一扇不愿意为你开的门是不礼貌的";
    }

    @RequestMapping(value = "/demo03",produces = "text/plain;charset=utf-8")
    @ResponseBody
    public String demo03(String name,String age) {
        return  "name:"+name+",age:"+age;
    }

【2】响应json字符串

先要导入JSON转换需要的包

<dependency> 
    <groupId>com.fasterxml.jackson.core</groupId> 
    <artifactId>jackson-core</artifactId> 
    <version>2.9.8</version> 
</dependency> 
<dependency> 
    <groupId>com.fasterxml.jackson.core</groupId> 
    <artifactId>jackson-databind</artifactId> 
    <version>2.9.8</version> 
</dependency> 
<dependency>
	<groupId>com.fasterxml.jackson.core</groupId> 
    <artifactId>jackson-annotations</artifactId> 
    <version>2.9.8</version> 
</dependency>

==========将返回结果转成json字符串返回给浏览器===========
  /**
     * json数据的序列化和反序列化
     * -- json的反序列化
     *  "{'name:'"+userVO.getName()+",'age:'"+userVO.getAge()+"}"---》UserVo
     *  -- 序列化
     *  UserVo----》"{'name:'"+userVO.getName()+",'age:'"+userVO.getAge()+"}"
     * @param userVo
     * @return
     */
    //@RequestMapping(value = "/demo04",produces = "application/json;charset=utf-8")
    @RequestMapping(value = "/demo04")
    @ResponseBody
    public UserVo demo03(UserVo userVo) {
        System.out.println(userVo);
        //  return  "{'name:'"+userVO.getName()+",'age:'"+userVO.getAge()+"}";
        // 序列化【可以直接将对象转成json字符串写回给浏览器】
        return  userVo;
    }
    /**
     *      响应json格式的数据给浏览器
     *      @ResponseBody + 对象
     *          【可以直接将对象转成json字符串写回给浏览器】
     * @RequestBody 获取请求体中的数据并反序列化成Vo对象
     * @ResponseBody 将对象序列化成json格式的字符串
     */
    @RequestMapping(value = "/demo05",method = RequestMethod.POST)
    @ResponseBody
    public UserVo demo04(@RequestBody UserVo userVo) {
        System.out.println(userVo);
        // return  "{'name:'"+userVO.getName()+",'age:'"+userVO.getAge()+"}";
        return  userVo;
    }

    @RequestMapping(value = "/demo06",method = RequestMethod.POST)
    @ResponseBody
    public UserVo demo04(String name, Integer age) {
        UserVo userVO = new UserVo();
        userVO.setAge(age);
        userVO.setName(name);
        System.out.println(userVO);
        //return  "{'name:'"+userVO.getName()+",'age:'"+userVO.getAge()+"}";
        return  userVO;
    }

3、@RequestBody和@ResponseBody的区别
【1】@RequestBody
1.@RequestBody核心是接收客户端发送的json格式字符串,并封装到某个vo下【反序列化过程】
2.get请求是没有请求体,而请求携带的json格式数据必须存放在请求体中携带给服务器
3.编写位置: 参数列表中参数的前面
作用:
  在post请求时,获取请求携带的json字符串,自动解析json字符串,
  并将解析到的内容封装到对应的java对象中(导入Jackson的jar包)
【2】@ResponseBody
@ResponseBody:
  作用1: 以流的形式写回字符串给浏览器
  作用2: 将对象转成json字符串,将转完后的字符串以流的形式写回给浏览器
    需要设置返回数据的mime类型: produces = "application/json;charset=utf-8"
4、小结
注意事项:
在SpringMvc配置文件中添加
<mvn:default-servlet-handler/>  防止访问静态资源时被核心控制器拦截;
# 面试题:@RequestBody和@ResponseBody都是用来干什么的?

@RequestBody : 使用在post请求中
1.获取请求体的参数(获取的是一个字符串)
2.获取请求携带的json格式字符串,并解析json格式的字符串,封装到对象中
3.使用在参数的前面

@ResponseBody: 生成响应字符串的
1.直接返回一个普通字符串
2.如果返回的是对象,则将对象转出json字符串并返回
3.使用在方法上或方法的返回值前面
【前端代码】
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="js/vue.js"></script>
    <script src="js/axios.min.js"></script>
</head>
<body>
    <h1>发送ajax请求</h1>
    <!-- V:视图 -->
    <div id="app">
        用户名: <input type="text" v-model="user.username"> <br>
        年龄: <input type="text" v-model="user.age"> <br>
        <button @click="sendAjax1()">get方式-发送ajax请求1</button> <br>
        <button @click="sendAjax2()">get方式-发送ajax请求2</button> <br>
        <button @click="sendAjax3()">post方式-发送ajax请求3</button> <br>
        <button @click="sendAjax4()">post方式-发送ajax请求4</button> <br>
        <button @click="sendAjax5()">post方式-发送ajax请求5-返回json格式的数据</button> <br>
        <br>
        <h3>{{msg}} == {{resultUser.username}} == {{resultUser.age}}</h3>
    </div>
</body>
</html>
<script>
    new Vue({
        el:"#app", // 将vue对象挂载到指定的视图上
        data:{ // model,存放数据的位置
            msg:"默认值",
            user:{
                username:"",
                age:0
            },
            resultUser:{

            }
        },
        methods:{
            sendAjax1(){
                // 发送ajax请求
                //alert("函数执行了,即将发送ajax请求...");
                let url = "/ajax/test01";
                axios.get(url,{
                    params: this.user
                }).then(resp=>{
                    this.msg = resp.data;
                });
            },
            sendAjax2(){
                // 发送ajax请求
                //alert("函数执行了,即将发送ajax请求...");
                let url = "/ajax/test02";
                axios.get(url,{
                    params: this.user
                }).then(resp=>{
                    this.msg = resp.data;
                });
            },

            sendAjax3(){
                // axios的post请求默认携带的参数类为 json 字符串
                // 发送ajax请求
                //alert("函数执行了,即将发送ajax请求...");
                let url = "/ajax/test03";
                axios.post(url,this.user).then(resp=>{
                    this.msg = resp.data;
                });
            },
            sendAjax4(){
                // axios的post请求默认携带的参数类为 json 字符串
                // 发送ajax请求
                //alert("函数执行了,即将发送ajax请求...");
                let url = "/ajax/test04";
                axios.post(url,this.user).then(resp=>{
                    this.msg = resp.data;
                });
            },
            sendAjax5(){
                // axios的post请求默认携带的参数类为 json 字符串
                // 发送ajax请求
                //alert("函数执行了,即将发送ajax请求...");
                let url = "/ajax/test05";
                axios.post(url,this.user).then(resp=>{
                    console.log(resp.data);
                    this.resultUser = resp.data;
                });
            }
        }
    });
</script>
【后台代码】
package com.itheima.controller;

import com.itheima.vo.UserVo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/ajax")
public class Demo4Controller {

    @RequestMapping(value = "/test01",produces = "text/html;charset=utf-8")
    @ResponseBody
    public String test01(String username,Integer age){
        System.out.println("test01方法执行了"  + username +" : "+age);
        return "你好美女";
    }

    @RequestMapping(value = "/test02",produces = "text/html;charset=utf-8")
    @ResponseBody
    public String test02(UserVo userVo){
        System.out.println("test02方法执行了"  + userVo);
        return "你好美女";
    }

    /**
     * 获取请求携带的json字符串,将字符串存放到一个变量中
     * @RequestBody:
     *      获取请求体中的字符串
     * @param str
     * @return
     */
    @RequestMapping(value = "/test03",produces = "text/html;charset=utf-8")
    @ResponseBody
    public String test03(@RequestBody String str){
        System.out.println("test03方法执行了");
        System.out.println(str);
        return "你好浏览器";
    }
    /**
     * 获取请求携带的json字符串,将字符串存放到一个变量中
     * @RequestBody:
     *      注意: 需要导入jackson的jar包
     *      解析请求体中的json字符串,并将数据封装到vo对象中
     * @param userVo
     * @return
     */
    @RequestMapping(value = "/test04",produces = "application/json;charset=utf-8")
    @ResponseBody
    public String test04(@RequestBody UserVo userVo){
        System.out.println("test04方法执行了");
        System.out.println(userVo);
        return "你好浏览器";
    }

    /**
     * 响应数据:
     *      响应json格式的数据给浏览器
     *      @ResponseBody + 对象
     *          可以直接将对象转成json字符串写回给浏览器
     * @param userVo
     * @return
     */
    @RequestMapping(value = "/test05",produces = "application/json;charset=utf-8")
    @ResponseBody
    public UserVo test05(@RequestBody UserVo userVo){
        System.out.println("test04方法执行了");
        System.out.println(userVo);
        userVo.setAge(21);
        userVo.setUsername("笑雯");
        return userVo;
    }
}

二、RESTFul风格的路径

1、什么是REST

Rest( Representational State Transfer 即表述性状态传递) 
一种网络资源的访问风格,定义了网络资源的访问方式;

传统风格访问路径
     http://localhost/user/getUser?id=1
     http://localhost/deleteUser?id=1

Rest风格访问路径
     http://localhost/user/1        请求方式:get
     http://localhost/user/1        请求方式:delete

Restful是按照Rest风格访问网络资源

优点
   <font color='red'>隐藏资源的访问行为</font>,通过地址无法得知做的是何种操作
   书写简化

2、REST行为约定方式

 GET(查询) http://localhost/user/1 GET
 POST(保存) http://localhost/user POST
 PUT(更新) http://localhost/user PUT
 DELETE(删除) http://localhost/user DELETE

注意:
1.上述行为是约定方式,约定不是规范,可以打破,所以称Rest风格,而不是Rest规范。
2.RESTFul就是路径编写的另一种格式,
这种格式面向的是资源(核心思想:资源即路径),而非具体的功能。
3.在springmvc中使用@PathVariable注解就可以从Rest风格的URL中获取参数的值。

3、代码演示

   /**
     * 
     * @PathVariable: 
     */
package com.ahcfl.controller;

import com.ahcfl.vo.UserVo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

/**
 * @Author: cfl
 * @CreateTime: 2021-05-30 15:22
 * @Description:
 * rest风格:
 *  1.路径即资源;
 *  2.使用http请求的方法表达对资源的操纵行为;
 *  注解 @RestController 等价于@Controller + @ResponseBody 加入IOC和获取参数
 *  注解 @PathVariable("")  绑定URL中的参数值 解析请求路径中的值,并将    值赋给形参变量
 * 请求路径中 {占位符} 接收请求路径中当前位置的值
 */
//@Controller
@RestController
@RequestMapping("/user")
public class RestFulController01 {

    //@RequestMapping(value = "/user/{userName}/{userAge}",method = RequestMethod.GET)
    // url: http://localhost:8080/user/ahcfl/16
    @GetMapping("/{userName}/{userAge}")
    //@ResponseBody
    public UserVo getUserVo1(@PathVariable("userName") String name,
                            @PathVariable("userAge") Integer age){
        System.out.println("GET");
        UserVo userVo = new UserVo();
        userVo.setAge(age);
        userVo.setName(name+"GET");
        return userVo;
    }

    @PostMapping("/{userName}/{userAge}")
    //@ResponseBody
    public UserVo getUserVo2(@PathVariable("userName") String name,
                            @PathVariable("userAge") Integer age){
        System.out.println("name = " + name + ", age = " + age);
        UserVo userVo = new UserVo();
        userVo.setAge(age);
        userVo.setName(name+"POST");
        return userVo;
    }


    //@RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
    @DeleteMapping(value = "/{id}")
    //@ResponseBody
    public String delete(@PathVariable Integer id){
        System.out.println("id="+id+"的用户被删除了");
        return "ok!!!";
    }

    //等价于@RequestMapping+method = RequestMethod.PUT
    @PutMapping("/{id}/{userName}/{userAge}")
    //@ResponseBody
    public UserVo updateUserVO(@PathVariable("userName") String name,
                               @PathVariable("userAge") Integer age,
                               @PathVariable("id") Integer id){
        System.out.println("id="+id+"的用户被修改了");
        UserVo userVo = new UserVo();
        userVo.setName(name+"PUT");
        userVo.setAge(age);
        return  userVo;
    }
}

}

三、静态资源映射

静态资源访问不到的原因

在这里插入图片描述

总之,通过资源URL 根据处理器映射器找不到对应的Handler处理器

【方案一】配置web.xml

激活Tomcat的defaultServlet来处理静态文件 ==》用原生servlet方式在web.xml中配置:

      <!-- 配置静态资源处理 -->
      <servlet-mapping>
          <servlet-name>default</servlet-name>
          <url-pattern>*.html</url-pattern>
          <url-pattern>*.css</url-pattern>
          <url-pattern>*.js</url-pattern>
          <url-pattern>*.png</url-pattern>
      </servlet-mapping>
【方案二】配置springmvc.xml

在springmvc.xml文件中配置

< mvc:resources mapping="匹配请求路径" location="真实路径">  
<!-- 设置静态资源不过滤 -->
	<mvc:resources mapping="/css/**" location="/css/" />  <!-- 样式 -->
	<mvc:resources mapping="/images/**" location="/images/" />  <!-- 图片 -->
	<mvc:resources mapping="/js/**" location="/js/" />  <!-- javascript -->
	<mvc:resources mapping="/html/**" location="/html/" />  <!-- html资源 -->
【方案三】配置springmvc.xml【推荐】

在springmvc.xml文件中配置

    <!-- 统一配置 ★ -->
	<!-- 代替以上所有的配置 -->
	<mvc:default-servlet-handler/>

四、异常处理机制

1、异常概念

【1】异常分类

在这里插入图片描述

Throwable类有两个直接子类:

​ Exception:出现的问题是可以被捕获的;

​ Error:系统错误,通常由JVM处理

可捕获的异常又可以分为两类:

​ check异常:直接派生自Exception的异常类,必须被捕获或再次声明抛出

​ runtime异常:派生自RuntimeException的异常类。使用throw语句可以随时抛出这种异常对象:

【2】异常处理

在编写代码处理异常时,对于检查异常,有2种不同的处理方式:

​ 使用try…catch…finally语句块处理它。

​ 在函数签名中使用throws 声明交给函数调用者caller去解决

# 1trycatchfinally语句块 

try{
     //try块中放可能发生异常的代码。
}catch(Exception exception){
    //发生异常时候处理的方式
    //如果try中没有发生异常,则所有的catch块将被忽略。
}finally{
   //无论异常是否发生,异常是否匹配被处理,finally都会执行。
  //finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。 
}
#  2throw exceptionObject 
    
public void save(User user){
      if(user  == null) 
          throw new IllegalArgumentException("User对象为空");
      //......
}

2、springMVC异常处理源码

在最初说springMVC执行流程时候,我们知道整个流程如下:
在这里插入图片描述

也就是说调用处理器映射器==>处理器适配器==>视图解析器的整个过程中,

M层(service、dao)发生异常时候,我们把异常throw出来,在抛到dispatcherServlet的时候

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
......	
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
......		
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {
		if (exception != null) {
			if (exception instanceof ModelAndViewDefiningException) {
				logger.debug("ModelAndViewDefiningException encountered", exception);
				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
			}
			else {
                
                =========处理异常===========    
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}
		.......
	}

最后调用HandlerExceptionResolver异常处理器来处理。

@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) throws Exception {

========// Success and error responses may use different content types======
		request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

=======// Check registered HandlerExceptionResolvers...========
		ModelAndView exMv = null;
		if (this.handlerExceptionResolvers != null) {
			for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
				exMv = resolver.resolveException(request, response, handler, ex);
				if (exMv != null) {
					break;
				}
			}
		}
		if (exMv != null) {
			if (exMv.isEmpty()) {
				request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
				return null;
			}
======//We might still need view name translation for a plain error model...====
			if (!exMv.hasView()) {
				String defaultViewName = getDefaultViewName(request);
				if (defaultViewName != null) {
					exMv.setViewName(defaultViewName);
				}
			}
			if (logger.isTraceEnabled()) {
				logger.trace("Using resolved error view: " + exMv, ex);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Using resolved error view: " + exMv);
			}
			WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
			return exMv;
		}

		throw ex;
	}

由此可以提供一个统一处理的思路

1、dao->service->controller逐层向上抛出异常
	不建议在dao、service使用try catch
	如果dao、service使用try catch,必须抛出异常
2、自定义一个实现HandlerExceptionResolver接口的类统一处理
	解析异常信息==》自定义异常类
	跳转错误提示页面==》错误统一处理的页面(错误码、错误信息)

3、为什么使用异常处理机制

【1】在我们项目中避免不了有异常的情况,当异常的情况发生时,我们不能将异常信息直接返回给用户
(浏览器),	采用友好的方式告知用户. 告知的方式就是我们要用的统一异常处理;

【处理方式】
项目中所有的异常都可以向上抛出,最终抛给异常处理类,
然后在异常处理类中定义友好的提示信息,并返回给用户.

【2】在Java中所有的异常最终的归宿都是被抓取的。
抓取后进行处理,而对于异常的处理一般有两种方式: 
一种是当前方法处理,这种处理方式会造成业务代码和异常处理代码的耦合。 
另一种是抛给调用者处理,在这种方法的基础上,衍生除了SpringMVC的异常处理机制。 
由于我们的代码最后都是有Spring框架来调用的,所以我们可以直接将异常抛给框架,然后由框架统一处理。 

4、 统一异常处理实现

SpringMVC的异常处理思路:【所有异常都往上抛,然后指定一个统一的异常处理器进行处理】

【方式一】基于HandlerExceptionResolver接口实现
自定义异常处理器
 a).编写一个类,实现 HandlerExceptionResolver 接口
 b).重写 resolveException方法:完成异常处理
        1.跳转到公共的错误页面
        2.携带错误信息
        返回:ModelAndView
           addObject:  携带参数
           setViewName:指定页面跳转
 c).配置异常处理器(交给spring容器管理)
<bean id="exceptionResolver" class="com.itheima.exception.MyHandlerExceptionResolver" />	
或者使用注解加入spring的ioc容器中;

代码实现:

自定义异常处理类
package com.ahcfl.resolver;

import com.itheima.exception.ProjectException;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
    /**
     * @param request : 请求对象
     * @param response : 响应对象
     * @param handler : 处理器对象(哪个处理器方法抛出的异常)
     * @param ex : 抛出的异常是什么
     * @return
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("msg",ex.getMessage());
        // 设置视图 转发到error.jsp
        modelAndView.setViewName("forward:/pages/error.jsp");
        return modelAndView;
    }
}

测试类:

package com.ahcfl.controller;

import com.itheima.exception.ProjectException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ExceptionController {

    @RequestMapping("/testException")
    public String testException() throws Exception{
            System.out.println(1/0);
        return "success";
    }
}
【方式二】注解开发异常处理器(推荐)
  • 使用注解实现异常分类管理
     名称: @ControllerAdvice
     类型: 类注解
     位置:异常处理器类上方
     作用:设置当前类为异常处理器类
@Component
@ControllerAdvice
public class ExceptionAdvice {
}  
  • 使用注解实现异常分类管理
     名称: @ExceptionHandler
     类型: 方法注解
     位置:异常处理器类中针对指定异常进行处理的方法上方
     作用:设置指定异常的处理方式
     说明:处理器方法可以设定多个
@ExceptionHandler(Exception.class)
@ResponseBody
public String doOtherException(Exception ex){
   return "出错啦,请联系管理员! ";
}  

5、理想的异常处理解决方案

【1】异常处理方案
  • 业务异常:
     发送对应消息传递给用户,提醒规范操作
  • 系统异常:
     发送固定消息传递给用户,安稳用户
     发送特定消息给运维人员,提醒维护
     记录日志
  • 其他异常:
     发送固定消息传递给用户,安稳用户
     发送特定消息给编程人员,提醒维护
     纳入预期范围内
     记录日志
【2】自定义异常
1.以业务异常为例说明
//自定义异常继承RuntimeException,覆盖父类所有的构造方法
public class BusinessException extends RuntimeException {
    public BusinessException() {
    }

    public BusinessException(String message) {
        super(message);
    }

    public BusinessException(String message, Throwable cause) {
        super(message, cause);
    }

    public BusinessException(Throwable cause) {
        super(cause);
    }

    public BusinessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}
2.定义分类异常管理器
@Component
@ControllerAdvice
public class MyExceptionAdvice {
    
    @ExceptionHandler(BusinessException.class)
    public ModelAndView handlerException(BusinessException exception){
        ModelAndView mv = new ModelAndView();
        mv.setViewName("forward:/pages/error.jsp");
        mv.addObject("msg",exception.getMessage());
        return mv;
    }


//    @ExceptionHandler(otherDefinerException.class)
//    public ModelAndView handlerException(BusinessException exception){
//        ModelAndView mv = new ModelAndView();
//        mv.setViewName("forward:/pages/error.jsp");
//        mv.addObject("msg",exception.getMessage());
//        return mv;
//    }

}
3.触发业务异常示例
@Controller
public class TestExceptionController {
    @GetMapping("/user")
    public String getUser(String name){
        //举例业务异常说明
        if(name.trim().length()<4 ){
            throw  new BusinessException("用户名长度必须在2-4位之间,请重新输入!");
        }
        return "hello";
    }
}

总之,通过自定义异常将所有的异常现象进行分类管理,以统一的格式对外呈现异常消息

五、SpringMVC文件上传

案例需求:用户通过客户端,将用户本地文件传递到服务器上,并在服务器上将用户上传的文件进行保存;

1、文件上传三要素

【1】请求方式必须是Post ,因为get请求携带的数据量受限

【2】form表单的enctype取值必须是:multipart/form-data

 form默认content-type是:application/x-www-form-urlencoded
 
enctype属性:是表单请求正文的类型
值:multipart/form-data 【多部分表单项】
    作用:
     1.将普通表单项与文件上传项分离,普通表单项可以直接被解析.
     2.而上传文件项需要对上传的文件进行解析,并保存文件

【3】提供一个文件输入框

    <form method="post" enctype="multipart/form-data">
          用户名: <input  type="text" name="username"/> 【普通表单项】
      密码: <input type="text" name="password" /> 【普通表单项】
        头像: <input type="file" name="filename" />   【上传文件项】
          <input type="submit" value="提交表单" />  
    </form>

2、代码实现

1.导入Fileupload的jar包
 	<!--普通文件上传-->
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.1</version>
    </dependency>
2.配置文件解析器

配置到springmvc.xml文件中

	<!-- 配置文件上传解析器 id为固定值,不能变-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--最大文件大小,单位字节 5m= 1024*1024*5-->
        <property name="maxUploadSize" value="5000000"/>
        <!--指定默认编码格式-->
        <property name="defaultEncoding" value="UTF-8"/>
    </bean>
3.编写前端页面(发送上传文件的请求)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <form action="" method="post" enctype="multipart/form-data">
        <%-- TODO:普通表单项 --%>
        用户名: <input type="text" name="username" value="tom"> <br>
        <%-- TODO:普通表单项 --%>
        年龄: <input type="text" name="age" value="18"> <br>
        <%-- TODO:文件上传项 --%>
        头像: <input type="file" name="filename"> <br>
        <input type="submit" value="提交表单">
    </form>
</body>
</html>

4.上传代码(单文件)
package com.ahcfl.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import java.util.UUID;

@RequestMapping("/file")
@Controller
public class FileUploadController {

    /**
     * @param username : 普通表单项
     * @param age : 普通表单项
     * @param filename : 文件上传项,在此对象中存放有上传的文件的所有信息
     * @return
     */
    @RequestMapping("/fileUpload")
    public String handler01(String username, Integer age, MultipartFile filename) throws IOException {
        System.out.println(username+" : "+age);
        // 文件对象,需要处理
        // 获取上传文件向的名称  name="filename"(获取name属性的值)
        String name = filename.getName();
        System.out.println(name);
        
        // 获取上传的文件的名称
        String originalName = filename.getOriginalFilename();
        System.out.println(originalName);
        
        // 截取文件的扩展名
        int startIndex = originalName.lastIndexOf(".");
        String fileStr = originalName.substring(startIndex, originalName.length());
        System.out.println(fileStr);
        
        // 生成唯一的文件名称   java.util.UUID
        originalName = UUID.randomUUID().toString()+fileStr;
        
        // 将上传的文件保存到本地
        File file = new File("G:/upload116/"+originalName);
        filename.transferTo(file);
        return "success";
    }
}
5、上传代码(多文件)
@RequestMapping("/fileUpload1")
    public String handler02(String username, Integer age, MultipartFile[] filename) throws IOException {
        System.out.println(username+" : "+age);
        for (MultipartFile multipartFile:filename) {
            // 文件对象,需要处理
            // 获取上传的文件的名称
            String originalName = multipartFile.getOriginalFilename();
            System.out.println(originalName);
            
            // 截取文件的扩展名
            String fileStr = originalName.substring(originalName.lastIndexOf("."), originalName.length());
            System.out.println(fileStr);
            
            // 生成唯一的文件名称
            originalName = UUID.randomUUID().toString()+fileStr;
            // 多文件夹存储图片
            Random random = new Random();
            int i = random.nextInt(10);
            int j = random.nextInt(10);

            File file = new File("G:/upload116/" + i + "/" + j);
            if (!file.exists()){
                // 创建多层文件夹
                file.mkdirs();
            }
            // 将上传的文件保存到本地
            File file1 = new File("G:/upload116/"+i+"/"+j+"/"+originalName);
            multipartFile.transferTo(file1);
        }

        return "success";
    }

总结

特殊的请求参数:
  日期类型: @DateTimeFormat(pattern="日期格式")
			自定义类型转换器
  中文乱码: 过滤器
  请求参数名与形参名称不一致:
			@RequestParam("请求参数名")
  获取请求头: @RequestHeader("头名称")
  获取cookie的值: @CookieValue("Cookie的名称")


响应:
	请求转发:
	  String + forward:物理视图路径
      String + 逻辑视图 + 视图解析器
  重定向:
			String + redirect:物理视图路径
  以流的形式写回字符串:
			@ResponseBody + String
      注解写在方法上,用于描述当前方法返回的内容需要放在响应体中
      以流的形式将结果返回
请求转发和重定向如何携带数据?
      原生API
      Model: 模型
      ModelAndView: 模式和视图
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程小栈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值