详解如何在SpringBoot中自定义参数解析器(读取springboot配置参数注解)

网友投稿 567 2022-07-30


目录前言1.自定义参数解析器2.PrincipalMethodArgumentResolver3.RequestParamMapMethodArgumentResolver4.小结

前言

在一个 Web 请求中,参数我们无非就是放在地址栏或者请求体中,个别请求可能放在请求头中。

放在地址栏中,我们可以通过如下方式获取参数:

Stringjavaboy=request.getParameter("name");

放在请求体中,如果是 key/value 形式,我们可以通过如下方式获取参数:

Stringjavaboy=request.getParameter("name");

如果是 jsON 形式,我们则通过如果如下方式获取到输入流,然后解析成 JSON 字符串,再通过 JSON 工具转为对象:

BufferedReaderreader=newBufferedReader(newInputStreamReader(request.getInputStream()));

Stringjson=reader.readLine();

reader.close();

Useruser=newObjectMapper().readValue(json,User.class);

如果参数放在请求头中,我们可以通过如下方式获取:

Stringjavaboy=request.getHeader("name");

如果你用的是 Jsp/Servlet 那一套技术栈,那么参数获取无外乎这几种方式。

如果用了 SpringMVC 框架,有的小伙伴们可能会觉得参数获取方式太丰富了,各种注解如 @RequestParam、@RequestBody、@RequestHeader、@PathVariable,参数可以是 key/value 形式,也可以是 JSON 形式,非常丰富!但是,无论多么丰富,最底层获取参数的方式无外乎上面几种。

那有小伙伴要问了,SpringMVC 到底是怎么样从 request 中把参数提取出来直接给我们用的呢?例如下面这个接口:

@RestController

publicclassHelloController{

@GetMapping("/hello")

publicStringhello(Stringname){

return"hello"+name;

}

}

我们都知道 name 参数是从 HttpServletRequest 中提取出来的,到底是怎么提取出来的?这就是松哥今天要和大家分享的话题。

1.自定义参数解析器

为了搞清楚这个问题,我们先来自定义一个参数解析器看看。

自定义参数解析器需要实现 HandlerMethodArgumentResolver 接口,我们先来看看该接口:

publicinterfaceHandlerMethodArgumentResolver{

booleansupportsParameter(MethodParameterparameter);

@Nullable

ObjectresolveArgument(MethodParameterparameter,@NullableModelAndViewContainermavContainer,

NativeWebRequestwebRequest,@NullableWebDataBinderFactorybinderFactory)throwsException;

}

这个接口中就两个方法:

supportsParameter:该方法表示是否启用这个参数解析器,返回 true 表示启用,返回 false 表示不启用。resolveArgument:这是具体的解析过程,就是从 request 中取出参数的过程,方法的返回值就对应了接口中参数的值。

自定义参数解析器只需要实现该接口即可。

假设我现在有这样一个需求(实际上在 Spring Security 中获取当前登录用户名非常方便,这里只是为了该案例而做,勿抬杠):

假设我现在系统安全框架使用了 Spring Security,如果我在接口的参数上添加了 @CurrentUserName 注解,那么该参数的值就是当前登录的用户名,像下面这样:

@RestController

publicclassHelloController{

@GetMapping("/hello")

publicStringhello(@CurrentUserNameStringname){

return"hello"+name;

}

}

要实现这个功能,非常 easy,首先我们自定义一个 @CurrentUserName 注解,如下:

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.PARAMETER)

public@interfaceCurrentUserName{

}

这个注解没啥好解释的。

接下来我们自定义参数解析器 CurrentUserNameHandlerMethodArgumentResolver,如下:

