栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring MVC参数传递总结

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring MVC参数传递总结

content-type定义

MediaType,即是Internet Media Type,互联网媒体类型;也叫做MIME类型,在Http协议消息头中,使用Content-Type来表示具体请求中的媒体类型信息。

常见媒体格式如下:

  • text/html : HTML格式
  • text/plain :纯文本格式
  • text/xml : XML格式
  • image/gif :gif图片格式
  • image/jpeg :jpg图片格式
  • image/png:png图片格式

以application开头的媒体格式类型:

  • application/xhtml+xml :XHTML格式
  • application/xml : XML数据格式
  • application/atom+xml :Atom XML聚合格式
  • application/json : JSON数据格式
  • application/pdf :pdf格式
  • application/msword : Word文档格式
  • application/octet-stream :
    二进制流数据(如常见的文件下载)
  • application/x-www-form-urlencoded :
    默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)

b. content-type 实例说明

上面算是基本定义和取值,下面结合实例对典型的几种方式进行说明
application/x-www-form-urlencoded:数据被编码为名称/值对。这是标准的编码格式。
multipart/form-data: 数据被编码为一条消息,页上的每个控件对应消息中的一个部分。
text/plain: 数据以纯文本形式(text/json/xml/html)进行编码,其中不含任何控件或格式字符

对于前端使用而言,form表单的enctype属性为编码方式,常用有两种:application/x-www-form-urlencoded和multipart/form-data,默认为application/x-www-form-urlencoded。

在日常使用SpringMVC进行开发的时候,有可能遇到前端各种类型的请求参数,这里做一次相对全面的总结。SpringMVC中处理控制器参数的接口是HandlerMethodArgumentResolver,此接口有众多子类,分别处理不同(注解类型)的参数,下面只列举几个子类:

  • RequestParamMethodArgumentResolver:解析处理使用了@RequestParam注解的参数、MultipartFile类型参数和Simple类型(如long、int等类型)参数。
  • RequestResponseBodyMethodProcessor:解析处理@RequestBody注解的参数。
  • PathVariableMapMethodArgumentResolver:解析处理@PathVariable注解的参数。

实际上,一般在解析一个控制器的请求参数的时候,用到的是HandlerMethodArgumentResolverComposite,里面装载了所有启用的HandlerMethodArgumentResolver子类。而HandlerMethodArgumentResolver子类在解析参数的时候使用到HttpMessageConverter(实际上也是一个列表,进行遍历匹配解析)子类进行匹配解析,常见的如MappingJackson2HttpMessageConverter(使用Jackson进行序列化和反序列化)。而HandlerMethodArgumentResolver子类到底依赖什么HttpMessageConverter实例实际上是由请求头中的Content-Type(在SpringMVC中统一命名为MediaType,见org.springframework.http.MediaType)决定的,因此我们在处理控制器的请求参数之前必须要明确外部请求的Content-Type到底是什么。上面的逻辑可以直接看源码AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters,思路是比较清晰的。在@RequestMapping注解中,produces和consumes属性就是和请求或者响应的Content-Type相关的:

consumes属性:指定处理请求的提交内容类型(Content-Type),例如application/json、text/html等等,只有命中了对应的Content-Type的值才会接受该请求。
produces属性:指定返回的内容类型,仅当某个请求的请求头中的(Accept)类型中包含该指定类型才返回,如果返回的是JSON数据一般考虑使用application/json;charset=UTF-8。
另外提一点,SpringMVC中默认使用Jackson作为JSON的工具包,如果不是完全理解透整套源码的运作,一般不是十分建议修改默认使用的MappingJackson2HttpMessageConverter(例如有些人喜欢使用FastJson,实现HttpMessageConverter引入FastJson做HTTP消息转换器,这种做法并不推荐)。

Get请求

发起Get请求时,浏览器用application/x-www-form-urlencoded方式,将表单数据转换成一个字符串(key1=value1&key2=value2…)拼接到url上,这就是我们常见的url带请求参数的情况
RequestBody同样支持GET方法?????

Post表单

发起post请求时,如果没有传文件,浏览器也是将form表单的数据封装成k=v的结果丢到http body中,拿开源中国的博客提交的表单为例,一个典型的post表单,上传的数据拼装在form data中,为kv结构
如果有传文件的场景,Content-Type类型会升级为multipart/form-data,这一块不详细展开

Post json对象

