乐趣区

日常使用SpringBoot自定义参数解析器

SpringBoot 自定义参数解析器

我们都知道 SpringMvc 的 Controller 的方法上可以接收各种各样的参数,比如 HttpServletRequestHttpServletResponse,各种注解@RequestParam@RequestHeader@RequestBody@PathVariable@ModelAttribute,这些参数是从哪里获取的?

这些参数都是由不同的参数解析器为我们解析出来的,可以解析类也可以解析带注解的类

  • 我们可以利用解析器解析自定义的参数 (类、注解),在我们需要的传入的 controller 方法上传入它(不需要每次都要在方法内部通过requestresponse 等参数做一系列操作来获取该类对象)

添加解析器

  • 我们想要自定义解析参数的时候我们就需要通过更改 SpringBoot 的配置来添加自己实现的解析类

自定义配置类实现 WebMvcConfigurer 接口,重写其中的 addArgumentResolvers 方法来添加自己的解析类(通过自动注入的方法注入)

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private SecKillUserArgumentResolvers secKillUserArgumentResolvers;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {argumentResolvers.add(secKillUserArgumentResolvers);
    }
}

实现自定义解析器

  • 我们自定义解析器需要实现 HandlerMethodArgumentResolver 处理器方法解析器 接口,并实现其中的 supportsParameterresolveArgument方法

HandlerMethodArgumentResolver的接口定义如下:

(1)supportsParameter 用于判断是否支持对某种参数的解析(支持则返回 true)

(2)resolveArgument 将请求中的参数值解析为某种对象(具体的操作获取解析对象)

下面这个自定义解析器用于获取 User 对象(通过 token 获取保存在 redis 中的 user),无需每次使用 resquest 和 response 在 controller 方法内部获取,可以直接获取到作为参数传入

/**
 * 自定义参数解析器
 * 解析每次都要获取的 user 自动传入,无需每次获取
 */
@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)){// 通过两种方式获取,如果都获取失败则返回 null
            return null;
        }else {String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
            return UserService.getUserByToken(response,token);
        }
    }
// 遍历 cookie 获取名称相同的那个 cookie 的值
    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;
    }
}

到此,一个自定义参数解析器就实现好了,我们可以通过传入参数的形式直接通过解析器帮我们获取到

解析对象的使用

  • 我们在 controller 方法中传入该参数,可以直接为我们获取到 User 对象
@RequestMapping("/to_list")
    public String toList(Model model,User user){}

Springboot 中其他参数解析器

Model

  • 我们知道我们可以传入 Model 对象参数就可以直接使用它,我们看看它的参数解析器

ModelAttributeMethodProcessor

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类,基于注解的方式

如果传入参数 parameter 上有该注解,则可以解析

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

PathVariableMethodArgumentResolver类,也是基于注解,与 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());
        }
    }

基于注解自定义参数解析器

  • 编写注解类,需要解析的类需要有此注解
@Target(value = ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ParamModel {}
  • 实现解析器
public class MyArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {return methodParameter.hasParameterAnnotation(ParamModel.class);// 带有注解就解析
    }
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {// 实现解析对象代码}
  • 配置到WebMvcConfigurer
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private MyArgumentResolvers myArgumentResolvers;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {argumentResolvers.add(myArgumentResolvers);
    }
}
退出移动版