SpringMVC第二讲

SpringMVC第二讲

回顾

SpringMVC的执行流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6M3dtSz0-1611460453603)(images\SpringMVC执行流程.png)]

SpringMVC:基于xml配置和基于Java都算主流,但是Java配置的目前主流(SpringBoot默认不使用xml)

课程目标:

掌握基于SpringMVC环境的JSON操作(掌握Json常用注解)

掌握RestFul风格请求(前端定义和后端定义处理方式)

掌握SpringMVC文件上传(两种方式)

1. SpringMVC-JSON操作

目前处理JSON操作的主流工具:

jackson(使用最多,SpringMVC底层默认支持)

fastjson(号称执行最快,但是bug最多,阿里巴巴公司提供)

goson(谷歌,一般在app开发中使用较多)

Spring底层对JackSon和goson都提供自动配置(也就是只需要添加jar就可以使用了):顺序是先加载jackson,后加载goson

org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter,构造函数中定义对转换器的加载

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CQgr6QkO-1611460453606)(images/1585362724931.png)]

1.1 @RequestBody

将JSON字符串转换Java(对象)数据

在处理器方法参数前面使用,标识参数的,用于读取请求时传入的json字符串通过SpringMVC的转换器将读到的内容转为Java数据,并绑定赋值给被标识的方法参数

public String show(@RequestBody User user){}

1.2 @ResponseBody

将Java数据转换为Json字符串返回

在需要返回JSON数据的处理器方法上添加注解:@ResponseBody

将方法返回的值数据(简单类型,对象类型,集合类型),通过底层转换器转为指定数据格式json字符串返回

@ResponseBody
public User show(){}

提示:如果当前处理器类所有方法都需要返回JSON数据时,可以将@ResponseBody注解添加在类上,这样方法上就可以不标识注解

补充:使用该注解标识的方法就不会通过视图解析器解析,哪怕方法返回值类型不是void,如:返回值类型为String,也等同于方法的是void

@RequestMapping("/xxxx")
@ResponseBody
public String show(){
	return "hello";
}
//以上处理器方法执行完,不是跳转hello.jsp,而是响应"hello"的json字符串数据

1.3 后端 JSON操作配置

**前提:**后端想要使用JSON操作注解,那么必须在springmvc-servlet.xml配置文件中配置:

<mvc:annotation-driven/>

以上配置本次包含的作用:加载支持JSON注解的消息转换器

1.3.1 HttpMessageConverter消息转换器:用于前端和后端的JSON数据转换(JSON字符串和Java数据之间转换)

Jackson和FastJSON使用区别:(JackSon用得多一些,只需要添加要一个jar包就行了,而fastjson需要添加一个jar包,还需要在springmvc-servlet.xml文件中配置)

1):使用JackSon作为Json转换器

那么只需要将Jar添加到项目中就好,SpringMVC底层已经完成了对JackSon转换器的配置

Jacksonjar依赖:

	<!--json支持-->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.11.0</version>
    </dependency>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9WxJlvcZ-1611460453607)(images/1590200621423.png)]

如果不是Maven环境,那么需要添加3个jar

SpringMVC可以将任意的类型进行数据转换,常见将对象类型转换为Json格式返回,或者将json格式的字符串转换为对象

使用方式:

使用注解@ResponseBody,将方法返回值作为JSON格式字符串返回

使用注解@RequestBody,将前端请求是传入的JSON格式数据转换为Java数据赋值参数

2):使用FastJson作为Json转换器

FastJSON依赖

	<dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.58</version>
    </dependency>

还需要在SpringMVC的配置文件中配置:

注册转换器

	<mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=UTF-8</value>
                        <value>application/json;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

(如果是使用java类的方式)或者在配置类中添加:

	@Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        converter.setDefaultCharset(Charset.forName("UTF-8"));
        FastJsonConfig config = new FastJsonConfig();
        config.setCharset(Charset.forName("UTF-8"));
        converter.setFastJsonConfig(config);
        converters.add(converter);
    }

使用方式:和基于Jackson一致

1.4 使用测试

准备实体:

public class User {
    private String username;
    private String password;
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

1.4.1 响应JSON数据:(从后端往前端响应json格式字符串)

方法上中使用**@ResponseBody**注解,将前端转入的JSON格式字符串转换为Java数据

编写处理方法

package com.yaorange.handler;

import com.yaorange.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

//如果处理器类方法既有返回json的,也有跳转视图的,那么就还是只使用@Controller,在需要返回json的方法上使用@ResponseBody
//如果明确整个处理器类中的方法都返回json时在类上使用@Controller同时使用@ResponseBody
@Controller
@ResponseBody//当前类中方法都返回JSON数据
public class JSONResponseHandler {