post表单除了前面一种方式之外,还有一种也是我们常见的,就是讲所有的表单数据放在一个大的json串中,然后丢给后端,这里也有一个在线的实例,某电商平台的商品发表,截图如下

注意看上面的Request Payload,是一个大的json串,和前面差别明显

代码示例:
Ajax的默认格式为:application/x-www-form-urlencoded,相当于(username=“admin”&password=123)来传递数据(这是GET请求的固定格式)
前端代码:
当Ajax以默认格式上传时,data数据直接使用JSON对象user,不用转换为JSON字符串(很方便)

 var user= {
                "username" : username,
                "password" : password,
                "rememberMe":rememberMe
            };
$.ajax({
    url : "http://...../jsontest.do",
    type : "POST",
    async : true,
    data : user,
    dataType : 'json',
    success : function(data) {
    }
});

后端使用@RequestParam注解或省略:
【推荐】

//直接省略注解
@RequestMapping("/jsontest.do")
public void test(User user,String username,String password,Boolean rememberMe){
    System.out.println(user);
    System.out.println("username: " + username);
    System.out.println("password: " + password);
    System.out.println("rememberMe: " + rememberMe);
    
}


//加上注解
@RequestMapping("/jsontest.do")
public void test(@RequestParam String username,
@RequestParam String password,@RequestParam Boolean rememberMe,){
    System.out.println("username: " + username);
    System.out.println("password: " + password);
    System.out.println("rememberMe: " + rememberMe);
}

优点:
1.前端传递数据不用转换为json字符串:JSON.stringify(user)
2.后端接受的参数很灵活,即可以封装为User对象,亦可以使用单个参数username,rememberMe,甚至User对象和单个rememberMe参数混合使用都可以

【对象】 - 对象类型参数接收

我们接着写一个接口用于提交用户信息,用到的是上面提到的模特类,主要包括用户姓名、年龄和联系人信息列表,这个时候,我们目标的控制器最终编码如下:

@Data
public class User {

    private String name;
    private Integer age;
    private List contacts;
}

@Data
public class Contact {

    private String name;
    private String phone;
}
@PostMapping(value = "/user")
public User saveUser(User user) {
    log.info(user.toString());
    return user;
}

我们还是指定Content-Type为application/x-www-form-urlencoded,接着我们需要构造请求参数:

**因为没有使用注解,最终的参数处理器为ServletModelAttributeMethodProcessor,主要是把HttpServletRequest中的表单参数封装到MutablePropertyValues实例中,再通过参数类型实例化(通过构造反射创建User实例),反射匹配属性进行值的填充。另外,请求复杂参数里面的列表属性请求参数看起来比较奇葩,实际上和在.properties文件中添加最终映射到Map类型的参数的写法是一致的。**那么,能不能把整个请求参数塞在一个字段中提交呢?

直接这样做是不行的,因为实际提交的Form表单,key是user字符串,value实际上也是一个字符串,缺少一个String->User类型的转换器,实际上RequestParamMethodArgumentResolver依赖WebConversionService中Converter实例列表进行参数转换:

解决办法还是有的,添加一个org.springframework.core.convert.converter.Converter实现即可:

@Component
public class StringUserConverter implements Converter {

    @Autowaired
    private ObjectMapper objectMapper;

    @Override
    public User convert(String source) {
        try {
               return objectMapper.readValue(source, User.class);
            } catch (IOException e) {
               throw new IllegalArgumentException(e);
        }
    }
}

上面这种做法属于曲线救国的做法,不推荐使用在生产环境,但是如果有些第三方接口的对接无法避免这种参数,可以选择这种实现方式。

【数组】 - 列表或者数组类型参数。

极度不推荐使用在application/x-www-form-urlencoded这种媒体类型的表单提交的形式下强行使用列表或者数组类型参数,除非是为了兼容处理历史遗留系统的参数提交处理。
例如提交的参数形式是:

list = [“string-1”, “string-2”, “string-3”]
那么表单参数的形式要写成:
name value
list[0] string-1
list[1] string-2
list[2] string-3

控制器的代码如下:

@PostMapping(path = "/list")
public void list(@RequestParam(name="list") List list) {
    log.info(list);
}

一个更加复杂的例子如下,假设想要提交的报文格式如下:
user = [{“name”:“doge-1”,“age”: 21},{“name”:“doge-2”,“age”: 22}]

那么表单参数的形式要写成:
name value
user[0].name doge-1
user[0].age 21
user[1].name doge-2
user[1].age 22

