SpringBoot集成Springmvc之参数解析器
在SpringBoot项目中调试SpringMVC
目录- 在SpringBoot项目中调试SpringMVC
- 0、概述
- 1、SpringBoot在启动的时候开始来加载@RequestMapping注解中的信息
- 2、确定请求参数
- 2.1、参数解析器
- RequestParamMethodArgumentResolver
- 判断是否支持
- 解析
- RequestParamMapMethodArgumentResolver
- 判断
- 解析
- PathVariableMethodArgumentResolver
- 判断是否支持
- 解析
- PathVariableMapMethodArgumentResolver
- 判断是否支持
- 解析
- MatrixVariableMethodArgumentResolver
- MatrixVariableMapMethodArgumentResolver
- ServletRequestMethodArgumentResolver
- 判断是否支持
- 解析
- ServletRequestMapMethodArgumentResolver
- RequestResponseBodyMethodProcessor
- 判断是否支持
- 解析
- RequestPartMethodArgumentResolver
- 判断是否支持
- 解析
- RequestHeaderMethodArgumentResolver
- 判断是否支持
- 解析
- RequestHeaderMapMethodArgumentResolver
- 判断是否支持
- 解析
- ServletModelAttributeMethodProcessor
- 判断是否支持
- 解析
- RequestResponseBodyMethodProcessor
- 判断是否支持
- 解析
- RequestParamMethodArgumentResolver
- 2.1、参数解析器
- 3、支持类型
- 注解类型
- ServletAPI
- 复杂参数
- 4、定制化web操作
- 示例一
- 一、问题描述
- 二、假设分析
- 三、应用技术
- 四、代码实现
- 示例二
- 1、问题描述
- 2、代码实现
- 1、定义注解
- 2、自定义参数解析器
- 3、添加到配置中
- 示例一
- 5、总结
- 5.1、参数解析器
- 5.2、类型转换器
- 5.3、自定义参数解析器
- 5.4、自定义SpringMVC中的组件
- 5.5、类型转换器和自定义参数解析器的理解
0、概述
学习调试SpringBoot项目中的启动过程并总结一下其中的一些具体的流程。
首先在SpringBoot项目中,在yml文件中填充项目的虚拟路径和DispatcherServlet的默认路径:
server:
servlet:
# 表示的是访问整个web应用需要带上的路径
context-path: /web
spring:
mvc:
servlet:
# 访问DispatcherServlet的路径,而默认是/,表示的是拦截所有的请求,不拦截jsp
path: /
所以修改之后的系统的访问路径就变成了:http://ip:port/web/
通过这种方式才能够来进行访问到对应的路径。
对于SpringMVC来说,所有的请求都需要从DispatcherServlet开始,而DispatcherServlet也是一个Servlet,所以也应该有对应的doPost和doService方法。
FrameworkServlet重写了其中的DoXxx方法,将所有的请求给
processRequest(request, response);
来进行处理FrameworkServlet在进行处理的时候调用了doService方法来进行处理。
这里有个点:
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
// 可以获取得到request和response对象
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
而doService方法在FrameworkServlet没有来进行实现,而是由子类DispatcherServlet来进行实现的,而DispatcherServlet在进行处理的时候又会调用doDispatch方法,所以所有的重心都会来到doDispatch方法中来
1、SpringBoot在启动的时候开始来加载@RequestMapping注解中的信息
1、扫描标注了@RestQuestMapping注解中的值
注:如果在@RequestMapping(path="hello")和@RequestMapping(path="/hello")的是一样的,springMVC会自动的来对其进行处理
从org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod()方法中来寻找得到对应的最佳匹配路径:
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List matches = new ArrayList<>();
List directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
Comparator comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = matches.get(0);
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
}
}
可以看到上面的一个异常信息,如果路径写重复了,当然还会根据请求方式等方式决定使用哪个Handler来对当前的请求来进行请求。如果写了多个相同的uri,并且请求方式等都是一样的,那么启动的时候就会来报出错误信息:
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
也就是多个controller方法中标注了@RequestMapping中的path是一样的值,那么将会导致无法匹配到具体的方法。
这里返回的是哪个controller类中的哪个方法(HandlerMethod)来处理当前的请求,找到了对应的handler之后,那么就应该来对请求来进行处理了,在处理之前,需要将handler上的方法进行确定,确定完成之后,使用反射来进行调用方法,然后获取得到方法的返回值,在此之后再对方法返回值来做处理。所以首先将重心放在是如何确定方法上的参数值的以及方法上能够写多少种参数类型的参数。
因为需要上面的操作过程,springmvc中将这样的一个操作过程封装成了一个类来进行处理:HandlerAdapter,在这个类中来进行处理。
2、确定请求参数
首先会从容器中拿到所有的参数解析器来针对于当前请求确定方法请求参数的值。所谓的参数解析器就是针对各种各样的方法来进行处理,如:参数上有什么注解、方法参数的类型以及方法上有什么样的注解等信息来决定使用哪个参数解析来进行处理
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
最终调用到invokeAndHandle方法来进行处理:
invocableMethod.invokeAndHandle(webRequest, mavContainer);
然后执行方法来确定方法中参数的值:
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
看下具体的执行细节:
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
首先获取得到所有的handler中写的参数的值,然后判断方法上是否标注了对应的值,如果方法参数没有写参数值,那么就直接返回,而不会直接来进行处理。如果方法参数上有值,那么获取得到所有的值之后,封装成数组,遍历循环来确定下来参数的值。
遍历循环每个参数,判断哪个参数解析器是支持当前的参数的。
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
那么断点查看一下,有多少种参数解析器以及如何来对每个参数进行解析的。
2.1、参数解析器
那么来确定下每个参数都支持哪些参数的解析
RequestParamMethodArgumentResolver
判断是否支持
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(RequestParam.class)) {
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
return (requestParam != null && StringUtils.hasText(requestParam.name()));
}
else {
return true;
}
}
else {
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return false;
}
parameter = parameter.nestedIfOptional();
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
return true;
}
else if (this.useDefaultResolution) {
return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
}
else {
return false;
}
}
}
下面画个图来描述一下这里的过程
这里也就说明了,@RequestParam也支持文件上传,也支持获取得到请求参数,也支持参数上写的是Map但是指定了对应的name的值。那么接下来看看是如何来进行解析的。
解析
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
// Check for null value after conversion of incoming argument value
if (arg == null && namedValueInfo.defaultValue == null &&
namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
}
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
首先获取得到@RequestParam的name或者是value的值,如果为空,抛出异常。
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
然后获取得到参数上的名称,这里针对了上面的是否是文件还是普通的获取得到请求参数的值。
如果是文件,可以看到直接转换成了对应的集合名称;
如果是路径变量,这里直接从reqeust域对象中来获取得到对应的值
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
if (servletRequest != null) {
Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
return mpArg;
}
}
Object arg = null;
MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
if (multipartRequest != null) {
List files = multipartRequest.getFiles(name);
if (!files.isEmpty()) {
arg = (files.size() == 1 ? files.get(0) : files);
}
}
if (arg == null) {
String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
}
return arg;
}
然后从WebDataBinder来将对应的值来进行转换得到我们想到转换的类型。因为这里从前端传递过来的参数都是byte类型的。
如果是文件,那么需要转换到文件类型;如果提交的是普通字符串的字符,那么我们需要转换成对应的类型。
这里的转换过程后面会继续提到。
RequestParamMapMethodArgumentResolver
判断
public boolean supportsParameter(MethodParameter parameter) {
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
return (requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
!StringUtils.hasText(requestParam.name()));
}
直接判断方法参数上是否有RequestParam注解,并且是Map类型的以及RequestParam注解中的属性不能有值。
解析
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 parts = servletRequest.getParts();
LinkedMultiValueMap result = new LinkedMultiValueMap<>(parts.size());
for (Part part : parts) {
result.add(part.getName(), part);
}
return result;
}
return new LinkedMultiValueMap<>(0);
}
else {
Map parameterMap = webRequest.getParameterMap();
MultiValueMap 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 parts = servletRequest.getParts();
LinkedHashMap 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 parameterMap = webRequest.getParameterMap();
Map result = CollectionUtils.newLinkedHashMap(parameterMap.size());
parameterMap.forEach((key, values) -> {
if (values.length > 0) {
result.put(key, values[0]);
}
});
return result;
}
}
}
得到对应值来进行转换,可以看到这里也可以书写其他类型的map,甚至里面保存了文件类型都是可以的。这里不在来进行赘述了。
PathVariableMethodArgumentResolver
判断是否支持
public boolean supportsParameter(MethodParameter parameter) {
if (!parameter.hasParameterAnnotation(PathVariable.class)) {
return false;
}
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
}
return true;
}
方法上如果没有PathVariable直接返回false,如果是map类型,必须要有值。和上面的RequestParamMethodArgumentResolver类似
解析
解析过程和RequestParamMethodArgumentResolver是一样的。
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
// Check for null value after conversion of incoming argument value
if (arg == null && namedValueInfo.defaultValue == null &&
namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
}
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
PathVariableMapMethodArgumentResolver
路径变量map解析器,那么对应的肯定还有一个PathVariableMethodArgumentResolver,因为这里是去掉map的。
判断是否支持
public boolean supportsParameter(MethodParameter parameter) {
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
return (ann != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
!StringUtils.hasText(ann.value()));
}
判断是否有PathVariable注解以及是否是Map类型以及对应的值不能够有值,因为是获取得到所有的。
解析
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
@SuppressWarnings("unchecked")
Map uriTemplateVars =
(Map) webRequest.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
if (!CollectionUtils.isEmpty(uriTemplateVars)) {
return new LinkedHashMap<>(uriTemplateVars);
}
else {
return Collections.emptyMap();
}
}
获取得到所有的参数来进行封装成LinkedHashMap,然后将其返回。所以可以看到为什么从外部接收来到的参数是LinkedHashMap类型的,这里需要注意下
MatrixVariableMethodArgumentResolver
MatrixVariableMapMethodArgumentResolver
ServletRequestMethodArgumentResolver
判断是否支持
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (WebRequest.class.isAssignableFrom(paramType) ||
ServletRequest.class.isAssignableFrom(paramType) ||
MultipartRequest.class.isAssignableFrom(paramType) ||
HttpSession.class.isAssignableFrom(paramType) ||
(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
(Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations()) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
}
这些是用来判断是否原生的请求方式,也就是说我们的参数上可以写这种原生的请求参数类型
解析
这里的注释很明显了,那么就不再赘述了
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();
// WebRequest / NativeWebRequest / ServletWebRequest
if (WebRequest.class.isAssignableFrom(paramType)) {
if (!paramType.isInstance(webRequest)) {
throw new IllegalStateException(
"Current request is not of type [" + paramType.getName() + "]: " + webRequest);
}
return webRequest;
}
// ServletRequest / HttpServletRequest / MultipartRequest / MultipartHttpServletRequest
if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) {
return resolveNativeRequest(webRequest, paramType);
}
// HttpServletRequest required for all further argument types
return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class));
}
ServletRequestMapMethodArgumentResolver
一看就是和上面的类似
RequestResponseBodyMethodProcessor
判断是否支持
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}
直接判断是否有RequestBody注解,这个也是我们常用的一种方式
解析
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
protected Object adaptArgumentIfNecessary(@Nullable Object arg, MethodParameter parameter) {
if (parameter.getParameterType() == Optional.class) {
if (arg == null || (arg instanceof Collection && ((Collection<?>) arg).isEmpty()) ||
(arg instanceof Object[] && ((Object[]) arg).length == 0)) {
return Optional.empty();
}
else {
return Optional.of(arg);
}
}
return arg;
}
直接拿到消息转换器来进行转换,我们最常用的是将这里的@RequestBody添加对应的自定义类型,接收得到json数据来进行转换。
RequestPartMethodArgumentResolver
判断是否支持
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return true;
}
else {
if (parameter.hasParameterAnnotation(RequestParam.class)) {
return false;
}
return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
}
}
方法参数上如果标注了RequestPart注解或者是RequestPart对应的集合或者是数组类型,那么直接返回true,
这里就非常类似RequestParamMethodArgumentResolver的解析过程。
解析
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional());
String name = getPartName(parameter, requestPart);
parameter = parameter.nestedIfOptional();
Object arg = null;
Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
arg = mpArg;
}
else {
try {
HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, name);
arg = readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType());
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(request, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
}
catch (MissingServletRequestPartException | MultipartException ex) {
if (isRequired) {
throw ex;
}
}
}
if (arg == null && isRequired) {
if (!MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
throw new MultipartException("Current request is not a multipart request");
}
else {
throw new MissingServletRequestPartException(name);
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
RequestHeaderMethodArgumentResolver
判断是否支持
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(RequestHeader.class) &&
!Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType()));
}
判断上是否有RequestHeader注解,而且参数类型不是map
解析
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
// Check for null value after conversion of incoming argument value
if (arg == null && namedValueInfo.defaultValue == null &&
namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
}
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
String[] headerValues = request.getHeaderValues(name);
if (headerValues != null) {
return (headerValues.length == 1 ? headerValues[0] : headerValues);
}
else {
return null;
}
}
直接调用原生的request从请求头中来进行获取得到对应的名称的响应头
RequestHeaderMapMethodArgumentResolver
判断是否支持
和上面的类似:
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(RequestHeader.class) &&
Map.class.isAssignableFrom(parameter.getParameterType()));
}
解析
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();
if (MultiValueMap.class.isAssignableFrom(paramType)) {
MultiValueMap result;
if (HttpHeaders.class.isAssignableFrom(paramType)) {
result = new HttpHeaders();
}
else {
result = new LinkedMultiValueMap<>();
}
for (Iterator iterator = webRequest.getHeaderNames(); iterator.hasNext();) {
String headerName = iterator.next();
String[] headerValues = webRequest.getHeaderValues(headerName);
if (headerValues != null) {
for (String headerValue : headerValues) {
result.add(headerName, headerValue);
}
}
}
return result;
}
else {
Map result = new LinkedHashMap<>();
for (Iterator iterator = webRequest.getHeaderNames(); iterator.hasNext();) {
String headerName = iterator.next();
String headerValue = webRequest.getHeader(headerName);
if (headerValue != null) {
result.put(headerName, headerValue);
}
}
return result;
}
}
ServletModelAttributeMethodProcessor
判断是否支持
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
public static boolean isSimpleValueType(Class<?> type) {
return (Void.class != type && void.class != type &&
(ClassUtils.isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
Locale.class == type ||
Class.class == type));
}
方法参数上没有ModelAttribute注解,我们可以在方法上写上对应的自定义的数据类型,所以这种参数处理器就可以来进行处理
解析
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 = 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 {
// Create attribute instance
try {
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
else {
attribute = ex.getTarget();
}
bindingResult = ex.getBindingResult();
}
}
if (bindingResult == null) {
// Bean property binding and validation;
// skipped in case of binding failure on construction.
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
// Add resolved attribute and BindingResult at the end of the model
Map bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
}
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean -- Integer)
byte -- > file