    @RequestMapping(value = "/getJsonString",produces = "text/html;charset=UTF-8")
//    produces = "text/html;charset=UTF-8"//设置响应内容
//    @ResponseBody//标识方法字符串返回值作为json字符串返回
    public String jsonString(){
       return "这是json字符串返回";
    }

    @RequestMapping("/getJsonObject")
//    @ResponseBody//标识方法对象返回值作为json对象字符串返回
    public User jsonObj(){
        User user = new User();
        user.setUsername("zhangsan");
        user.setPassword("zs123");
        return user;
    }

    @RequestMapping("/getJsonObjList")
//    @ResponseBody//标识方法集合返回值作为json对象数组字符串返回
    public List<User> jsonObjList(){
        User user = new User();
        user.setUsername("zhangsan");
        user.setPassword("zs123");
        User user1 = new User();
        user1.setUsername("lisi");
        user1.setPassword("ls123");
        List<User> users = new ArrayList<>();
        users.add(user);
        users.add(user1);
        return users;
    }

    @RequestMapping("/getJsonStringList")
//    @ResponseBody//标识方法集合返回值作为json数组字符串返回
    public List<String> jsonStringList(){
        List<String> users = new ArrayList<>();
        users.add("one");
        users.add("two");
        return users;
    }
}

测试地址:

http://localhost:8080/getJsonString
http://localhost:8080/getJsonObject
http://localhost:8080/getJsonObjList
http://localhost:8080/getJsonStringList

使用PostMan测试

也可以直接使用浏览器地址输入,观看返回值

1.4.2 获取请求的JSON

方法参数中使用**@RequestBody**注解,将前端转入的JSON格式字符串转换为Java数据

1.4.2.1 准备前端
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<div id="app">
    <input type="button" value="发送对象数据" v-on:click="showObj"><br/>
    <input type="button" value="发送对象数组数据" v-on:click="showObjArray"><br/>
    <input type="button" value="发送字符串数组数据" v-on:click="showStringArray"><br/>
    {{msg}}
</div>
</body>
</html>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
<script>
    var vue = new Vue({
        el: "#app",
        data: {
            msg: "",
            obj: {
                username: "lisi",
                password: 'ls123'
            },
            arrayObj: [
                {
                    username: "zhangsan",
                    password: 'zs123'
                },
                {
                    username: "lisi",
                    password: 'ls123'
                }
            ],
            arrayString: ["one", "two"]
        },
        methods: {
            showObj: function () {
                axios({
                    url: "http://localhost:8080/addUser",
                    method: 'post',
                    // params:{},//会通过?拼接在地址后面
                    data: this.obj
                }).then(function (resp) {
                    vue.msg = resp.data;//动态渲染必须同vue.属性名
                })
            },
            showObjArray: function () {
                axios({
                    url: "http://localhost:8080/addList",
                    method: 'post',
                    data: this.arrayObj
                }).then(function (resp) {
                    vue.msg = resp.data;//动态渲染必须同vue.属性名
                })
            },
            showStringArray: function () {
                axios({
                    url: "http://localhost:8080/addArray",
                    method: 'post',
                    data: this.arrayString
                }).then(function (resp) {
                    vue.msg = resp.data;//动态渲染必须同vue.属性名
                })
            }
        }
    });
</script>

1.4.2.2.编写处理方法
package com.yaorange.handler;

import com.yaorange.entity.User;
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;

import java.util.List;

@Controller
@ResponseBody
public class JSONRquestHandler {
    @RequestMapping("/addUser")
//    @ResponseBody
    public String getJsonObjcect(@RequestBody User user){
        System.out.println(user);
        return "ok";
    }

    @RequestMapping("/addList")
//    @ResponseBody
    public String getJsonListObjcect(@RequestBody List<User> users){
        for (User user : users) {
            System.out.println(user);
        }
        return "ok";
    }

    @RequestMapping("/addArray")
//    @ResponseBody
    public String getJsonArray(@RequestBody String[] strings){
        for (String string : strings) {
            System.out.println(string);
        }
        return "ok";
    }

}

1.5 补充:json返回中文乱码(了解)(后端返回前端的json字符串数据中,有中文,会乱码)

1)第一种方式

该方式主要用于处理器方法返回值为String时的中文处理

在@RequestMapping中配置响应内容:

@RequestMapping(value = "/getJsonString",produces = "text/html;charset=UTF-8")
2)在消息转换器中配置(在springmvc-servlet.xml中的那个mvc:annotation-driven中配置)