控制器的代码如下:

@PostMapping(path = "/user")
public void saveUsers(@RequestParam(name="user") List users) {
    log.info(users);
}

@Data
public class UserVo{

	private String name;
	private Integer age;
}

Post json字符串

@RequestBody使得REST接口接收的不再content-type为application/x-www-form-urlencoded的请求, 反而需要显示指定为application/json
也是在body中添加json串格式的请求,使用此方式的同时,还是老老实实的选择POST方法比较合适,无法通过javax.servlet.ServletRequest#getParameter获取参数。
代码示例:
前端代码:

var user= {
                "username" : username,
                "password" : password
          };
$.ajax({
        url : "http://...../jsontest.do",
        type : "POST",
        async : true,
        contentType: "application/json; charset=utf-8",
        data : JSON.stringify(user),
        dataType : 'json',
        success : function(data) {
        }
 });

后端必须使用@RequestBody 注解:

//这种方式下所有的参数都只能封装在User对象中,不能单独设置参数
@RequestMapping("/jsontest")
public void test(@RequestBody User user  ){
    String username = user.getUsername();
    String password = user.getPassword();
}

或者

@RequestMapping("/jsontest")
public void test(@RequestBody Map map  ){
    String username = map.get("username").toString();
    String password = map.get("password").toString();
}

或者

 public void test(@RequestBody String jsonData) {
    JSONObject jsonObject = JSON.parseObject(jsonData);
    String username= jsonObject.getString("username");
    String username= jsonObject.getString("password");
 }

缺点:
1.前端需要使用JSON.stringify()将JSON对象转换为JSON字符串
2.后端在接受参数的时候比较麻烦,没有第1种简单,也没有第一种灵活

postman写入参数如下:

后端控制器的代码也比较简单:

@PostMapping(value = "/user-2")
public User saveUser2(@RequestBody User user) {
    log.info(user.toString());
    return user;
}

因为使用了@RequestBody注解,最终使用到的参数处理器为RequestResponseBodyMethodProcessor,实际上会用到MappingJackson2HttpMessageConverter进行参数类型的转换,底层依赖到Jackson相关的包。推荐使用这种方式,这是最常用也是最稳健的JSON参数处理方式。

@PathVariable

URL路径参数,或者叫请求路径参数是基于URL模板获取到的参数,例如/user/{userId}是一个URL模板(URL模板中的参数占位符是{}),实际请求的URL为/user/1,那么通过匹配实际请求的URL和URL模板就能提取到userId为1。在SpringMVC中,URL模板中的路径参数叫做PathVariable,对应注解@PathVariable,对应的参数处理器为PathVariableMethodArgumentResolver。注意一点是,@PathVariable的解析是按照value(name)属性进行匹配,和URL参数的顺序是无关的。举个简单的例子:
后台的控制器如下:

@GetMapping(value = "/user/{name}/{age}")
public String findUser1(@PathVariable(value = "age") Integer age,
						@PathVariable(value = "name") String name) {
	String content = String.format("name = %s,age = %d", name, age);
	log.info(content);
	return content;
}

这种用法被广泛使用于Representational State Transfer(REST)的软件架构风格,个人觉得这种风格是比较灵活和清晰的(从URL和请求方法就能完全理解接口的意义和功能)。下面再介绍两种相对特殊的使用方式。

  • 带条件的URL参数。

其实路径参数支持正则表达式,例如我们在使用/sex/{sex}接口的时候,要求sex必须是F(Female)或者M(Male),那么我们的URL模板可以定义为/sex/{sex:M|F},代码如下:

@GetMapping(value = "/sex/{sex:M|F}")
public String findUser2(@PathVariable(value = "sex") String sex){
    log.info(sex);
    return sex;
}

只有/sex/F或者/sex/M的请求才会进入findUser2()控制器方法,其他该路径前缀的请求都是非法的,会返回404状态码。
这里仅仅是介绍了一个最简单的URL参数正则表达式的使用方式,更强大的用法可以自行摸索。

文件上传#

文件上传在使用POSTMAN模拟请求的时候需要选择form-data,POST方式进行提交:

假设我们在D盘有一个图片文件叫doge.jpg,现在要通过本地服务接口把文件上传,控制器的代码如下:

@PostMapping(value = "/file1")
public String file1(@RequestPart(name = "file1") MultipartFile multipartFile) {
	String content = String.format("name = %s,originName = %s,size = %d",
	multipartFile.getName(), multipartFile.getOriginalFilename(), multipartFile.getSize());
	log.info(content);
	return content;
}

