在一个 Web 申请中,参数咱们无非就是放在地址栏或者申请体中,个别申请可能放在申请头中。
放在地址栏中,咱们能够通过如下形式获取参数:
String javaboy = request.getParameter("name");
放在申请体中,如果是 key/value 模式,咱们能够通过如下形式获取参数:
String javaboy = request.getParameter("name");
如果是 JSON 模式,咱们则通过如果如下形式获取到输出流,而后解析成 JSON 字符串,再通过 JSON 工具转为对象:
BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));
String json = reader.readLine();
reader.close();
User user = new ObjectMapper().readValue(json, User.class);
如果参数放在申请头中,咱们能够通过如下形式获取:
String javaboy = request.getHeader("name");
如果你用的是 Jsp/Servlet 那一套技术栈,那么参数获取无外乎这几种形式。
如果用了 SpringMVC 框架,有的小伙伴们可能会感觉参数获取形式太丰盛了,各种注解如 @RequestParam
、@RequestBody
、@RequestHeader
、@PathVariable
,参数能够是 key/value 模式,也能够是 JSON 模式,十分丰盛!然而, 无论如许丰盛,最底层获取参数的形式无外乎下面几种。
那有小伙伴要问了,SpringMVC 到底是怎么样从 request 中把参数提取进去间接给咱们用的呢?例如上面这个接口:
@RestController
public class HelloController {@GetMapping("/hello")
public String hello(String name) {return "hello"+name;}
}
咱们都晓得 name 参数是从 HttpServletRequest 中提取进去的,到底是怎么提取进去的?这就是松哥明天要和大家分享的话题。
1. 自定义参数解析器
为了搞清楚这个问题,咱们先来自定义一个参数解析器看看。
自定义参数解析器须要实现 HandlerMethodArgumentResolver 接口,咱们先来看看该接口:
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(对 Spring Security 不相熟的小伙伴,能够在公众号江南一点雨后盾回复 ss,有教程),如果我在接口的参数上增加了 @CurrentUserName 注解,那么该参数的值就是以后登录的用户名,像上面这样:
@RestController
public class HelloController {@GetMapping("/hello")
public String hello(@CurrentUserName String name) {return "hello"+name;}
}
要实现这个性能,十分 easy,首先咱们自定义一个 @CurrentUserName
注解,如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface CurrentUserName {}
这个注解没啥好解释的。
接下来咱们自定义参数解析器 CurrentUserNameHandlerMethodArgumentResolver,如下:
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 中获取即可(具体参数松哥的 Spring Security 教程,公号后盾回复 ss)。
最初,咱们再将自定义的参数解析器配置到 HandlerAdapter 中,配置形式如下:
@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,咱们能够通过如下形式获取以后登录用户信息:
@GetMapping("/hello2")
public String hello2(Principal principal) {return "hello" + principal.getName();
}
即间接在以后接口的参数中增加 Principal 类型的参数即可,该参数形容了以后登录用户信息,这个用过 Spring Security 的小伙伴应该都晓得(不相熟 Spring Security 的小伙伴能够在公众号【江南一点雨】后盾回复 ss)。
那么这个性能是怎么实现的呢?当然就是 PrincipalMethodArgumentResolver 在起作用了!
咱们一起来看下这个参数解析器:
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
松哥再给大家举个例子:
@RestController
public class HelloController {@PostMapping("/hello")
public void hello(@RequestParam MultiValueMap map) throws IOException {// 省略...}
}
这个接口很多小伙伴可能都写过,应用 Map 去接管前端传来的参数,那么这里用到的参数解析器就是 RequestParamMapMethodArgumentResolver。
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 松哥当前再和大家具体聊。同时还有一个问题就是这些参数解析器具体是在哪里调用的,这个也会在松哥近期的 SpringMVC 源码解析系列文章中和大家分享,好啦,明天周末,就这点简略的小常识祝大家周末欢快~