spring容器中内置了许多的参数解析器
,这些参数解析器在《HandlerAdapter源码解析》有提到,有兴趣的小伙伴可以看一看。今天我们来谈一谈参数解析器是如何工作的。
第一步:初始化定义的参数
当定义一个Controller时,我们会写一些形参来接收参数。还记得DispatchServlet中返回一个HandlerExecutionChain
,那么其中的HandlerMethod就封装了我们定义的Handler信息(包括参数信息)。
private HandlerMethod(HandlerMethod handlerMethod, Object handler) {
this.bean = handler;
this.beanFactory = handlerMethod.beanFactory;
this.beanType = handlerMethod.beanType;
this.method = handlerMethod.method;
this.bridgedMethod = handlerMethod.bridgedMethod;
// private final MethodParameter[] parameters;
this.parameters = handlerMethod.parameters;
this.responseStatus = handlerMethod.responseStatus;
this.responseStatusReason = handlerMethod.responseStatusReason;
this.resolvedFromHandlerMethod = handlerMethod;
this.description = handlerMethod.description;
}
从上面可以得知,springmvc会创建一个HandlerMethod,其中this.parameters就封装了我们的参数信息。
第二步:解析参数
从DispatchServlet的HandlerApdater.handle()
方法中,可以追踪到如下代码,说明参数绑定是在handleMethod方法执行的时候才开始的。
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
}
// InvocableHandlerMethod.java
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// 将第一步初始化的参数信息,全部取出
MethodParameter[] parameters = this.getMethodParameters();
Object[] args = new Object[parameters.length];
for(int i = 0; i < parameters.length; ++i) {
// 遍历解析每一个参数
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
return args;
}
当得到所有的参数时,我们需要对其进行解析。由于每一个参数的类型不同,所需要的的参数解析器不同,所以我们遍历每一个参数,并为其挑选合适的参数解析器进行参数解析
。
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 根据当前参数,挑选出合适的参数解析器。eg:针对@RequestParam有特定的参数解析器
HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
} else {
// 根据所挑选的参数解析器,进行参数解析
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
}
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 以下三行获取参数信息,得到参数名称
AbstractNamedValueMethodArgumentResolver.NamedValueInfo namedValueInfo = this.getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
// 根据参数名,从请求中获取参数值。
Object arg = this.resolveName(resolvedName.toString(), nestedParameter, webRequest);
// 创建web数据绑定器
WebDataBinder binder = binderFactory.createBinder(webRequest, (Object)null, namedValueInfo.name);
// 如果必要,可以进行参数转换。把字符串形式的参数转换成服务端真正需要的类型的转换
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
// 空实现,子类可以实现,进行定制化
this.handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
根据参数名,从请求中获取参数值。底层使用的还是传统开发模式中的request.getParameterValues("argName")
。核心处理代码中涉及到参数解析器
和数据绑定器(DataBinder)
对参数进行先后处理。注意SpringMVC是WebDataBinder。
第三步:考虑扩展
spring内置
了许多参数解析器
和数据绑定器
,但如果遇到内置无法解决的参数,开发人员可以根据实际参数类型,添加合适的参数解析器
和数据绑定器(DataBinder)
,对特殊的数据进行处理。
自定义参数解析器需要实现HandlerMethodArgumentResolver
接口,然后将实现类追加到
容器中。
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private CusHandlerMethodArgumentResolver cusHandlerMethodArgumentResolver;
// 添加自定义参数解析器
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(cusHandlerMethodArgumentResolver);
}
}
数据绑定器,不仅可以完成数据类型转换,还可以进行参数校验。开发人员可以自定义DataBinder通过实现Converter<S, T>
接口,并将实现类追加到
容器中。