控制台输出是:
name = file1,originName = doge.jpg,size = 68727

可能有点疑惑,参数是怎么来的,我们可以用Fildder软件抓个包看下:

可知MultipartFile实例的主要属性分别来自Content-Disposition、Content-Type和Content-Length,另外,InputStream用于读取请求体的最后部分(文件的字节序列)。参数处理器用到的是RequestPartMethodArgumentResolver(记住一点,使用了@RequestPart和MultipartFile一定是使用此参数处理器)。在其他情况下,使用@RequestParam和MultipartFile或者仅仅使用MultipartFile(参数的名字必须和POST表单中的Content-Disposition描述的name一致)也可以接收上传的文件数据,主要是通过RequestParamMethodArgumentResolver进行解析处理的,它的功能比较强大,具体可以看其supportsParameter方法,这两种情况的控制器方法代码如下:

@PostMapping(value = "/file2")
public String file2(MultipartFile file1) {
	String content = String.format("name = %s,originName = %s,size = %d",
				file1.getName(), file1.getOriginalFilename(), file1.getSize());
	log.info(content);
	return content;
}

@PostMapping(value = "/file3")
public String file3(@RequestParam(name = "file1") MultipartFile multipartFile) {
	String content = String.format("name = %s,originName = %s,size = %d",
			multipartFile.getName(), multipartFile.getOriginalFilename(), multipartFile.getSize());
	log.info(content);
	return content;
}

请求头#

请求头的值主要通过@RequestHeader注解的参数获取,参数处理器是RequestHeaderMethodArgumentResolver,需要在注解中指定请求头的Key。简单实用如下:
控制器方法代码:

@PostMapping(value = "/header")
public String header(@RequestHeader(name = "Content-Type") String Content-Type) {
    return Content-Type;
}

cookie#

cookie的值主要通过@cookievalue注解的参数获取,参数处理器为ServletcookievalueMethodArgumentResolver,需要在注解中指定cookie的Key。控制器方法代码如下:

@PostMapping(value = "/cookie")
public String cookie(@cookievalue(name = "JSESSIONID") String sessionId) {
	return sessionId;
}

Model类型参数#

Model类型参数的处理器是ModelMethodProcessor,实际上处理此参数是直接返回ModelAndViewContainer实例中的Model(ModelMap类型),因为要桥接不同的接口和类的功能,因此回调的实例是BindingAwareModelMap类型,此类型继承自ModelMap同时实现了Model接口。举个例子:

@GetMapping(value = "/model")
public String model(Model model, ModelMap modelMap) {
    log.info("{}", model == modelMap);
    return "success";
}

注意调用此接口,控制台输出INFO日志内容为:true。还要注意一点:ModelMap或者Model中添加的属性项会附加到HttpRequestServlet实例中带到页面中进行渲染。

Jackson序列化和反序列化定制#

因为SpringMVC默认使用Jackson处理@RequestBody的参数转换,因此可以通过定制序列化器和反序列化器来实现日期类型的转换,这样我们就可以使用application/json的形式提交请求参数。这里的例子是转换请求Json参数中的字符串为LocalDateTime类型,属于Json反序列化,因此需要定制反序列化器:

@PostMapping(value = "/date3")
public String date3(@RequestBody UserDto3 userDto3) {
    log.info(userDto3.toString());
    return "success";
}
@Data
public class UserDto3 {

	private String userId;
	@JsonDeserialize(using = CustomLocalDateTimeDeserializer.class)
	private LocalDateTime birthdayTime;
	@JsonDeserialize(using = CustomLocalDateTimeDeserializer.class)
	private LocalDateTime graduationTime;
}

public class CustomLocalDateTimeDeserializer extends LocalDateTimeDeserializer {

	public CustomLocalDateTimeDeserializer() {
		super(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
	}
}

spring-web.xml中需要如下配置

 
    
        
            
            
            
            
            
            
                
                
                    
                        
                        text/html;charset=UTF-8
                        application/json;charset=UTF-8
                        application/xml;charset=UTF-8
                    
                
                
                    
                        
                            
                                AllowArbitraryCommas
                                AllowUnQuotedFieldNames
                                DisableCircularReferenceDetect
                            
                        
                        
                    
                
            
        
    

部分原理参考:https://juejin.cn/post/6844903648858734600

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/571997.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号