SpringMVC参数绑定源码解析

1,714 阅读1分钟

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>接口,并将实现类追加到容器中。