SpringMVC参数解析全解与JSON属性分解

PRE-HTTP请求

结构

HTTP请求结构

实例

HTTP请求实例

处理函数

处理函数可以通过多种方式定义,其最主要还是通过注解方式,即@RequestMapping方式。Spring为不同的HTTP方法提供了简化的注解,分别如下:

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

另外还可以在WebConfig中手动配置,但这种方法用得不多,这里就不再赘述。

处理函数接受的参数

处理函数由于本质上还是由SpringMVC来调用,因此SpringMVC是可以通过反射获取到特定处理函数的参数列表的。所以SpringMVC提供了灵活的参数选择,可以填写多种参数,具体如下表:

参数 描述
ServerWebExchange 获取一个ServerWebExchange对象,这个对象是由Spring定义的一个类,其相当于是整个HTTP过程的容器,可以从中获取request,response或者attributes等。
ServerHttpRequest
ServerHttpResponse
获取一个原始的HTTP request或者response对象
WebSession 获取一个session对象。(如果不增加session值,不会新创建session)
java.security.Principal 获取一个Spring Security的Principal对象,实际上就是当前用户认证信息。(但用的不多,需要时再研究)
org.springframework.http.HttpM ethod 获取当前请求的HTTP方法。(既然定义了,肯定是知道方法的,所以用得也不多)
java.util.Locale 获取当前请求的Locale对象,可以通过配置LocaleResolver/LocaleContextResolver来决定。
java.util.TimeZone
java.time.ZoneId
获取当前request的时区对象,也是由LocaleContextResolver来决定。
@PathVariable 获取URL模板变量。详细见下
@MatrixVariable 获取在URL中定义的通过分号间隔的键值对变量。详细见下
@RequestParam 获取Servlet request的请求参数。参数值会转换到指定的类型。详细见下
@RequestParam是可选的,简单类型如果不加,则相当于加上了该注解。
@RequestHeader 获取请求头部。头部值会被转换为指定的类型。详细见下
@CookieValue 获取指定cookie值。cookie值会被转换为指定类型。详细见下
@RequestBody 获取HTTP请求的body部分。body部分会通过HttpMessageConverter实例来转换为方法中给出的目标参数类型。详细见下
HttpEntity<B> 获取请求的header和body。body部分会通过HttpMessageConverter实例来转换为方法中给出的目标参数类型。
@RequestPart 获取 multipart/form-data请求中的一个部分。
java.util.Map
org.springframework.ui.Model
org.springframework.ui.ModelMa p
用在模板系统中的注入的变量。(待确定)
@ModelAttribute 通过应用数据绑定和验证访问模型中的现有属性(如果不存在,则实例化)。(待确定)
Errors
BindingResult
获取验证和绑定对象时的错误。(必须在验证过的方法参数之后立即声明Errors或BindingResult参数。)
SessionStatus
class-level@SessionAttributes
UriComponentsBuilder 用于获取相对于当前请求的主机、端口、方案和上下文路径的URL。
@SessionAttribute 用于获取会话属性-与存储在会话中的作为类级别结果的模型属性相反@SessionAttributes声明。
@RequestAttribute 获取请求的参数。即request的attribute。
其他类型参数 如果这个参数不匹配以上任意的参数。则如果是简单类型,就默认添加@RequestParam,否则就默认添加@ModelAttribute

详细解析

类型转换

从HTTP协议种得到的数据归根到底都是字符串,而有时候我们的处理函数中的参数不仅仅是字符串。这个时候就需要转换器将字符串转换到对应的类型,比如数字类型等。

可以在Webconfig中添加转换器,如:

1
2
3
4
5
6
7
8
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
WebMvcConfigurer.super.configureMessageConverters(converters);
converters.add(new MappingJackson2HttpMessageConverter());
converters.add(new StringHttpMessageConverter());
}
}

@RequestParam

这个注解是用来将URL上的query部分转换为处理函数的参数。例如URL=http://baidu.com/addUser?user=123&group=1中的query就是user=123group=1