2.1)给字符串转换器配置(前提:基于jackson使用

	<mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=UTF-8</value>
                        <value>application/json;charset=utf-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

2.2)给fastJson转换配置(前提:基于fastjson使用

	<mvc:annotation-driven>
        <mvc:message-converters>
            <!--引用been进行消息转换器注册-->
            <!--<ref bean="bean的id"/>-->
            <ref bean="httpMessageConverter"/>
        </mvc:message-converters>
    </mvc:annotation-driven>

    <!--配置fastjson转接器-->
    <bean id="httpMessageConverter" class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
        <property name="supportedMediaTypes">
            <list>
                <value>text/html;charset=UTF-8</value>
                <value>application/json;charset=utf-8</value>
            </list>
        </property>
    </bean>

1.6 补充:JSON进行日期转换(了解)

1)基于Jackson:使用在接收的参数对象实体的属性上使用注解@JsonFormat
public class User {
    private String username;
    private String password;
    //设置响应时间数据格式,默认基于JSON返回的日期为时间戳值
    @JsonFormat(pattern = "yyyy-MM-dd",timezone ="GMT+8")
    //指定传入json字符串转换日期对象数据时的格式
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date birthday;
}
2)基于fastjson在消息转换器配置(在springmvc-servlet.xml中的那个mvc:annotation-driven中配置)(类似于重写代码)
	<mvc:annotation-driven>
        <mvc:message-converters>
            <!--配置fastjson转接器-->
            <bean id="httpMessageConverter" class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <property name="fastJsonConfig">
                    <bean class="com.alibaba.fastjson.support.config.FastJsonConfig">
                        <!--将前端传入的日期时间字符串转换为指定格式日期数据-->
                        <property name="dateFormat" value="yyyy-MM-dd"/>
                    </bean>
                </property>
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=UTF-8</value>
                        <value>application/json;charset=utf-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

总结:

基于Json操作:掌握2个注解:

类或方法上注解:在方法上@ResponseBody(标识方法本身返回值作为JSON数据(通过转换器转换)返回,不做视图跳转),在类上使用表示当前类中所有方法都返回json

方法中注解:在参数列表中使用@RequestBody(接收前端返回的json数据,通过SpringMVC的转换器将数据转换为指定Java数据类型,赋值给被标识参数)

前提:需要添加使用的json工具jar

常用jackSon或fastJson:

如果使用JackSon那么只需要添加对应的jar,就可以了,SpringMVC底层已经自动配置了基于JackSon操作的消息转换器,除非想要配置其他的操作(如配置日期格式转换和中文乱码处理)

如果使用fastJson,那么除了需要添加对应的jar以外,还必须在mvc:annotation-driven配置注册对应fastjson消息转换器

2. RESTFul风格开发(主流,一般是前后端分离合作开发)

RESTFul:是由http协议设计者提出的一种请求风格,就是一种资源定位的操作风格,它不是标准,也不是协议,而是一种风格而已,也就是一种约定

REST:表述性状态转换(Representational State Transfer,简称REST

RESTFul目标:统一前后端请求地址的定义风格

前端要求:希望在请求时:

1)通过HTTP中的请求方式动词(get,post,delete,put),来告诉后端要进行什么操作

GET:select查询

POST:insert新增

PUT:update操作

DELETE:delete删除

2)请求地址:希望在所有URL中只有名词,没有动词

原始风格:http://localhost:8080/addBook

RestFul风格:http://localhost:8080/book 请求方式:post ,关联后端add操作

3)如果有需要通过地址传值:没有参数名,只有参数值

原始风格:http://localhost:8080/getPage?pageNum=1&pageSize=5

RestFul风格:http://localhost:8080/book/1/5 请求方式:get,关联后端分页查询操作

后端处理:通过SpringMVC对RestFul支持的注解

@RestController

在类上使用,将被标识的整个处理器类中所有方法都隐式添加@ResponseBody

在类上只使用@RestController等同于:类上添加@Controller+添加@ResponseBody

@PathVariable

只有前端通过地址中传值时(而不是地址参数?拼接传值),后端在方法参数列表中使用注解,用于获取请求映射地址中的占位数据

提示:axios前端不能使用params传值(会使用?传参),而是在地址后面直接/手动拼接

使用步骤

1)需要在@RequestMapping注解的映射地址中使用**{标识名}**占位符,

2)在方法参数列表中使用**@PathVariable(“标识名”)**注解将地址中的数据,赋值给被标识参数

//原生请求地址:http://localhost:8080/getBooks?pageNum=1&pageSize=5

@RequestMapping(value="/addBook",method=RequestMethod.GET)
public String getBooks(@RequestParam("pageNum") String num,@RequestParam("pageSize") String size){
	//.........
}
//或使用同名参数直接接收
@RequestMapping(value="/addBook",method=RequestMethod.GET)
public String getBooks(String pageNum,String pageSize){
	//.........
}


