SpringBoot custom parameter resolver
We all know that SpringMvc's Controller method can receive various parameters, such as HttpServletRequest or HttpServletResponse, various annotations @ RequestParam, @ RequestHeader, @ RequestBody, @ PathVariable, @ ModelAttribute. Where are these parameters obtained?
These parameters are parsed for us by different parameter parsers, which can parse classes or annotated classes
- We can use the parser to parse the custom parameters (class, annotation), upload them into the controller method we need (we don't need to do a series of operations inside the method through the request, response and other parameters to get the class object every time)
Add parser
- When we want to customize the parsing parameters, we need to add our own parsing classes by changing the configuration of SpringBoot
The user-defined configuration class implements the WebMvcConfigurer interface, rewrites the addArgumentResolvers method to add its own parsing class (through automatic injection method injection)
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private SecKillUserArgumentResolvers secKillUserArgumentResolvers; @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(secKillUserArgumentResolvers); } }
Implement a custom parser
- We need to implement the HandlerMethodArgumentResolver processor method resolver interface, and the supportsParameter and resolveArgument methods
The interface of HandlerMethodArgumentResolver is defined as follows:
(1) supportsaparameter is used to determine whether the resolution of a parameter is supported (true if supported)
(2) resolveArgument resolves the parameter value in the request to an object (the specific operation gets the resolution object)
The following custom resolver is used to get the User object (get the User saved in redis through token). It does not need to use the request and response to get it inside the controller method every time, but can get it directly as a parameter
/** * Custom parameter resolver * Parse the user to be obtained every time and automatically pass in, without obtaining every time */ @Component public class SecKillUserArgumentResolvers implements HandlerMethodArgumentResolver { @Autowired private UserService userService; @Override public boolean supportsParameter(MethodParameter methodParameter) { Class<?> c = methodParameter.getParameterType(); return c == User.class; } @Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class); HttpServletResponse response = nativeWebRequest.getNativeResponse(HttpServletResponse.class); assert request != null; String paramToken = request.getParameter(UserServiceImpl.COOKIE_NAME_TOKEN);//COOKIE_NAME_TOKEN="token" String cookieToken = getCookieValue(request); if(StringUtils.isEmpty(cookieToken)&&StringUtils.isEmpty(paramToken)){//Get it in two ways. If both fail, return null return null; }else { String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken; return UserService.getUserByToken(response,token); } } //Traverse the cookie to get the value of the cookie with the same name private String getCookieValue(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); for(Cookie cookie:cookies){ if(cookie.getName().equals(SecKillUserServiceImpl.COOKIE_NAME_TOKEN)){ return cookie.getValue(); } } return null; } }
At this point, a custom parameter parser will be implemented. We can get it directly through the parser in the form of incoming parameters
Use of resolution objects
- We pass in this parameter in the controller method, and we can get the User object directly for us
@RequestMapping("/to_list") public String toList(Model model,User user){ }
Other parameter parsers in Springboot
Model
- We know that we can pass in Model object parameters and use it directly. Let's take a look at its parameter parser
ModelAttributeMethodProcessor class
public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(ModelAttribute.class) || this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType()); } @Nullable public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer"); Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory"); String name = ModelFactory.getNameForParameter(parameter); ModelAttribute ann = (ModelAttribute)parameter.getParameterAnnotation(ModelAttribute.class); if (ann != null) { mavContainer.setBinding(name, ann.binding()); } Object attribute = null; BindingResult bindingResult = null; if (mavContainer.containsAttribute(name)) { attribute = mavContainer.getModel().get(name); } else { try { attribute = this.createAttribute(name, parameter, binderFactory, webRequest); } catch (BindException var10) { if (this.isBindExceptionRequired(parameter)) { throw var10; } if (parameter.getParameterType() == Optional.class) { attribute = Optional.empty(); } bindingResult = var10.getBindingResult(); } } if (bindingResult == null) { WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { if (!mavContainer.isBindingDisabled(name)) { this.bindRequestParameters(binder, webRequest); } this.validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } if (!parameter.getParameterType().isInstance(attribute)) { attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); } bindingResult = binder.getBindingResult(); } Map<String, Object> bindingResultModel = bindingResult.getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return attribute; }
RequestParam
RequestParamMethodArgumentResolver class, annotation based
If the annotation exists on the parameter passed in, it can be parsed
public boolean supportsParameter(MethodParameter parameter) { if (parameter.hasParameterAnnotation(RequestParam.class)) { if (!Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) { return true; } else { RequestParam requestParam = (RequestParam)parameter.getParameterAnnotation(RequestParam.class); return requestParam != null && StringUtils.hasText(requestParam.name()); } } else if (parameter.hasParameterAnnotation(RequestPart.class)) { return false; } else { parameter = parameter.nestedIfOptional(); if (MultipartResolutionDelegate.isMultipartArgument(parameter)) { return true; } else { return this.useDefaultResolution ? BeanUtils.isSimpleProperty(parameter.getNestedParameterType()) : false; } } }
PathVariable
The PathVariableMethodArgumentResolver class is also annotation based, similar to RequestParam
public boolean supportsParameter(MethodParameter parameter) { if (!parameter.hasParameterAnnotation(PathVariable.class)) { return false; } else if (!Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) { return true; } else { PathVariable pathVariable = (PathVariable)parameter.getParameterAnnotation(PathVariable.class); return pathVariable != null && StringUtils.hasText(pathVariable.value()); } }
Custom parameter resolver based on annotation
- Write the annotation class. The class to be parsed needs this annotation
@Target(value = ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface ParamModel { }
- Implement parser
public class MyArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter methodParameter) { return methodParameter.hasParameterAnnotation(ParamModel.class);//Parse with comments } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { //Implement parsing object code }
- Configure to WebMvcConfigurer
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private MyArgumentResolvers myArgumentResolvers; @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(myArgumentResolvers); } }