那么可以这样获取参数:

1
2
3
4
5
@GetMapping("/addUser")
public void addUser(@RequestParam String user, @RequestParam Integer group){
System.out.println(user);
System.out.println(group);
};

值得注意的是,这个注解是默认添加的,即时不加,也可以获取query的参数。所以下面的代码又是一样的:

1
2
3
4
5
@GetMapping("/addUser")
public void addUser(String user, Integer group){
System.out.println(user);
System.out.println(group);
};

值得注意的是,该注解还可以接受其他参数为集合类型,比如Map,List。

例如:

Map接收

URL:http://localhost:8080/xpan/file/file/compressFile?d=123&dd=ddd

处理函数:

1
2
3
4
@PostMapping("/compressFile")
public void compressFile(@RequestParam Map<String, String> res){
System.out.println(res);
}

结果:

1
{d=123, dd=ddd}

说明:

用Map接收的时候,不需要在乎参数的名字,因为其名字会作为Map的键。

List接收

URL:http://localhost:8080/xpan/file/file/compressFile?res=123&res=ddd

处理函数:

1
2
3
4
@PostMapping("/compressFile")
public void compressFile(@RequestParam List<String> res){
System.out.println(res);
}

结果:

1
[123, ddd]

说明:

使用List的时候就需要注意,提交的参数名字必须和接收的参数名字一样,或者使用@RequestParam("xxx")的形式来获取xxx的值。

数组接收

URL:http://localhost:8080/xpan/file/file/compressFile?res=123&res=ddd

处理函数:

1
2
3
4
@PostMapping("/compressFile")
public void compressFile(@RequestParam String[] res){
System.out.println(Arrays.toString(res));
}

结果:

1
[123, ddd]

说明:

使用数组和List基本一致,就是名字也需要保持一致,或者在注解中自定义。

源码位置在RequestParamMethodArgumentResolver。待解析。

@RequestBody

这个注解是用来获取HTTP请求的body部分参数(见前面的HTTP结构图)。

并且可以通过converter将其转换为确定的类型。

这个注解也是需要converter来支持特定的request类型,即header中的content-type属性。即实际上body部分就是一串字符串,我们需要约定特定的格式来进行交互,而这个格式得有一个名字,这个名字就存储在content-type中。然后就按照这种格式来编码body得内容。

而我们一般使用的是JSON格式进行交互,所以此时的content-type就为application/json

那么就需要一个JSON的转换器,这个转换器spring官方是没有内置的,需要我们自己添加,比如Jackson,可以在Webconfig中进行配置:

1
2
3
4
5
6
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
WebMvcConfigurer.super.configureMessageConverters(converters);
converters.add(new MappingJackson2HttpMessageConverter());
converters.add(new StringHttpMessageConverter()); //添加这一个是为了解决StringHttpMessageConverter在前面导致String在统一返回对象时出现错误
}

接收JSON字符串

URL:http://localhost:8080/xpan/file/folder/createFolder

body:

1
2
3
4
5
6
7
8
{
"folder_name": "myfolder",
"depth": 1,
"pid": 0,
"size": 0,
"sub_folder_count": 0,
"sub_file_count": 0
}

处理函数:

1
2
3
4
@PostMapping("/createFolder")
public void createFolder(@RequestBody Folder folder){
System.out.println(folder);
}

结果:

1
Folder(id=null, user_id=null, folder_name=myfolder, depth=1, icon=null, pid=0, size=0, sub_folder_count=0, sub_file_count=0, gmt_statistics=null, gmt_create=null, gmt_update=null)

值得注意的是:这里还有一些Jackson的配置,比如默认是可以忽略参数的,即如果一些参数不带也是可以解析的。同样可以设置为全参数,否则报错的模式。

@PathVariable

这个注解可以用于获取URI中的变量,与requestParam,这里是可以通过自定义的格式获取URI中的某一部分作为路径变量,然后注入到处理方法的参数中。

