前言
在一个 Web 请求中,参数我们无非就是放在地址栏或者请求体中,个别请求可能放在请求头中。
放在地址栏中,我们可以通过如下方式获取参数:
1
|
String javaboy = request.getParameter( "name " ); |
放在请求体中,如果是 key/value 形式,我们可以通过如下方式获取参数:
1
|
String javaboy = request.getParameter( "name " ); |
如果是 JSON 形式,我们则通过如果如下方式获取到输入流,然后解析成 JSON 字符串,再通过 JSON 工具转为对象:
1
2
3
4
|
BufferedReader reader = new BufferedReader( new InputStreamReader(request.getInputStream())); String json = reader.readLine(); reader.close(); User user = new ObjectMapper().readValue(json, User. class ); |
如果参数放在请求头中,我们可以通过如下方式获取:
1
|
String javaboy = request.getHeader( "name" ); |
如果你用的是 Jsp/Servlet 那一套技术栈,那么参数获取无外乎这几种方式。
如果用了 SpringMVC 框架,有的小伙伴们可能会觉得参数获取方式太丰富了,各种注解如 @RequestParam
、@RequestBody
、@RequestHeader
、@PathVariable
,参数可以是 key/value 形式,也可以是 JSON 形式,非常丰富!但是,无论多么丰富,最底层获取参数的方式无外乎上面几种。
那有小伙伴要问了,SpringMVC 到底是怎么样从 request 中把参数提取出来直接给我们用的呢?例如下面这个接口:
1
2
3
4
5
6
7
|
@RestController public class HelloController { @GetMapping ( "/hello" ) public String hello(String name) { return "hello " +name; } } |
我们都知道 name 参数是从 HttpServletRequest 中提取出来的,到底是怎么提取出来的?这就是松哥今天要和大家分享的话题。
1.自定义参数解析器
为了搞清楚这个问题,我们先来自定义一个参数解析器看看。
自定义参数解析器需要实现 HandlerMethodArgumentResolver 接口,我们先来看看该接口:
1
2
3
4
5
6
7
|
public interface HandlerMethodArgumentResolver { boolean supportsParameter(MethodParameter parameter); @Nullable Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception; } |
这个接口中就两个方法:
- supportsParameter:该方法表示是否启用这个参数解析器,返回 true 表示启用,返回 false 表示不启用。
- resolveArgument:这是具体的解析过程,就是从 request 中取出参数的过程,方法的返回值就对应了接口中参数的值。
自定义参数解析器只需要实现该接口即可。
假设我现在有这样一个需求(实际上在 Spring Security 中获取当前登录用户名非常方便,这里只是为了该案例而做,勿抬杠):
假设我现在系统安全框架使用了 Spring Security,如果我在接口的参数上添加了 @CurrentUserName 注解,那么该参数的值就是当前登录的用户名,像下面这样:
1
2
3
4
5
6
7
|
@RestController public class HelloController { @GetMapping ( "/hello" ) public String hello( @CurrentUserName String name) { return "hello " +name; } } |
要实现这个功能,非常 easy,首先我们自定义一个 @CurrentUserName
注解,如下:
1
2
3
4
|
@Retention (RetentionPolicy.RUNTIME) @Target (ElementType.PARAMETER) public @interface CurrentUserName { } |
这个注解没啥好解释的。
接下来我们自定义参数解析器 CurrentUserNameHandlerMethodArgumentResolver,如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
public class CurrentUserNameHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType().isAssignableFrom(String. class )&¶meter.hasParameterAnnotation(CurrentUserName. class ); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); return user.getUsername(); } } |
-
supportsParameter:如果参数类型是 String,并且参数上有
@CurrentUserName
注解,则使用该参数解析器。 - resolveArgument:该方法的返回值就是参数的具体值,当前登录用户名从 SecurityContextHolder 中获取即可
最后,我们再将自定义的参数解析器配置到 HandlerAdapter 中,配置方式如下:
1
2
3
4
5
6
7
|
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add( new CurrentUserNameHandlerMethodArgumentResolver()); } } |
至此,就算配置完成了。
接下来启动项目,用户登录成功后,访问 /hello
接口,就可以看到返回当前登录用户数据了。
这就是我们自定义的一个参数类型解析器。可以看到,非常 Easy。
在 SpringMVC 中,默认也有很多 HandlerMethodArgumentResolver 的实现类,他们处理的问题也都类似,松哥再给大家举个例子。
2.PrincipalMethodArgumentResolver
如果我们在项目中使用了 Spring Security,我们可以通过如下方式获取当前登录用户信息:
1
2
3
4
|
@GetMapping ( "/hello2" ) public String hello2(Principal principal) { return "hello " + principal.getName(); } |
即直接在当前接口的参数中添加 Principal 类型的参数即可,该参数描述了当前登录用户信息,这个用过 Spring Security 的小伙伴应该都知道
那么这个功能是怎么实现的呢?当然就是 PrincipalMethodArgumentResolver 在起作用了!
我们一起来看下这个参数解析器:
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
|
public class PrincipalMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return Principal. class .isAssignableFrom(parameter.getParameterType()); } @Override public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest. class ); if (request == null ) { throw new IllegalStateException( "Current request is not of type HttpServletRequest: " + webRequest); } Principal principal = request.getUserPrincipal(); if (principal != null && !parameter.getParameterType().isInstance(principal)) { throw new IllegalStateException( "Current user principal is not of type [" + parameter.getParameterType().getName() + "]: " + principal); } return principal; } } |
- supportsParameter:这个方法主要是判断参数类型是不是 Principal,如果参数类型是 Principal,就支持。
- resolveArgument:这个方法的逻辑很简单,首先获取原生的请求,再从请求中获取 Principal 对象返回即可。
是不是很简单,有了这个,我们就可以随时加载到当前登录用户信息了。
3.RequestParamMapMethodArgumentResolver
松哥再给大家举个例子:
1
2
3
4
5
6
7
|
@RestController public class HelloController { @PostMapping ( "/hello" ) public void hello( @RequestParam MultiValueMap map) throws IOException { //省略... } } |
这个接口很多小伙伴可能都写过,使用 Map 去接收前端传来的参数,那么这里用到的参数解析器就是 RequestParamMapMethodArgumentResolver。
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
|
public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { RequestParam requestParam = parameter.getParameterAnnotation(RequestParam. class ); return (requestParam != null && Map. class .isAssignableFrom(parameter.getParameterType()) && !StringUtils.hasText(requestParam.name())); } @Override public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter); if (MultiValueMap. class .isAssignableFrom(parameter.getParameterType())) { // MultiValueMap Class<?> valueType = resolvableType.as(MultiValueMap. class ).getGeneric( 1 ).resolve(); if (valueType == MultipartFile. class ) { MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest); return (multipartRequest != null ? multipartRequest.getMultiFileMap() : new LinkedMultiValueMap<>( 0 )); } else if (valueType == Part. class ) { HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest. class ); if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) { Collection<Part> parts = servletRequest.getParts(); LinkedMultiValueMap<String, Part> result = new LinkedMultiValueMap<>(parts.size()); for (Part part : parts) { result.add(part.getName(), part); } return result; } return new LinkedMultiValueMap<>( 0 ); } else { Map<String, String[]> parameterMap = webRequest.getParameterMap(); MultiValueMap<String, String> result = new LinkedMultiValueMap<>(parameterMap.size()); parameterMap.forEach((key, values) -> { for (String value : values) { result.add(key, value); } }); return result; } } else { // Regular Map Class<?> valueType = resolvableType.asMap().getGeneric( 1 ).resolve(); if (valueType == MultipartFile. class ) { MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest); return (multipartRequest != null ? multipartRequest.getFileMap() : new LinkedHashMap<>( 0 )); } else if (valueType == Part. class ) { HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest. class ); if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) { Collection<Part> parts = servletRequest.getParts(); LinkedHashMap<String, Part> result = CollectionUtils.newLinkedHashMap(parts.size()); for (Part part : parts) { if (!result.containsKey(part.getName())) { result.put(part.getName(), part); } } return result; } return new LinkedHashMap<>( 0 ); } else { Map<String, String[]> parameterMap = webRequest.getParameterMap(); Map<String, String> result = CollectionUtils.newLinkedHashMap(parameterMap.size()); parameterMap.forEach((key, values) -> { if (values.length > 0 ) { result.put(key, values[ 0 ]); } }); return result; } } } } |
-
supportsParameter:参数类型是 Map,并且使用了
@RequestParam
注解,并且@RequestParam
注解中没有配置 name 属性,就可以使用该参数解析器。 - resolveArgument:具体解析分为两种情况:MultiValueMap 和其他 Map,前者中又分三种情况:MultipartFile、Part 或者其他普通请求,前两者可以处理文件上传,第三个就是普通参数。如果是普通 Map,则直接获取到原始请求参数放到一个 Map 集合中返回即可。
4.小结
前面和大家聊的都是几种简单的情况,还有复杂的如 PathVariableMethodArgumentResolver 和 RequestParamMethodArgumentResolver 松哥以后再和大家详细聊。
以上就是详解如何在SpringBoot中自定义参数解析器的详细内容,更多关于SpringBoot自定义参数解析器的资料请关注服务器之家其它相关文章!
原文链接:https://mp.weixin.qq.com/s/mbstcEEtwOS9ZRSSjusbfA