//RestFul请求地址:http://localhost:8080/book/1/5
//请求方式为:get

@GetMapping("/book/{pageNum}/{pageSize}")
public String getBooks(@PathVariable("pageNum") Integer num,@PathVariable("pageSize") Integer size){
	//.........
}

提示:如果方法参数列表中的参数名和映射地址中{}的标识名一致时,可以在注解中@PathVariable(“标识名”),省略标识名

@GetMapping("/book/{pageNum}/{pageSize}")
public String getBooks(@PathVariable Integer pageNum,@PathVariable Integer pageSize){
	//.........
}

重点说明:

前端:可以同时在地址使用/拼接传入值的同时,也可以使用data发送json数据

后端:可以在参数列表中同时使用@PathVariable接受地址参数,同时使用@RequestBody接受前端请求体中发送的json数据

RestFul扩展注解:

@GetMapping,@PostMapping,@DeleteMapping,@PutMapping

有特定指定请求方式限制的映射请求注解,等同于@RequestMapping(value=“映射地址”,method=请求方式)

如:@RequestMapping(value=“映射地址”,method=RequestMethod.GET) 等同于 @GetMapping(“映射地址”)

2.2 基于RestFul风格的前后端开发(掌握)

数据实体:
package com.yaorange.entity;

import com.fasterxml.jackson.annotation.JsonFormat;

import java.util.Date;

public class User_rest {
    private Integer id;
    private String username;
    private String password;
	
	//省略getter和setter,toString方法
}

2.2.1 前端页面代码(基于ajax请求)

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<div id="app">
    <input type="button" value="发送get请求" v-on:click="showGet"><br/>
    <input type="button" value="发送post请求" v-on:click="showPost"><br/>
    <input type="button" value="发送put请求" v-on:click="showPut"><br/>
    <input type="button" value="发送delete请求,单个删除" v-on:click="showDeleteOne"><br/>
    <input type="button" value="发送delete请求,批量删除" v-on:click="showDeleteList"><br/>
    {{msg}}
</div>
</body>
</html>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
<script>
    var vue = new Vue({
        el: "#app",
        data: {
            msg: "",
            userId:10,
            addUser:{
                username:'zhangsan',
                password:'zs123'

            },
            updateUser:{
                id:10,
                username:'zhangsan',
                password:'zs123'

            },
            userIds:[10,11]
        },
        methods: {
            //查询单个用户
            showGet: function () {
                var id = this.userId;
                axios({
                    url: "http://localhost:8080/user/"+id,
                    method: 'get'
                    // params:{}//通过地址传值时不能使用params
                }).then(function (resp) {
                    vue.msg = resp.data;//动态渲染必须同vue.属性名
                })
            },
            //新增用户
            showPost: function () {
                axios({
                    url: "http://localhost:8080/user",
                    method: 'post',
                    data: this.addUser
                }).then(function (resp) {
                    vue.msg = resp.data;//动态渲染必须同vue.属性名
                })
            },
            //更新用户
            showPut: function () {
                axios({
                    url: "http://localhost:8080/user",
                    method: 'put',
                    data: this.updateUser
                }).then(function (resp) {
                    vue.msg = resp.data;//动态渲染必须同vue.属性名
                })
            },
            //删除多个用户
            showDeleteOne: function () {
                axios({
                    url: "http://localhost:8080/user/"+this.userId,
                    method: 'delete',
                }).then(function (resp) {
                    vue.msg = resp.data;//动态渲染必须同vue.属性名
                })
            },
            //删除多个用户
            showDeleteList: function () {
                axios({
                    url: "http://localhost:8080/user",
                    method: 'delete',
                    data: this.userIds
                }).then(function (resp) {
                    vue.msg = resp.data;//动态渲染必须同vue.属性名
                })
            }
        }
    });
</script>

2.1.2 后端实现代码:

package com.yaorange.web;

import com.yaorange.entity.User_rest;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;

/**
 * @description: 基于restFul开发都是基于前后端分离开发
 */
//当前处理器的所有方法都是基于JSON数据返回
@RestController
@RequestMapping("/user")
public class RestFulHandler {
//    @GetMapping("/user/{userId}")
    @GetMapping("/{userId}")
    //通过用户id获取用户对象
    public User_rest get(@PathVariable Integer userId){//要求参数名和{}中标识名一致
        //省略调用业务方法获取对象的方法
        //模拟静态数据
        User_rest user = new User_rest();
        user.setId(userId);
        user.setUsername("zhangsan");
        user.setPassword("zs123");
        return user;
    }

//    @PostMapping("/user")
    @PostMapping
    //新增用户对象
    public String post(@RequestBody User_rest user){
        //省略调用业务方法新增对象
        System.out.println(user);//没有用户id
        return "add success";
    }

//    @PutMapping("/user")
    @PutMapping
    //更新用户对象
    public String put(@RequestBody User_rest user){
        //省略调用业务方法更新对象
        System.out.println(user);//有用户id,因为sql语句需要id作为条件
        return "update success";
    }