其中URI的匹配规则如下:

Pattern Description Example
? 匹配一个字符 /pages/t?st.html 匹配 /pages/test.html/pages/t3st.html
* 匹配0个或多个字符(在一个路径段中) /resources/*.png 匹配/resources/file.png
/projects/*/versions 匹配 /projects/spring/versions
但是不匹配/projects/spring/boot/versions
** 匹配直到路径尾部的一个或多个路径段 /resources/** 匹配 /resources/file.png/resources/images/file.png
/resources/**/file.png 是不合法的,因为**只允许在路径尾部。
{name} 匹配一个路径段并且将其捕捉为一个名为name的变量 /projects/{project}/versions 匹配 /projects/spring/versions 并且捕捉为
project=spring
{name:[a-z]+} 匹配[a-z]+为一个名为name的变量 /projects/{project:[a-z]+}/versions 匹配/projects/spring/versions 但不匹配
/projects/spring1/versions
{*path} 匹配直到路径尾部的一个或多个路径段并作为一个名为path的变量

注:

  • 路径段(path segment):指两个反斜杠内的部分。如https://www.baidu.com/xxx/yyy其中xxx就是一个路径段。

接下来可以在处理方法中的参数中使用@PathVariable来获取路径变量。

如:

URL:http://localhost:8080/xpan/file/file/compressFile/12345

处理函数:

1
2
3
4
@PostMapping("/compressFile/{userId}")
public void compressFile(@PathVariable("userId") Integer userId){
System.out.println(userId);
}

结果:

1
12345

当然也可以用其他更复杂,比如正则表达式来接收路径变量。

@MatrixVariable

矩阵变量(Matrix Variable)其实很简单,就是在路径段中的键值对。

与路径query的作用一致,只是形式不同。矩阵变量是以;分号作为URL结尾与矩阵变量开始的分隔符(query中是是?)和不同的矩阵变量之间的分隔符(query中是是&)。而同一个name的不同值用,分割。同样的,键值之间仍然是以=作为连接符的。

例如/cars;color=red,green;year=2012中的矩阵变量为:color=red,green;year=2012

@RequestParam一致,也支持List和数组接收。

普通参数

URL:http://localhost:8080/xpan/file/file/compressFile/12345;groupId=111;username=hhhhh

处理函数:

1
2
3
4
5
6
@PostMapping("/compressFile/{userId}")
public void compressFile(@PathVariable("userId") Integer userId, @MatrixVariable("groupId") String groupId, @MatrixVariable("username") String username){
System.out.println(userId);
System.out.println(groupId);
System.out.println(username);
}

结果:

1
2
3
12345
111
hhhhh

List接收

URL:http://localhost:8080/xpan/file/file/compressFile/12345;groupId=111;username=hhhhh;color=red,blue

处理函数:

1
2
3
4
5
6
7
@PostMapping("/compressFile/{userId}")
public void compressFile(@PathVariable("userId") Integer userId, @MatrixVariable("groupId") String groupId, @MatrixVariable("username") String username, @MatrixVariable("color") List color){
System.out.println(userId);
System.out.println(groupId);
System.out.println(username);
System.out.println(color);
}

结果:

1
2
3
4
12345
111
hhhhh
[red, blue]

数组接收

URL:http://localhost:8080/xpan/file/file/compressFile/12345;groupId=111;username=hhhhh;color=red,blue

处理函数:

1
2
3
4
5
6
7
@PostMapping("/compressFile/{userId}")
public void compressFile(@PathVariable("userId") Integer userId, @MatrixVariable("groupId") String groupId, @MatrixVariable("username") String username, @MatrixVariable("color") String[] color){
System.out.println(userId);
System.out.println(groupId);
System.out.println(username);
System.out.println(Arrays.toString(color));
}

结果:

1
2
3
4
12345
111
hhhhh
[red, blue]

Map接收

使用map接收的时候,@MatrixVariable中就不需要填写参数了。例如:

URL:http://localhost:8080/xpan/file/file/compressFile/12345;groupId=111;username=hhhhh;color=red,blue

处理函数:

1
2
3
4
5
6
@PostMapping("/compressFile/{userId}")
public void compressFile(@PathVariable("userId") Integer userId, @MatrixVariable("groupId") String groupId, @MatrixVariable Map matrixVars){
System.out.println(userId);
System.out.println(groupId);
System.out.println(matrixVars);
}

结果:

1
2
3
12345
111
{groupId=111, username=hhhhh, color=red}

这里有两个点值得注意:

  • 矩阵变量可以单独读取和Map读取同时进行,即一个矩阵变量在多个处理方法的参数中。
  • 当使用Map接收的时候,如果不指定泛型类型,则默认为<String, String>,所以后面color只解析出一个值。

如果要克服也很简单,就是将泛型类型指定为List,如下:

1
2
3
4
5
6
@PostMapping("/compressFile/{userId}")
public void compressFile(@PathVariable("userId") Integer userId, @MatrixVariable("groupId") String groupId, @MatrixVariable Map<String, List> matrixVars){
System.out.println(userId);
System.out.println(groupId);
System.out.println(matrixVars);
}

那么就能正常读取结果:

1
2
3
12345
111
{groupId=[111], username=[hhhhh], color=[red, blue]}

矩阵变量与路径参数的匹配

矩阵变量与路径参数是可以匹配的,即如果矩阵变量名字相同,可以通过前置的路径变量进行定位。例如:

URL:http://localhost:8080/xpan/file/file/compressFile/12345;order=111/123;order=222

处理函数:

1
2
3
4
5
6
7
@PostMapping("/compressFile/{userId}/{groupId}")
public void compressFile(@PathVariable("userId") Integer userId, @MatrixVariable(name = "order", pathVar = "userId") Integer userOrder, @PathVariable("groupId") Integer groupId, @MatrixVariable(name = "order", pathVar = "groupId") String groupOrder){
System.out.println(userId);
System.out.println(userOrder);
System.out.println(groupId);
System.out.println(groupOrder);
}

结果:

1
2
3
4
12345
111
123
222

@RequestHeader

这个注解很简单,就是将header中的属性注入到处理函数的参数中。例如:

处理函数:

1
2
3
4
5
@PostMapping("/compressFile")
public void compressFile(@RequestHeader("Host") String host, @RequestHeader("Accept") String accept){
System.out.println(host);
System.out.println(accept);
}

结果:

1
2
localhost:8080
*/*

@CookieValue

这个注解是用来获取cookie的值并注入到处理函数的参数中,例如:

cookie(附在HTTP header中):

Cookie: userId=123

处理函数:

1
2
3
4
@PostMapping("/compressFile")
public void compressFile(@CookieValue("userId") Integer userId){
System.out.println(userId);
}

结果:

1
123

JSON属性分解

自定义注解

Java中注解的定义也很简单,但是需要注意的是需要加上另外两个注解:

  • Target:当前注解被用的位置(参数、方法、类等),参数是java.lang.annotation.ElementType
  • Retention:注解存留的时间(源代码、类、运行时),参数是java.lang.annotation.RetentionPolicy

HandlerMethodArgumentResolver

HandlerMethodArgumentResolver接口可以用来解析HTTP请求,并且给处理函数的参数进行赋值。相当于自己实现一个参数解析的过程,本来springMVC中是有这一部分的实现,比如@RequestParamRequestParamMethodArgumentResolver

这个接口也很简单,只含有两个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

package org.springframework.web.method.support;

import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;

public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);

@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
  • supportsParameter:该参数解析器是否支持当前参数。
  • resolveArgument:返回解析后的对象,这个对象会直接赋值给当前参数。

自定义解析JSON

利用上述两个知识和Jackson,就可以实现自定义的JSON解析方式。理论上可以分为2步:

  • 定义一个注解,并且TargetElementType.PARAMETERRetentionRetentionPolicy.RUNTIME,即确保整个注解是加在参数上,保留到运行时。
  • 定义一个HandlerMethodArgumentResolver,support方法查看这个参数是否有上面定义的注解,resolveArgument方法利用Jackson来解析字符串。

一个简单的例子如下,其功能是解析JSON中包含的user_id并返回:

注解类:

1
2
3
4
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserId {
}

UserIdHandlerMethodArgumentResolver类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* @Auther: MichaelWang
* @Date: 2022/6/12 19:45
* @Description: UserIdHandlerMethodArgumentResolver
* @Version 1.0.0
*/
@Component
public class UserIdHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
private final String[] userMethodIdExpList = {"uid", "id"};

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(UserId.class);
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String token = webRequest.getHeader("Authorization");
Integer id = JWTUtils.getIdFromToken(token.substring(7));
if(id == null){
throw new AuthenticationException("身份认证失败!");
}
Class<?> parameterType = parameter.getParameterType();