未来我们可以给WebDataBinder里面放自己的Converter;
**private static final class **StringToNumber
//1、WebMvcConfigurer定制化SpringMVC的功能
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter() {
@Override
public Pet convert(String source) {
// 啊猫,3
if(!StringUtils.isEmpty(source)){
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
};
}
比如说springmvc不支持将yyyy-MM-dd的类型的,我们也可以自定义的添加进来进行转换使用,也是非常方便的。
例子:
public class MyDateConverter implements Converter {
private SimpleDateFormat format = new SimpleDateFormat();
@Override
public Date convert(String source) {
if (source == null) {
// return null;
throw new RuntimeException("日期为空,不能转换");
}
try {
//先尝试以yyyy/MM/dd解析转换
format.applyPattern("yyyy/MM/dd");
return format.parse(source);
} catch (ParseException e) {}
try {
//再尝试以yyyy-MM-dd解析转换
format.applyPattern("yyyy-MM-dd");
return format.parse(source);
} catch (ParseException e) {
throw new RuntimeException("不支持的日期格式");
}
}
}
RequestResponseBodyMethodProcessor
判断是否支持
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}
这是只是来判断是否支持
解析
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
首先拿到所有的参数解析器来进行解析,也就是说在@RequestBody标注了的情况下,会使用到消息转换器来进行使用。这个也会在响应阶段使用到,但是因为使用到的方法是不同的而已。
protected Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
if (arg == null && checkRequired(parameter)) {
throw new HttpMessageNotReadableException("Required request body is missing: " +
parameter.getExecutable().toGenericString(), inputMessage);
}
return arg;
}
使用消息转换器来进行读取
protected Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
MediaType contentType;
boolean noContentType = false;
try {
// 获取得到请求头中的内容类型
contentType = inputMessage.getHeaders().getContentType();
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
if (contentType == null) {
noContentType = true;
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
// handler所在的类的controller
Class<?> contextClass = parameter.getContainingClass();
// 目标类型
Class targetClass = (targetType instanceof Class ? (Class) targetType : null);
if (targetClass == null) {
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
targetClass = (Class) resolvableType.resolve();
}
HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
Object body = NO_VALUE;
EmptyBodyCheckingHttpInputMessage message;
try {
// 拿到流对应的数据
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
// 遍历所有的消息转换器来进行转换,可以看到genericConverter.canRead,哪种消息转换器是支持的,支持了之后就会来读取
// 将对应的值读取到参数上去,因为每种参数处理器处理的不同,所以可以支持不同类型的
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class> converterType = (Class>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter) converter).read(targetClass, msgToUse));
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
catch (IOException ex) {
throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
}
if (body == NO_VALUE) {
if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
(noContentType && !message.hasBody())) {
return null;
}
throw new HttpMediaTypeNotSupportedException(contentType,
getSupportedMediaTypes(targetClass != null ? targetClass : Object.class));
}
MediaType selectedContentType = contentType;
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
});
return body;
}
如果是自定义类型,将会使用JACSON转换工具类来进行转换,也就要求了传递进来的属性要和java中的属性要求一致,才能够进行转换。
但是这里也发现了一个隐蔽的地方:
if (message.hasBody()) {
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter) converter).read(targetClass, msgToUse));
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
如果方法参数上加了@RequestBody注解,那么在使用的时候可以在读取之前和读取之后来做一些处理以及方法为空的时候来做一些处理。
这里都是可以来进行操作的。
在这里来放两个链接上来:
处理问题:在实际项目中,我们常常需要在请求前后进行一些操作,比如:参数解密/返回结果加密,打印请求参数和返回结果的日志等。这些与业务无关的东西,我们不希望写在controller方法中,造成代码重复可读性变差。这里,我们讲讲使用@ControllerAdvice和RequestBodyAdvice、ResponseBodyAdvice来对请求前后进行处理(本质上就是AOP),来实现日志记录每一个请求的参数和返回结果。
https://blog.csdn.net/pongandnoon/article/details/84555414
https://blog.csdn.net/jsq6681993/article/details/119712621
分为了请求前处理和请求后处理,可以参考下对应的案例。但是我看评论区中,有一个评论说的:
那种name value形式的请求参数,如何在参数绑定成功之后,实现类似的拦截呢 ?
其实这种也可以利用AOP来进行实现即可,也不为一种优雅的方式。
这里也放上我在练习中使用的代码:https://gitee.com/guangjava/springboot-project/blob/master/springboot-web-02/src/main/java/com/guang/springbootrequestversion/aspect/AopLogAspect.java
在对应的writeWithMessageConverters方法中:
protected void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
.................
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
在进行响应之前,也可以写入对应的操作,可以来进行自定义操作。
3、支持类型
总结一下上面的参数处理器来进行处理类型
注解类型
@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody
ServletAPI
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
复杂参数
Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
4、定制化web操作
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多场景我们都无需自定义配置)
The auto-configuration adds the following features on top of Spring’s defaults:
-
Inclusion of
ContentNegotiatingViewResolverandBeanNameViewResolverbeans. -
- 内容协商视图解析器和BeanName视图解析器
-
Support for serving static resources, including support for WebJars (covered later in this document)).
-
- 静态资源(包括webjars)
-
Automatic registration of
Converter,GenericConverter, andFormatterbeans. -
- 自动注册
Converter,GenericConverter,Formatter
- 自动注册
-
Support for
HttpMessageConverters(covered later in this document). -
- 支持
HttpMessageConverters(后来我们配合内容协商理解原理)
- 支持
-
Automatic registration of
MessageCodesResolver(covered later in this document). -
- 自动注册
MessageCodesResolver(国际化用)
- 自动注册
-
Static
index.htmlsupport. -
- 静态index.html 页支持
-
Custom
Faviconsupport (covered later in this document). -
- 自定义
Favicon
- 自定义
-
Automatic use of a
ConfigurableWebBindingInitializerbean (covered later in this document). -
- 自动使用
ConfigurableWebBindingInitializer,(DataBinder负责将请求数据绑定到JavaBean上)
- 自动使用
If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.
不用@EnableWebMvc注解。使用 **@Configuration** + **WebMvcConfigurer** 自定义规则
If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.
声明 **WebMvcRegistrations** 改变默认底层组件
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.
**使用 ****@EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC**
从上面可以看到所有的参数解析器都会来实现一个接口:HandlerMethodArgumentResolver,并重写其中的方法来进行操作。
public interface HandlerMethodArgumentResolver {
// 判断是否支持
boolean supportsParameter(MethodParameter parameter);
// 支持之后,如何来进行处理。返回值最终需要来赋值给参数
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
那么我们也可以来自定义一个并将对应的参数解析器放置到容器中进行管理。
示例一
贴上对应的链接:
一、问题描述
前端把 token 存放在请求头里面,后端接口如果需要获取当前登录用户信息,我们是使用 HttpServletRequest 来获取请求头的 token,但是如果后续很多接口都需要获取当前登录用户信息,此时就出现代码重复问题。
注:最佳方式是在拦截器中来进行操作。但是这里为了使用参数解析器,所以在这里来演示一下对应的使用方式。
二、假设分析
我们能不能做出一个接口参数,让所有访问这个具备指定参数的接口都能获取当前登录用户对象,该怎么实现?
三、应用技术
使用技术:SpringMVC 参数解析器
解析器有两种:
- SpringMVC 默认解析器
- 自定义参数解析器
作用:将接口(请求映射方法)UserInfo 类型的参数解析成当前登录用户对象
这两种,选哪个呢?不难看出,因为默认参数解析器是无法实现当前登录用户对象注入,此时选择第二种。
四、代码实现
业务需要区分场景的时候(比如登录与未登录),可以编写一个自定义注解
编写一个自定义注解,放于 annotation 包下
@Target(ElementType.PARAMETER) // 参数
@Retention(RetentionPolicy.RUNTIME)
public @interface UserParam {
}
新建一个 resolver 包,编写自定义解析器
/**
* 自定义参数解析器
* 作用:
* 将接口中声明 UserInfo 类型的参数,解析成当前登录用户对象,并注入
*/
public class UserInfoArgumentResolver implements HandlerMethodArgumentResolver {
@Autowired
private IUserInfoRedisService userInfoRedisService;
//用于识别接口参数类型
//指定当前解析器能解析接口参数的类型: 此处是UserInfo.class类型
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterType() == UserInfo.class
&& methodParameter.hasParameterAnnotation(UserParam.class);
}
//用于解析接口参数,并注入参数值
//前提: supportsParameter 方法返回true之后执行
@Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
String token = request.getHeader("token");
UserInfo userInfo = userInfoRedisService.getUserByToken(token);
return userInfo;
}
}
配置类
@Configuration
public class WebsiteConfig implements WebMvcConfigurer {
// 参数解析器
@Bean
public UserInfoArgumentResolver userInfoArgumentResolver(){
return new UserInfoArgumentResolver();
}
// 加进 HandlerMethodArgumentResolver 里面作为它的实现类
@Override
public void addArgumentResolvers(List resolvers) {
resolvers.add(userInfoArgumentResolver());
}
}
controller层
@GetMapping("/userInfo")
@ResponseBody
public Object user(@UserParam UserInfo user){
return JsonResult.success(user);
}
以上,代码已实现功能
对应的流程图如下所示:
示例二
1、问题描述
前端样例
Title
liguang,欢迎您
测试封装POJO;
想要在提交的的时候自动将啊猫,3封装成对应的Cat对象
2、代码实现
1、定义注解
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserParam {
}
2、自定义参数解析器
public class BindHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
/**
* 方法参数对象,可以获取得到任意方法的信息
*
* @param parameter
* @return
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType() == Cat.class && parameter.hasParameterAnnotation(UserParam.class);
}
/**
* supportsParameter返回true,那么如何来进行解析
*
* @param parameter
* @param mavContainer
* @param webRequest
* @param binderFactory
* @return
* @throws Exception
*/
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// 获取得到原生的请求
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
String name = request.getParameter("name");
String age = request.getParameter("age");
int catAge = Integer.parseInt(age);
Cat cat = new Cat();
cat.setAge(catAge);
cat.setName(name);
return cat;
}
}
3、添加到配置中
@Configuration(proxyBeanMethods = false)
public class CustomWebMvcConfigurer implements WebMvcConfigurer {
private static final Logger logger = LoggerFactory.getLogger(CustomWebMvcConfigurer.class);
/**
* 自定义参数解析器
* @param resolvers
*/
@Override
public void addArgumentResolvers(List resolvers) {
resolvers.add(new BindHandlerMethodArgumentResolver());
}
}
5、总结
5.1、参数解析器
学会了SpringMVC中是怎么来确定每个参数的值的,循环遍历每个参数,获取得到对应的解析器,通过解析器来进行解析,在解析的过程中会利用到各种工具来进行操作。
5.2、类型转换器
如果是获取得到原生的API的话,那么将会从request域对象中直接来进行获取;
如果获取不是原生的API,而是自定义的数据类型,那么将会调用类型转换器来进行转换,在SpringMVC中内置了很多的类型转换器,如果不符合我们的条件的话,我们也可以自定义对应的数据类型转换器;
5.3、自定义参数解析器
将我们自定义的参数解析器来进行解析
5.4、自定义SpringMVC中的组件
推荐使用@Configuration+WebConfigure方法来进行注入对应的组件到容器中来
5.5、类型转换器和自定义参数解析器的理解
参数解析器是针对参数来说的,看看是否支持解析。而类型转换器是将request域对象中的数据转换到对应的数据类型中来,先支持,然后解析,在解析的过程中来进行数据转换,所以二者相对来说,粒度是不同的。