    // @DeleteMapping("/user/{userId}")
    @DeleteMapping("/{userId}")
    //删除单个用户对象
    public String deleteOne(@PathVariable Integer userId){
        //省略调用业务方法根据id删除对象
        System.out.println(userId);
        return "delete one success";
    }

//    @DeleteMapping("/user")
    @DeleteMapping
    //删除多个用户对象
    public String deleteList(@RequestBody Integer[] userIds){
        //省略调用业务方法根据id数组遍历删除对象
        List<Integer> ids = Arrays.asList(userIds);
        System.out.println(ids);
        return "delete list success";
    }
}

如果参数名和占位别名一致可以在注解省略名称,不一致就必须指定别名

  @GetMapping("/user/{userId}")
  public String get(@PathVariable("userId") Integer id) {}//因为参数名和地址占位名不一致,注解不能省略名称

  @GetMapping("/user/{userId}"
  public String get(@PathVariable Integer userId) {}//因为参数名和地址占位名不一致,注解可以省略名称

补充:不使用AJAX(基于原生表单)前端发起RestFul的请求(了解)

浏览器表单只能支持get和post请求,不能发起put和delete请求,在Spring提供了一个过滤器来转换post请求为put和delete

基于原生表单完成RestFul请求步骤

1.在web.xml配置过滤器
	<filter>
        <filter-name>methodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>methodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
2. 在请求方式为Post的表单中创建一个隐藏文本框,该文本框的name必须赋值为:name="_method",value赋值PUT或DELETE

前端代码

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="/user1/12" method="get">
    <input type="submit" value="Get测试">
</form>
<form action="/user1" method="post">
    用户名:<input name="username"><br/>
    密码:<input name="password"><br/>
    <input type="submit" value="Post测试">
</form>
<form action="/user1" method="post">
    <%--省略表单控件传参--%>
    <input type="hidden" name="_method" value="PUT">
    <input type="submit" value="Put测试">
</form>
<form action="/user1" method="post">
    <%--省略表单控件传参--%>
    <input type="hidden" name="_method" value="DELETE">
    <input type="submit" value="Delete测试">
</form>
</body>
</html>

总结:

RestFul一般是用于前后端分离开发中:

RestFul前端要求:请求时地址中不能存在?参数名方式传值,请求时地址中不能出现动词而是使用名词,操作的动作一般通过在请求时设置明确的请求方法(get,post,put,delete)来表达

后端开发:

1)如果前端通过地址传值,那么后端通过在@RequestMapping的映射地址中使用{}地址占位,这样就可以通过在方法参数中使用注解@PathVariable获取地址占位对应的实际值,并将值赋值给被标识的参数

2)如果前端通过请求体(body)传JSON值,那么后端依然使用@RequestBody标识参数接收json数据

3)在每个处理器方法上使用@RequestMapping(…,mathod="")来指定请求的方式,建议直接使用RestFul扩展映射注解:@GetMapping,@PostMapping,@PutMapping,@DeleteMapping,注解标识的方法应该和请求方式的对应操作一致

实际开发中遵循RestFul风格开发是目前前后端分离项目的趋势

3. 文件上传

SpringMVC的文件上传有两种种方式:一种基于Servlet3.0以前版本,一种基于Servlet3.0以后版本

实际开发中根据项目环境选择开发方式

3.1 MultipartResolver接口

DispatcherServlet中文件上传的执行流程:

MultipartResolver 用于处理文件上传的解析器,当收到请求时 DispatcherServlet 的 checkMultipart() 方法会调用 MultipartResolver 的 isMultipart() 方法判断请求中是否包含文件。如果请求数据中包含文件,则调用 MultipartResolver 的 resolveMultipart() 方法对请求的数据进行解析,然后将文件数据解析成 MultipartFile 并封装在 MultipartHttpServletRequest (继承了 HttpServletRequest) 对象中,最后传递给 Controller(也就是处理器方法)

MultipartResolver 是一个接口,它的实现类如下图所示,分为 CommonsMultipartResolver 类和 StandardServletMultipartResolver 类。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W7yupyDn-1611460453609)(images/1118116-20171013140518699-1201438767.png)]