// 简单类型参数
if (parameterType == Integer.class || parameterType == int.class){
return id;
}else{
String requestJsonString = HttpUtils.getRequestJsonString(webRequest.getNativeRequest(HttpServletRequest.class));
Object o = JSONUtils.JSON2Object(requestJsonString, parameterType);
for (String s : userMethodIdExpList) {
PropertyDescriptor pd = new PropertyDescriptor(s,parameterType);
Method writeMethod = pd.getWriteMethod();
writeMethod.invoke(o, id);
return o;
}
throw new ServerException("UserId注解obj模式下,参数对象必须含有uid/id字段");
}
}
}

注:这里面还加入了身份认证和参数类型验证等。

JSON属性分解注入

在面对前端传入的JSON数据,有时候可能并不是系统内定义的实体类,可能只是分开的几个参数,此时一般采用Map来接收,但是这样并不好做验证。所以我就想实现一个JSON属性分解,然后分别注入到处理函数的参数。所以一个简单的实现如下:

注解类:

1
2
3
4
5
6
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface SingleRequestBody {
String value() default "-1";
}

UserIdHandlerMethodArgumentResolver类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package priv.mw.xpan.resolver;

import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import priv.mw.xpan.annotation.SingleRequestBody;
import priv.mw.xpan.utils.HttpUtils;
import priv.mw.xpan.utils.JSONUtils;
import priv.mw.xpan.utils.XPanRequestWrapper;

import java.util.HashMap;

/**
* @Auther: MichaelWang
* @Date: 2022/6/20 18:15
* @Description: SingalMethodParamResolver
* @Version 1.0.0
*/
@Component
public class SingleMethodParamResolver implements HandlerMethodArgumentResolver {

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(SingleRequestBody.class);
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String requestJsonString = HttpUtils.getRequestJsonString(webRequest.getNativeRequest(XPanRequestWrapper.class));
HashMap<String, String> jsonObject = JSONUtils.JSON2Object(requestJsonString, HashMap.class);
SingleRequestBody singleRequestBody = parameter.getParameterAnnotation(SingleRequestBody.class);
String value = singleRequestBody.value();
if(!value.equals("-1")){
return jsonObject.get(value);
}else {
String parameterName = parameter.getParameterName();
return jsonObject.get(parameterName);
}
}
}

测试:

1
2
3
4
@PostMapping("/renameFile")
public void renameFile(@SingleRequestBody String oldName){
System.out.println(oldName);
}

完全没问题。

但是在测试如下代码时:

1
2
3
4
5
6
7
@PostMapping("/renameFile")
public void renameFile(@SingleRequestBody String oldName,@SingleRequestBody String newName,@SingleRequestBody Integer pId, @UserId Integer userId){
System.out.println(oldName);
System.out.println(newName);
System.out.println(pId);
System.out.println(userId);
}

这样就出现问题了,解析第一个参数时没问题,但是在解析第二个参数时就没办法从request中读取body部分了。