publicclassCurrentUserNameHandlerMethodArgumentResolverimplementsHandlerMethodArgumentResolver{

@Override

publicbooleansupportsParameter(MethodParameterparameter){

returnparameter.getParameterType().isAssignableFrom(String.class)&&parameter.hasParameterAnnotation(CurrentUserName.class);

}

@Override

publicObjectresolveArgument(MethodParameterparameter,ModelAndViewContainermavContainer,NativeWebRequestwebRequest,WebDataBinderFactorybinderFactory)throwsException{

Useruser=(User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();

returnuser.getUsername();

}

}

supportsParameter:如果参数类型是 String,并且参数上有 @CurrentUserName 注解,则使用该参数解析器。resolveArgument:该方法的返回值就是参数的具体值,当前登录用户名从 SecurityContextHolder 中获取即可

最后,我们再将自定义的参数解析器配置到 HandlerAdapter 中,配置方式如下:

@Configuration

publicclassWebConfigimplementsWebMvcConfigurer{

@Override

publicvoidaddArgumentResolvers(Listresolvers){

resolvers.add(newCurrentUserNameHandlerMethodArgumentResolver());

}

}

至此,就算配置完成了。

接下来启动项目,用户登录成功后,访问 /hello 接口,就可以看到返回当前登录用户数据了。

这就是我们自定义的一个参数类型解析器。可以看到,非常 Easy。

在 SpringMVC 中,默认也有很多 HandlerMethodArgumentResolver 的实现类,他们处理的问题也都类似,松哥再给大家举个例子。

2.PrincipalMethodArgumentResolver

如果我们在项目中使用了 Spring Security,我们可以通过如下方式获取当前登录用户信息:

@GetMapping("/hello2")

publicStringhello2(Principalprincipal){

return"hello"+principal.getName();

}

即直接在当前接口的参数中添加 Principal 类型的参数即可,该参数描述了当前登录用户信息,这个用过 Spring Security 的小伙伴应该都知道

那么这个功能是怎么实现的呢?当然就是 PrincipalMethodArgumentResolver 在起作用了!

我们一起来看下这个参数解析器:

publicclassPrincipalMethodArgumentResolverimplementsHandlerMethodArgumentResolver{

@Override

publicbooleansupportsParameter(MethodParameterparameter){

returnPrincipal.class.isAssignableFrom(parameter.getParameterType());

}

@Override

publicObjectresolveArgument(MethodParameterparameter,@NullableModelAndViewContainermavContainer,

NativeWebRequestwebRequest,@NullableWebDataBinderFactorybinderFactory)throwsException{

HttpServletRequestrequest=webRequest.getNativeRequest(HttpServletRequest.class);

if(request==null){

thrownewIllegalStateException("CurrentrequestisnotoftypeHttpServletRequest:"+webRequest);

}

Principalprincipal=request.getUserPrincipal();

if(principal!=null&&!parameter.getParameterType().isInstance(principal)){

thrownewIllegalStateException("Currentuserprincipalisnotoftype["+

parameter.getParameterType().getName()+"]:"+principal);

}

returnprincipal;

}

}

supportsParameter:这个方法主要是判断参数类型是不是 Principal,如果参数类型是 Principal,就支持。resolveArgument:这个方法的逻辑很简单,首先获取原生的请求,再从请求中获取 Principal 对象返回即可。

是不是很简单,有了这个,我们就可以随时加载到当前登录用户信息了。

3.RequestParamMapMethodArgumentResolver

松哥再给大家举个例子:

@RestController

publicclassHelloController{

@PostMapping("/hello")

publicvoidhello(@RequestParamMultiValueMapmap)throwsIOException{

//省略...

}

}

这个接口很多小伙伴可能都写过,使用 Map 去接收前端传来的参数,那么这里用到的参数解析器就是 RequestParamMapMethodArgumentResolver。

publicclassRequestParamMapMethodArgumentResolverimplementsHandlerMethodArgumentResolver{

@Override

publicbooleansupportsParameter(MethodParameterparameter){

RequestParamrequestParam=parameter.getParameterAnnotation(RequestParam.class);

return(requestParam!=null&&Map.class.isAssignableFrom(parameter.getParameterType())&&

!StringUtils.hasText(requestParam.name()));

}

@Override

publicObjectresolveArgument(MethodParameterparameter,@NullableModelAndViewContainermavContainer,

NativeWebRequestwebRequest,@NullableWebDataBinderFactorybinderFactory)throwsException{

ResolvableTyperesolvableType=ResolvableType.forMethodParameter(parameter);

if(MultiValueMap.class.isAssignableFrom(parameter.getParameterType())){

//MultiValueMap

Class>valueType=resolvableType.as(MultiValueMap.class).getGeneric(1).resolve();

if(valueType==MultipartFile.class){

MultipartRequestmultipartRequest=MultipartResolutionDelegate.resolveMultipartRequest(webRequest);

return(multipartRequest!=null?multipartRequest.getMultiFileMap():newLinkedMultiValueMap<>(0));

}

elseif(valueType==Part.class){

HttpServletRequestservletRequest=webRequest.getNativeRequest(HttpServletRequest.class);

if(servletRequest!=null&&MultipartResolutionDelegate.isMultipartRequest(servletRequest)){

Collectionparts=servletRequest.getParts();

LinkedMultiValueMapresult=newLinkedMultiValueMap<>(parts.size());

for(PartpanKNxYrt:parts){

result.add(part.getName(),part);

}

returnresult;

}

returnnewLinkedMultiValueMap<>(0);

}

else{

MapparameterMap=webRequest.getParameterMap();

MultiValueMapresult=newLinkedMultiValueMap<>(parameterMap.size());

parameterMap.forEach((key,values)->{

for(Stringvalue:values){

result.add(key,value);

}

});

returnresult;

}

}

else{

//RegularMap

Class>valueType=resolvableType.asMap().getGeneric(1).resolve();

if(valueType==MultipartFile.class){

MultipartRequestmultipartRequest=MultipartResolutionDelegate.resolveMultipartRequest(webRequest);

return(multipartRequest!=null?multipartRequest.getFileMap():newLinkedHashMap<>(0));

}

elseif(valueType==Part.class){

HttpServletRequestservletRequest=webRequest.getNativeRequest(HttpServletRequest.class);

if(servletRequest!=null&&MultipartResolutionDelegate.isMultipartRequest(servletRequest)){

Collectionparts=servletRequest.getParts();

LinkedHashMapresult=CollectionUtils.newLinkedHashMap(parts.size());

for(Partpart:parts){

if(!result.containsKey(part.getName())){

result.put(part.getName(),part);

}

}

returnresult;

}

returnnewLinkedHashMap<>(0);

}

else{

MapparameterMap=webRequest.getParameterMap();

Mapresult=CollectionUtils.newLinkedHashMap(parameterMap.size());

parameterMap.forEach((key,values)->{

if(values.length>0){

result.put(key,values[0]);

}

});

returnresult;

}

}

}

}

supportsParameter:参数类型是 Map,并且使用了 @RequestParam 注解,并且 @RequestParam 注解中没有配置 name 属性,就可以使用该参数解析器。resolveArgument:具体解析分为两种情况:MultiValueMap 和其他 Map,前者中又分三种情况:MultipartFile、Part 或者其他普通请求,前两者可以处理文件上传,第三个就是普通参数。如果是普通 Map,则直接获取到原始请求参数放到一个 Map 集合中返回即可。

4.小结

前面和大家聊的都是几种简单的情况,还有复杂的如 PathVariableMethodArgumentResolver 和 RequestParamMethodArgumentResolver 松哥以后再和大家详细聊。

以上就是详解如何在SpringBoot中自定义参数解析器的详细内容,更多关于SpringBoot自定义参数解析器的资料请关注我们其它相关文章!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:SpringBoot+kaptcha实现验证码花式玩法详解(kaptcha验证码样式)
下一篇:Java使用正则表达式演示电话与邮箱格式(java邮箱正则表达式)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~