CommonsMultipartResolver处理器兼容性较好,可以兼容Servlet3.0之前的版本,但是底层使用第三方Apache的commons Fileupload工具来处理multipart请求,所以在使用时,必须要引入相应的jar包;

StandardServletMultipartResolver处理器兼容较差,只能用于Servlet3.0以后的版本不依赖第三方工具jar,直接可以进行文件上传操作

3.1.1 MultipartFile接口

一般使用该接口作为文件上传处理方法参数,直接接收前端传入的文件对象,在处理器方法中实现springmvc中的文件上传操作,它有两个实现类:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V8zOfRNT-1611460453610)(images/1121080-20190525201019251-1142266016.png)]

接口定义的方法:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UAHzxcZF-1611460453611)(images/1121080-20190525201042265-1796182099.png)]

3.2基于Servlet3.0以上版本的文件上传

不依赖第三方工具

前提:项目中使用的Servlet-api必须是3.0以上版本:

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

3.2.1 前端准备

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="file"><br/>
    <input type="submit">
</form>
</body>
</html>

说明:action一般使用绝对请求地址,不建议使用相对地址,表单的method必须为post,表单需要进行文件上传,必须设置enctype="multipart/form-data"

在表单中通过file控件进行文件上传

<input type="file" name=“file”/>

3.2.2 后端开发:

步骤:

1).在配置文件中配置文件上传解析器,bean的id名必须为multipartResolver
2).如果想要限制文件上传的大小等等,那么需要在web.xml中对DispatcherServlet进行配置
3).在文件上传操作的处理器方法的参数列表中声明SpringMVC提供文件上传操作对象:MultipartFile对象,进行文件操作,在处理器方法中通过调用MultipartFile的transferTo(“服务器文件地址”)

提示:要求MultipartFile参数的参数名必须和前端文件上传控件的name属性值一致,不一致使用@RequestParam注解(或@RequestPart)标识

1. 配置文件上传解析器

在springmvc配置文件中配置

<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>

说明:此处的bean的id必须为’multipartResolver’,此为Spring的约定,否则可能会导致接受到null。

基于java类配置:
	@Bean(name = "multipartResolver")//将方法返回值添加到Spring容器
    public StandardServletMultipartResolver addResolver(){
        return new StandardServletMultipartResolver();
    }
2. 配置文件上传的属性

在web.xml给DispatcherServlet配置multipart-config

	<servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
        <!--配置跟随服务器启动加载-->
        <load-on-startup>1</load-on-startup>
        <!--类似于一阶段在Servlet上使用@MultipartConfig-->
        <multipart-config>
            <!--指定上传文件的临时存储位置,可忽略,让应用服务器使用它默认临时目录即可-->
            <!--<location>D:\file</location>-->
            <!--指定上传的单个文件大小:单位字节-->
            <max-file-size>10240</max-file-size>
            <!--指定上传文件的总大小-->
            <max-request-size>102400</max-request-size>
            <!--指定内存中存储的文件最大大小,超过大小就写入文件-->
            <file-size-threshold>0</file-size-threshold>
        </multipart-config>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
基于java类配置

在web.xml的java配置类中添加配置

	@Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {
        MultipartConfigElement multipartConfigElement = new MultipartConfigElement(LOCATION,MAX_FILE_SIZE,MAX_REQUEST_SIZE,FILE_SIZE_THRESHOLD);
        // Optionally also set maxFileSize, maxRequestSize, fileSizeThreshold
        registration.setMultipartConfig(multipartConfigElement);
    }
    private static final String LOCATION = "";
    private static final Long MAX_FILE_SIZE = 10240L;
    private static final Long MAX_REQUEST_SIZE = 102400L;
    private static final int FILE_SIZE_THRESHOLD = 0;
3. 后端处理器方法
package com.yaorange.web;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.UUID;