经过搜索才发现request的body是一个流,读取一次过后body指针就到最后的位置了,第二次就没办法读取了。

解决request body只能读取一次

这个问题网上也提供了一种比较好的把那份,即使用自定义的HttpServletRequestWrapper来包装一次request,这个HttpServletRequestWrapper需要重新定义一下body的读取方式。这里的思路也很简单,就是定义一个私有的body变量。读取的时候,如果已经有了就直接读取了。

下面给出一个示例(来自网上):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/**
* @Auther: MichaelWang
* @Date: 2022/6/20 19:14
* @Description: XPanRequestWrapper
* @Version 1.0.0
*/
public class XPanRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;

/**
* Construct a wrapper for the specified request.
*
* @param request The request to be wrapped
*/
public XPanRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = IOUtils.toByteArray(super.getInputStream());
}

@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}

@Override
public ServletInputStream getInputStream() throws IOException {
return new RequestBodyCachingInputStream(body);
}

private class RequestBodyCachingInputStream extends ServletInputStream {
private byte[] body;
private int lastIndexRetrieved = -1;
private ReadListener listener;

public RequestBodyCachingInputStream(byte[] body) {
this.body = body;
}

@Override
public int read() throws IOException {
if (isFinished()) {
return -1;
}
int i = body[lastIndexRetrieved + 1];
lastIndexRetrieved++;
if (isFinished() && listener != null) {
try {
listener.onAllDataRead();
} catch (IOException e) {
listener.onError(e);
throw e;
}
}
return i;
}

@Override
public boolean isFinished() {
return lastIndexRetrieved == body.length - 1;
}

@Override
public boolean isReady() {
// This implementation will never block
// We also never need to call the readListener from this method, as this method will never return false
return isFinished();
}

@Override
public void setReadListener(ReadListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener cann not be null");
}
if (this.listener != null) {
throw new IllegalArgumentException("listener has been set");
}
this.listener = listener;
if (!isFinished()) {
try {
listener.onAllDataRead();
} catch (IOException e) {
listener.onError(e);
}
} else {
try {
listener.onAllDataRead();
} catch (IOException e) {
listener.onError(e);
}
}
}

@Override
public int available() throws IOException {
return body.length - lastIndexRetrieved - 1;
}

@Override
public void close() throws IOException {
lastIndexRetrieved = body.length - 1;
body = null;
}


}
}

经过以上的步骤,还需要定义一个Filter,用来每次替换request:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @Auther: MichaelWang
* @Date: 2022/6/20 19:15
* @Description: RequestWrapperFilter
* @Version 1.0.0
*/
@Component
public class XPanRequestWrapperFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if(!(request instanceof XPanRequestWrapper)){
request = new XPanRequestWrapper(request);
}
filterChain.doFilter(request, response);
}
}

HttpServletRequestWrapper无法处理muilti-part文件

解决了上面的问题,又发现上传文件时就无法读取到文件了,这里有一个曲线救国的方法:

就是检测content-typemultipart/form-data的时候,就不进行包装。

所以Filter定义需要重新加一段逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* @Auther: MichaelWang
* @Date: 2022/6/20 19:15
* @Description: RequestWrapperFilter
* @Version 1.0.0
*/
@Component
public class XPanRequestWrapperFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
/**
* TODO: 完善文件上传时,request的封装
*/
String contentType = request.getContentType();
String method = "multipart/form-data";
if (contentType != null && contentType.contains(method)) {
// 将转化后的 request 放入过滤链中
request = new StandardServletMultipartResolver().resolveMultipart(request);
}else{
if(!(request instanceof XPanRequestWrapper)){
request = new XPanRequestWrapper(request);
}
}
filterChain.doFilter(request, response);
}
}

等后期研究一下,我觉得还有更好的解决方案,即完善这个HttpServletRequestWrapper,让它也支持multi-part文件。

Powered by Hexo and Hexo-theme-hiker

Copyright © 2019 - 2024 My Wonderland All Rights Reserved.

UV : | PV :