Spring MVC中的HandlerMethodArgumentResolver

Spring MVC中的HandlerMethodArgumentResolver

什么是HandlerMethodArgumentResolver(处理器方法参数解析器)?它有什么用?这篇博客用于记录对HandlerMethodArgumentResolver的理解和用法。

什么是处理器方法参数解析器?

首先什么是Handler?我的理解是Spring MVC中真正用于处理业务逻辑的类,也就是常说的、标注了@Controller注解的Controller类;Controller类中使用@RequestMapping(或GetMapping等变种注解)标注的方法称为处理器方法;处理器方法的参数指的就是该方法的参数。
处理器方法参数解析器HandlerMethodArgumentResolver用于自动向处理器方法参数注入值(自动赋值)
从代码角度说HandlerMethodArgumentResolver是一个接口,开发者实现该接口可以实现自定义的Controller方法的参数的自动注入。

public interface HandlerMethodArgumentResolver {
 /**
  * 对应的方法参数是否支持该处理器解析处理
  * @param 要检查的方法参数,MethodParameter Spring MVC中对方法参数的包装类
  * @return 如果支持该参数则返回true,否则返回false
  */
 boolean supportsParameter(MethodParameter parameter);

 /**
  * 从给定的request中解析出方法参数所需要的值,并返回
  * ModelAndViewContainer 提供了request中的model
  * WebDataBinderFactory 提供了创建WebDataBinder实例的方法(当需要绑定数据和类型转换时)
  * @param parameter 要解析的方法参数。
  * @param mavContainer 当前请求的ModelAndViewContainer
  * @param webRequest 当前请求对象request
  * @param binderFactory 创建WebDataBinder实例的工厂
  * @return 返回解析后的参数值,如果不能解析返回null
  * @throws Exception 如果解析参数值出错抛出异常
  */
 @Nullable
 Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
 		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}

该接口应用了策略模式,让不同的子类有不同的实现,进而实现各种类型参数的解析。

接下来看几个Spring MVC帮我们实现的参数解析器!

在Controller的方法参数中直接使用HttpServletRequest对象

我们经常在controller的方法中直接这样写就可以拿到HttpServletRequest对象和HttpServletResponse对象:

@Controller
public class TestController {
	@GetMapping
	@ResponseBody
	public String test(HttpServletRequest request, HttpServletResponse response){
        //...
		return "ok";
	}
}

在test方法中直接可以用request和response,很方便,无需开发者自己去获取即可使用。这就是Spring的优雅及魅力所在,它能干的都帮你干了!
空穴来风是不可能的!原因在于Spring MVC帮我们实现了HttpServletRequest和HttpServletResponse参数类型的解析。看源码:

public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
    @Nullable
    private static Class<?> pushBuilder;

    public 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) || 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();
        if (WebRequest.class.isAssignableFrom(paramType)) {
            if (!paramType.isInstance(webRequest)) {
                throw new IllegalStateException("Current request is not of type [" + paramType.getName() + "]: " + webRequest);
            } else {
                return webRequest;
            }
        } else {
            return !ServletRequest.class.isAssignableFrom(paramType) && !MultipartRequest.class.isAssignableFrom(paramType) ? this.resolveArgument(paramType, (HttpServletRequest)this.resolveNativeRequest(webRequest, HttpServletRequest.class)) : this.resolveNativeRequest(webRequest, paramType);
        }
    }
 }
 //...省略其他代码

使用WebRequest.class.isAssignableFrom(paramType) || ServletRequest.class.isAssignableFrom(paramType)等判断参数的类型是否支持,可以看到它不仅支持HttpServletRequestHttpServletResponse参数还支持HttpSessionServletInputStream等参数类型。

在Controller的方法参数中使用@RequestParam注解来接受参数

看代码示例:

@Controller
public class TestController {
	@GetMapping("/test/map")
	@ResponseBody
	public Map<String, Object> testMapParam(@RequestParam Map<String,Object> map){
		return map;
	}
}

Spring MVC自动将web参数封装到map中,key是参数名,value是参数值。这里我们要接受前端传过来的参数必须加@RequestParam注解否则Spring MVC会将参数解析为ModelAndView对象中的Model Map对象,导致取不到参数值。
使用@RequestParam标注的参数类型不只支持Map还支持普通类型比如int,String,基本类型的数组和日期等数据类型,对于这些基本数据类型无需使用@RequestParam注解。

public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
		implements UriComponentsContributor {
		//省略其他代码

	/**
	 * Supports the following:
	 * <ul>
	 * <li>@RequestParam-annotated method arguments.
	 * This excludes {@link Map} params where the annotation does not specify a name.
	 * See {@link RequestParamMapMethodArgumentResolver} instead for such params.
	 * <li>Arguments of type {@link MultipartFile} unless annotated with @{@link RequestPart}.
	 * <li>Arguments of type {@code Part} unless annotated with @{@link RequestPart}.
	 * <li>In default resolution mode, simple type arguments even if not with @{@link RequestParam}.
	 * </ul>
	 */
	@Override
	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;
			}
		}
	}
}

在Controller的方法中使用@RequestBody获取Json参数

@PostMapping("/test/json")
	@ResponseBody
	public Map<String, Object> testJsonParam(@RequestBody Map<String,Object> map){
		return map;
	}

传Json参数,必须使用@RequestBody注解和POST请求。

RequestResponseBodyMethodProcessor类负责处理@RequestBodyJSON参数的注入和@ResponseBody的返回值。

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
    public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters) {
        super(converters);
    }

    public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters, @Nullable ContentNegotiationManager manager) {
        super(converters, manager);
    }

    public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters, @Nullable List<Object> requestResponseBodyAdvice) {
        super(converters, (ContentNegotiationManager)null, requestResponseBodyAdvice);
    }

    public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters, @Nullable ContentNegotiationManager manager, @Nullable List<Object> requestResponseBodyAdvice) {
        super(converters, manager, requestResponseBodyAdvice);
    }

    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RequestBody.class);
    }

    public boolean supportsReturnType(MethodParameter returnType) {
        return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class);
    }
    //省略其他代码...
}

Spring MVC除了支持这些参数的解析,还支持@CookieValue注解对Cookie数据进行参数解析和赋值,实现类为ServletCookieValueMethodArgumentResolver。如果这些都不能满足你的需求还可以自定义参数解析器,实现特定类型参数的解析,只需要实现接口HandlerMethodArgumentResolver即可!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cloud-Future

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值