@RestController
public class UploadHandler {
    @RequestMapping("/upload")
    public String upload(MultipartFile file, HttpServletRequest request) throws IllegalStateException, IOException {
        //检查文件是否为空
		if (file.isEmpty()){
			return  "文件为空";
		}
		//检查文件大小  2097152 =2M
		if(file.getSize() > 2097152) {
			return "文件大于2M";
		}
		//检查是否是图片
		try {
			BufferedImage bi = ImageIO.read(file.getInputStream());
			if(bi == null){
				return "不是图片";
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
        // 获取文件存储路径(绝对路径)
        String path = request.getServletContext().getRealPath("/WEB-INF/upload/");
//        System.out.println("服务器中的相对/WEB-INF/upload/完整路径:"+path);
        //获取上传文件后缀名
//        System.out.println("客户端上传的文件名:"+file.getOriginalFilename());
        String originalFilename = file.getOriginalFilename();
        String suff = originalFilename.substring(originalFilename.lastIndexOf("."));
        // 获取文件名
        String fileName = UUID.randomUUID().toString().replaceAll("-","")+suff;
        // 创建文件实例
        File filePath = new File(path, fileName);
        // 如果文件目录不存在,创建目录
        if (!filePath.getParentFile().exists()) {
            filePath.getParentFile().mkdirs();
            System.out.println("创建目录" + filePath);
        }
        // 写入文件
        file.transferTo(filePath);
        return "success";
    }
}

说明:如果前端的文件上传控件的name名和处理器方法参数名不一致可以使用该注解@RequestPart(“参数名”)或@RequestParam(“参数名”)

@RequestPart在前后端不分离的文件上传中和@RequestParam意义效果一致

@RequestPart可以处理特殊的数据格式,如JSON

一致:

//前端:
<input type="file" name="uploadFile">
//后端:
public String upload(MultipartFile uploadFile, HttpServletRequest request){}

如果不一致:

//前端:
<input type="file" name="uploadFile">
//后端:
public String upload(@RequestParam("uploadFile") MultipartFile file, ....){}
//或
public String upload(@RequestPart("uploadFile") MultipartFile file, ....){}

3.3 基于Apache fileupload文件上传

在web阶段中我们用到的是 Apache fileupload这个组件来实现上传,在springmvc中对它进行了封装,让我们使用起来比较方便,但是底层还是由Apache fileupload来实现的。

添加依赖:

	<dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.2</version>
    </dependency>

3.3.1 前端准备

页面和基于Servlet3.0一致

3.3.2 后端开发

1. 只需要在配置文件中配置上传解析器并配置上传属性
<!--如果SpringMVC通过CommonsMultipartResolver做文件上传操作-->
    <!--1.添加第三方工具jar:commons-fileupload-->
    <!--2.在SpringMVC配置文件中配置解析器-->
    <!--3.如果使用的是CommonsMultipartResolver作为解析器,那么想要限制文件上传的大小等等,那么需要当前解析器的标记中配置-->
    <!--4.在文件上传操作的处理器方法的参数列表中声明SpringMVC提供文件上传操作对象:MultipartFile对象,进行文件操作,
    要求参数名必须和前端文件上传控件名一致,不一致使用@ReqParam注解标识-->
    <bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">
        <!--指定编码-->
        <property name="defaultEncoding" value="UTF-8"/>
        <!--指定上传文件的总文件大小:单位byte-->
        <property name="maxUploadSize" value="102400000"/>
        <!--指定上传文件的单个文件大小-->
        <property name="maxUploadSizePerFile" value="10240000"/>
        <!-- 设定文件上传时写入内存的最大值,如果小于这个参数不会生成临时文件,默认为10240 -->
        <!--<property name="maxInMemorySize" value="5120"/>-->
        <!--指定临时目录,超过maxInMemorySize指定值时,数据就写入指定文件目录中,在全部文件上传完成后,再进行数据合并到真正文件上传的目录中-->
        <!--<property name="uploadTempDir" value="fileUpload/temp"/>-->
    </bean>

说明:此处的bean的id必须为’multipartResolver’,此为Spring的约定,否则可能会导致Controller接受到null。

基于Java类配置:

在SpringMVC配置类上新增解析器

	//配置普通Bean
    @Bean(name = "multipartResolver")//将方法返回值添加到Spring容器
    public CommonsMultipartResolver addResolver(){
        CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
        //设置文件上传控制属性
        commonsMultipartResolver.setDefaultEncoding("UTF-8");
        //......
        return commonsMultipartResolver;
    }
2. 后端处理器开发

基本和基于Servlet3.0开发一致

package com.yaorange.web;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.UUID;

@RestController
public class UploadHandler {

    @RequestMapping("/upload")
    public String upload(MultipartFile file, HttpServletRequest request) throws IllegalStateException, IOException {
        //检查文件是否为空
		if (file.isEmpty()){
			return  "文件为空";
		}
		//检查文件大小  2097152 =2M
		if(file.getSize() > 2097152) {
			return "文件大于2M";
		}
		//检查是否是图片
		try {
			BufferedImage bi = ImageIO.read(file.getInputStream());
			if(bi == null){
				return "不是图片";
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
        // 获取文件存储路径(绝对路径)
        String path = request.getServletContext().getRealPath("/WEB-INF/upload/");
//        System.out.println("服务器中的相对/WEB-INF/upload/完整路径:"+path);
        //获取上传文件后缀名
//        System.out.println("客户端上传的文件名:"+file.getOriginalFilename());
        String originalFilename = file.getOriginalFilename();
        String suff = originalFilename.substring(originalFilename.lastIndexOf("."));
        // 获取文件名
        String fileName = UUID.randomUUID().toString().replaceAll("-","")+suff;
        // 创建文件实例
        File filePath = new File(path, fileName);
        // 如果文件目录不存在,创建目录
        if (!filePath.getParentFile().exists()) {
            filePath.getParentFile().mkdirs();
            System.out.println("创建目录" + filePath);
        }
        // 写入文件
        file.transferTo(filePath);
        return "success";
    }
}

3.4 多文件上传

多文件上传只需要把html代码中的多个< input type=“file” name="…" >name属性设置为一样的就好。然后在controller中使用MultipartFile数组接受就行。

@RequestMapping(value="/upload", method=RequestMethod.POST)
public String fileUpload(@RequestPart(value="file") MultipartFile[] files){
   //遍历数组单个上传
    
}

补充:如果想要限制文件上传类型

解决思路:

1)基于前端,限制文件类型

2)基于后端,建议使用SpringMVC框架的拦截器进行拦截校验

4. 补充

4.1 /和/*的区别

	<servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

配置前端控制器为/,表示拦截所有请求(包括css,img,js…但是不包括jsp

/*也是拦截所有请求(包括jsp,css,img,js…),请求直接JSP也会出现404

如果配置:*.后缀,表示只拦截指定后缀的请求,如: *.action(struts2), *.do(struts1)

如果想要在配置/的同时不拦截静态资源(css,图片,js,…),那么必须进行静态资源放行配置

4.2 静态资源的放行(只有前后端不分离开发需要解决)

官方文档:https://docs.spring.io/spring-framework/docs/5.2.12.RELEASE/spring-framework-reference/web.html#mvc-config-static-resources

注意配置位置必须在:mvc:annotation-driven/后面

方式1:在SpringMVC配置文件中设置放行所有静态资源

	<!--放行所有静态资源-->
    <mvc:default-servlet-handler/>

注意:顺序必须在mvc:annotation-driven标签之后

方式2:在SpringMVC配置文件中设置放行指定文件夹中的资源

	<!--放行指定文件目录下的静态资源,有几个文件目录就配置几个标记-->
    <mvc:resources mapping="/images/**" location="/images/"/>
    <mvc:resources mapping="/css/**" location="/css/"/>

面试题

1. 简述 REST 中HiddenHttpMethodFilter 过滤器的作用

该过滤器主要负责转换客户端请求的方式,当浏览器的请求方式为 POST,并且在请求中能通过 _method 获取到请求参数值。该过滤器就会进行请求方式的转换。

一般在 REST 中,都是将 POST 请求转换为对应的DELETE 或者是 PUT

2. 简述 Springmvc 中如何返回 JSON 响应json数据

Step1:在项目中加入 json 转换的依赖,例如 jackson,fastjson,gson 等

Step2:在配置中开启注解驱动支持mvc:annotation-driven/

Step3:在请求处理方法中将返回值改为具体返回的数据的类型, 例如数据的集合类 List等

Step4:在请求处理方法上或类上使用@ResponseBody 注解

3. SpringMVC如何获取JSON数据

Step1:在项目中加入 json 转换的依赖,例如 jackson,fastjson,gson 等

Step2:在配置中开启注解驱动支持mvc:annotation-driven/

Step3:在请求处理方法参数列表中使用注解@RequestBody将前端传入的JSON字符串转换为Java对象(集合)数据, 并赋值给标识参数。

4. 如何在SpringMVC中实现RestFul服务

类上使用注解@RestController

在方法上使用@GetMapping,@PostMapping,@PutMapping,@DeleteMapping注解映射方法

如果前端通过地址传值,那么在@XXXMapping("/映射地址/{占位标识1}/{占位标识2}…"),在方法参数列表中使用@PathVariable(“占位标识”)接收地址中的参数值赋值给标识参数(同名参数可以省略(“占位标识”),直接写@PathVariable)

如果前端发送JSON数据,那么在参数列表中使用@RequestBody处理接收

5. 简述 REST 中的四种请求方式及对应的操作

GET 查询操作

POST 添加操作

DELETE 删除操作

PUT 修改操作

问题总结:

1. 在使用RestFul服务是如果发起的请求为put/delete时,Tomcat可能会出现403异常

原因:这是因为在Tomcat8.0的某些版本关闭了put和delete请求方式

解决办法:一般建议使用使用Tomcat8.5版本

2. 在页面加载某些静态资源(图片,css,js…)时出现404错误,比如图片不正常显示

原因:一般是因为在配置DispatcherServlet时,拦截方式配置的是/(拦截所有请求,但是不包括jsp)

解决办法:放行静态资源,在SpringMVC的配置文件中配置资源放行

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页